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.
Files changed (114) hide show
  1. package/package.json +2 -6
  2. package/template/AGENTS.md +143 -0
  3. package/template/CLAUDE.md +25 -46
  4. package/template/README.md +6 -12
  5. package/template/_gitignore +0 -1
  6. package/template/dist/assets/index-BEylY2j7.css +1 -0
  7. package/template/dist/assets/index-CnYqArKN.js +8741 -0
  8. package/template/dist/assets/sia_bg-BTOHUC1A.wasm +0 -0
  9. package/template/dist/assets/slab-download-worker-DhW6ZBJs.js +2 -0
  10. package/template/dist/assets/slab-upload-worker-B2uSB2iY.js +2 -0
  11. package/template/dist/index.html +13 -0
  12. package/template/e2e/smoke.spec.ts +20 -0
  13. package/template/index.html +0 -1
  14. package/template/package.json +2 -2
  15. package/template/playwright.config.ts +13 -0
  16. package/template/src/components/Navbar.tsx +3 -3
  17. package/template/src/components/auth/ApproveScreen.tsx +10 -13
  18. package/template/src/components/auth/AuthFlow.tsx +13 -10
  19. package/template/src/components/auth/ConnectScreen.tsx +2 -3
  20. package/template/src/components/auth/RecoveryScreen.tsx +7 -7
  21. package/template/src/components/upload/UploadZone.tsx +192 -74
  22. package/template/src/index.css +14 -4
  23. package/template/src/stores/auth.ts +6 -12
  24. package/template/test-results/.last-run.json +4 -0
  25. package/template/tsconfig.app.json +1 -1
  26. package/template/tsconfig.node.json +0 -1
  27. package/template/vite.config.ts +2 -3
  28. package/template/rust/README.md +0 -16
  29. package/template/rust/sia-sdk-rs/.changeset/added_cancel_function_to_cancel_inflight_packed_uploads.md +0 -6
  30. package/template/rust/sia-sdk-rs/.changeset/check_if_we_have_enough_hosts_prior_to_encoding_in_upload_slabs.md +0 -16
  31. package/template/rust/sia-sdk-rs/.changeset/fix_slab_length_in_packed_object.md +0 -5
  32. package/template/rust/sia-sdk-rs/.changeset/fix_upload_racing_race_conditon.md +0 -13
  33. package/template/rust/sia-sdk-rs/.changeset/improved_parallelism_of_packed_uploads.md +0 -5
  34. package/template/rust/sia-sdk-rs/.changeset/progress_callback_will_now_be_called_as_expected_for_packed_uploads.md +0 -5
  35. package/template/rust/sia-sdk-rs/.github/dependabot.yml +0 -10
  36. package/template/rust/sia-sdk-rs/.github/workflows/main.yml +0 -36
  37. package/template/rust/sia-sdk-rs/.github/workflows/prepare-release.yml +0 -34
  38. package/template/rust/sia-sdk-rs/.github/workflows/release.yml +0 -30
  39. package/template/rust/sia-sdk-rs/.rustfmt.toml +0 -4
  40. package/template/rust/sia-sdk-rs/Cargo.lock +0 -4127
  41. package/template/rust/sia-sdk-rs/Cargo.toml +0 -3
  42. package/template/rust/sia-sdk-rs/LICENSE +0 -21
  43. package/template/rust/sia-sdk-rs/README.md +0 -30
  44. package/template/rust/sia-sdk-rs/indexd/CHANGELOG.md +0 -79
  45. package/template/rust/sia-sdk-rs/indexd/Cargo.toml +0 -79
  46. package/template/rust/sia-sdk-rs/indexd/benches/upload.rs +0 -258
  47. package/template/rust/sia-sdk-rs/indexd/src/app_client.rs +0 -1710
  48. package/template/rust/sia-sdk-rs/indexd/src/builder.rs +0 -354
  49. package/template/rust/sia-sdk-rs/indexd/src/download.rs +0 -379
  50. package/template/rust/sia-sdk-rs/indexd/src/hosts.rs +0 -659
  51. package/template/rust/sia-sdk-rs/indexd/src/lib.rs +0 -827
  52. package/template/rust/sia-sdk-rs/indexd/src/mock.rs +0 -162
  53. package/template/rust/sia-sdk-rs/indexd/src/object_encryption.rs +0 -125
  54. package/template/rust/sia-sdk-rs/indexd/src/quic.rs +0 -575
  55. package/template/rust/sia-sdk-rs/indexd/src/rhp4.rs +0 -52
  56. package/template/rust/sia-sdk-rs/indexd/src/slabs.rs +0 -497
  57. package/template/rust/sia-sdk-rs/indexd/src/upload.rs +0 -629
  58. package/template/rust/sia-sdk-rs/indexd/src/wasm_time.rs +0 -41
  59. package/template/rust/sia-sdk-rs/indexd/src/web_transport.rs +0 -398
  60. package/template/rust/sia-sdk-rs/indexd_ffi/CHANGELOG.md +0 -76
  61. package/template/rust/sia-sdk-rs/indexd_ffi/Cargo.toml +0 -47
  62. package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/README.md +0 -10
  63. package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/example.py +0 -130
  64. package/template/rust/sia-sdk-rs/indexd_ffi/src/bin/uniffi-bindgen.rs +0 -3
  65. package/template/rust/sia-sdk-rs/indexd_ffi/src/builder.rs +0 -377
  66. package/template/rust/sia-sdk-rs/indexd_ffi/src/io.rs +0 -155
  67. package/template/rust/sia-sdk-rs/indexd_ffi/src/lib.rs +0 -1039
  68. package/template/rust/sia-sdk-rs/indexd_ffi/src/logging.rs +0 -58
  69. package/template/rust/sia-sdk-rs/indexd_ffi/src/tls.rs +0 -23
  70. package/template/rust/sia-sdk-rs/indexd_wasm/Cargo.toml +0 -33
  71. package/template/rust/sia-sdk-rs/indexd_wasm/src/lib.rs +0 -818
  72. package/template/rust/sia-sdk-rs/knope.toml +0 -54
  73. package/template/rust/sia-sdk-rs/sia_derive/CHANGELOG.md +0 -38
  74. package/template/rust/sia-sdk-rs/sia_derive/Cargo.toml +0 -19
  75. package/template/rust/sia-sdk-rs/sia_derive/src/lib.rs +0 -278
  76. package/template/rust/sia-sdk-rs/sia_sdk/CHANGELOG.md +0 -91
  77. package/template/rust/sia-sdk-rs/sia_sdk/Cargo.toml +0 -59
  78. package/template/rust/sia-sdk-rs/sia_sdk/benches/merkle_root.rs +0 -12
  79. package/template/rust/sia-sdk-rs/sia_sdk/src/blake2.rs +0 -22
  80. package/template/rust/sia-sdk-rs/sia_sdk/src/consensus.rs +0 -767
  81. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v1.rs +0 -257
  82. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v2.rs +0 -291
  83. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding.rs +0 -26
  84. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async/v2.rs +0 -367
  85. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async.rs +0 -6
  86. package/template/rust/sia-sdk-rs/sia_sdk/src/encryption.rs +0 -303
  87. package/template/rust/sia-sdk-rs/sia_sdk/src/erasure_coding.rs +0 -347
  88. package/template/rust/sia-sdk-rs/sia_sdk/src/lib.rs +0 -15
  89. package/template/rust/sia-sdk-rs/sia_sdk/src/macros.rs +0 -435
  90. package/template/rust/sia-sdk-rs/sia_sdk/src/merkle.rs +0 -112
  91. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/merkle.rs +0 -357
  92. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/rpc.rs +0 -1507
  93. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/types.rs +0 -146
  94. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp.rs +0 -7
  95. package/template/rust/sia-sdk-rs/sia_sdk/src/seed.rs +0 -278
  96. package/template/rust/sia-sdk-rs/sia_sdk/src/signing.rs +0 -236
  97. package/template/rust/sia-sdk-rs/sia_sdk/src/types/common.rs +0 -677
  98. package/template/rust/sia-sdk-rs/sia_sdk/src/types/currency.rs +0 -450
  99. package/template/rust/sia-sdk-rs/sia_sdk/src/types/specifier.rs +0 -110
  100. package/template/rust/sia-sdk-rs/sia_sdk/src/types/spendpolicy.rs +0 -778
  101. package/template/rust/sia-sdk-rs/sia_sdk/src/types/utils.rs +0 -117
  102. package/template/rust/sia-sdk-rs/sia_sdk/src/types/v1.rs +0 -1737
  103. package/template/rust/sia-sdk-rs/sia_sdk/src/types/v2.rs +0 -1726
  104. package/template/rust/sia-sdk-rs/sia_sdk/src/types/work.rs +0 -59
  105. package/template/rust/sia-sdk-rs/sia_sdk/src/types.rs +0 -16
  106. package/template/scripts/setup-rust.js +0 -29
  107. package/template/src/lib/format.ts +0 -35
  108. package/template/src/lib/hex.ts +0 -13
  109. package/template/src/lib/sdk.ts +0 -25
  110. package/template/src/lib/wasm-env.ts +0 -5
  111. package/template/wasm/indexd_wasm/indexd_wasm.d.ts +0 -309
  112. package/template/wasm/indexd_wasm/indexd_wasm.js +0 -1507
  113. package/template/wasm/indexd_wasm/indexd_wasm_bg.wasm +0 -0
  114. 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.7",
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
+ ```
@@ -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 WASM SDK compiled from Rust to handle encryption, uploads, downloads, and key management.
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, WASM (Rust)
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
- ### WASM SDK
25
+ ### SDK
26
26
 
27
- The SDK (`indexd_wasm`) is a Rust-compiled WASM module that handles:
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/lib/hex.ts` | Hex encode/decode utilities |
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 data = new Uint8Array(arrayBuffer)
68
- const pinnedObject = await sdk.uploadWithProgress(data, (current, total) => {
69
- console.log(`${current}/${total} shards`)
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: data.byteLength,
67
+ size: file.size,
75
68
  hash: '...',
76
69
  })))
77
- await sdk.pinObject(pinnedObject)
78
- await sdk.updateObjectMetadata(pinnedObject)
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 object = await sdk.object(objectId)
85
- const data = await sdk.downloadWithProgress(object, (current, total) => {
86
- console.log(`${current}/${total} slabs`)
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
- const events = await sdk.objectEvents(null, 100)
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 = JSON.parse(new TextDecoder().decode(event.object.metadata()))
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 sdk.deleteObject(objectId)
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 = sdk.shareObject(pinnedObject, validUntil)
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 sdk.sharedObject(shareUrl)
120
- const data = await sdk.download(object)
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 SDK is available via `useAuthStore((s) => s.sdk)`.
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
@@ -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
- - **WASM SDK** — Rust-compiled SDK handles encryption, erasure coding, and direct host transfers in the browser
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 `sdk.upload(data)`. Metadata (filename, type, size) is also encrypted and pinned to the indexer.
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 sdk = useAuthStore((s) => s.sdk)
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
- - [indexd_wasm](https://github.com/SiaFoundation/sia-sdk-rs) (Sia SDK, compiled from Rust to WASM)
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/ # SDK wrapper, constants, utilities
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
- - [Sia SDK (Rust)](https://github.com/SiaFoundation/sia-sdk-rs)
96
+ - [@siafoundation/sia](https://www.npmjs.com/package/@siafoundation/sia)
@@ -2,4 +2,3 @@ node_modules
2
2
  dist
3
3
  *.local
4
4
  .DS_Store
5
- rust/sia-sdk-rs/
@@ -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)}}