tangled
alpha
login
or
join now
xan.lol
/
wisp.place-monorepo
forked from
nekomimi.pet/wisp.place-monorepo
0
fork
atom
Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol.
0
fork
atom
overview
issues
pulls
pipelines
test
nekomimi.pet
2 months ago
6cb1e859
db73a55a
+304
-189
2 changed files
expand all
collapse all
unified
split
apps
main-app
public
editor
tabs
DomainsTab.tsx
src
index.ts
+59
-2
apps/main-app/public/editor/tabs/DomainsTab.tsx
···
23
23
CheckCircle2,
24
24
XCircle,
25
25
Loader2,
26
26
-
Trash2
26
26
+
Trash2,
27
27
+
AlertCircle
27
28
} from 'lucide-react'
28
29
import type { WispDomain, CustomDomain } from '../hooks/useDomainData'
29
30
import type { UserInfo } from '../hooks/useUserInfo'
31
31
+
32
32
+
// Hosting node IP addresses for A record fallback
33
33
+
const HOSTING_NODES = [
34
34
+
{ region: 'US East (Virginia)', ip: '192.0.2.1' },
35
35
+
{ region: 'US West (California)', ip: '192.0.2.2' },
36
36
+
{ region: 'Europe (Frankfurt)', ip: '192.0.2.3' },
37
37
+
] as const
30
38
31
39
interface DomainsTabProps {
32
40
wispDomains: WispDomain[]
···
511
519
<div className="p-3 bg-background rounded border border-border">
512
520
<div className="flex justify-between items-start mb-2">
513
521
<span className="text-xs font-semibold text-muted-foreground">
514
514
-
CNAME Record (Pointing)
522
522
+
CNAME Record (Pointing) — Recommended
515
523
</span>
516
524
</div>
517
525
<div className="font-mono text-xs space-y-2">
···
534
542
</div>
535
543
<p className="text-xs text-muted-foreground mt-2">
536
544
Note: Some DNS providers (like Cloudflare) flatten CNAMEs to A records - this is fine and won't affect verification.
545
545
+
</p>
546
546
+
</div>
547
547
+
548
548
+
<div className="p-3 bg-background rounded border border-border">
549
549
+
<div className="flex items-start gap-2 mb-2">
550
550
+
<span className="text-xs font-semibold text-muted-foreground">
551
551
+
A Records (Fallback Option)
552
552
+
</span>
553
553
+
</div>
554
554
+
<div className="p-2 bg-yellow-500/10 border border-yellow-500/20 rounded mb-3 flex gap-2">
555
555
+
<AlertCircle className="w-4 h-4 text-yellow-600 shrink-0 mt-0.5" />
556
556
+
<p className="text-xs text-yellow-700 dark:text-yellow-500">
557
557
+
<strong>Warning:</strong> Using A records instead of CNAME means you lose GeoDNS capabilities.
558
558
+
Your site will always be served from the specific node you choose below, regardless of visitor location.
559
559
+
</p>
560
560
+
</div>
561
561
+
<div className="space-y-3">
562
562
+
{HOSTING_NODES.map((node) => (
563
563
+
<div key={node.ip} className="font-mono text-xs space-y-1 pl-3 border-l-2 border-muted">
564
564
+
<div className="font-semibold text-muted-foreground mb-1">
565
565
+
{node.region}
566
566
+
</div>
567
567
+
<div>
568
568
+
<span className="text-muted-foreground">
569
569
+
Name:
570
570
+
</span>{' '}
571
571
+
<span className="select-all">
572
572
+
{domain.domain}
573
573
+
</span>
574
574
+
</div>
575
575
+
<div>
576
576
+
<span className="text-muted-foreground">
577
577
+
Type:
578
578
+
</span>{' '}
579
579
+
<span>A</span>
580
580
+
</div>
581
581
+
<div>
582
582
+
<span className="text-muted-foreground">
583
583
+
Value:
584
584
+
</span>{' '}
585
585
+
<span className="select-all">
586
586
+
{node.ip}
587
587
+
</span>
588
588
+
</div>
589
589
+
</div>
590
590
+
))}
591
591
+
</div>
592
592
+
<p className="text-xs text-muted-foreground mt-3">
593
593
+
Choose one region that best matches your primary audience location.
537
594
</p>
538
595
</div>
539
596
</div>
+245
-187
apps/main-app/src/index.ts
···
132
132
133
133
set.headers['Content-Type'] = 'text/html; charset=utf-8'
134
134
135
135
-
return `<!doctype html>
135
135
+
return `<!DOCTYPE html>
136
136
<html lang="en">
137
137
-
<head>
138
138
-
<meta charset="UTF-8" />
139
139
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
140
140
-
<title>wisp.place</title>
141
141
-
<meta name="description" content="Host static websites directly in your AT Protocol account. Keep full ownership and control with fast CDN distribution. Built on Bluesky's decentralized network." />
137
137
+
<head>
138
138
+
<meta charset="UTF-8">
139
139
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
140
140
+
<title>wisp.place</title>
141
141
+
<meta name="description" content="Host static websites directly in your AT Protocol account. Keep full ownership and control with fast CDN distribution. Built on Bluesky's decentralized network." />
142
142
143
143
-
<!-- Open Graph / Facebook -->
144
144
-
<meta property="og:type" content="website" />
145
145
-
<meta property="og:url" content="https://wisp.place/" />
146
146
-
<meta property="og:title" content="wisp.place - Decentralized Static Site Hosting" />
147
147
-
<meta property="og:description" content="Host static websites directly in your AT Protocol account. Keep full ownership and control with fast CDN distribution." />
148
148
-
<meta property="og:site_name" content="wisp.place" />
143
143
+
<!-- Open Graph / Facebook -->
144
144
+
<meta property="og:type" content="website" />
145
145
+
<meta property="og:url" content="https://wisp.place/" />
146
146
+
<meta property="og:title" content="wisp.place - Decentralized Static Site Hosting" />
147
147
+
<meta property="og:description" content="Host static websites directly in your AT Protocol account. Keep full ownership and control with fast CDN distribution." />
148
148
+
<meta property="og:site_name" content="wisp.place" />
149
149
150
150
-
<!-- Twitter -->
151
151
-
<meta name="twitter:card" content="summary_large_image" />
152
152
-
<meta name="twitter:url" content="https://wisp.place/" />
153
153
-
<meta name="twitter:title" content="wisp.place - Decentralized Static Site Hosting" />
154
154
-
<meta name="twitter:description" content="Host static websites directly in your AT Protocol account. Keep full ownership and control with fast CDN distribution." />
150
150
+
<!-- Twitter -->
151
151
+
<meta name="twitter:card" content="summary_large_image" />
152
152
+
<meta name="twitter:url" content="https://wisp.place/" />
153
153
+
<meta name="twitter:title" content="wisp.place - Decentralized Static Site Hosting" />
154
154
+
<meta name="twitter:description" content="Host static websites directly in your AT Protocol account. Keep full ownership and control with fast CDN distribution." />
155
155
156
156
-
<!-- Theme -->
157
157
-
<meta name="theme-color" content="#7c3aed" />
156
156
+
<!-- Theme -->
157
157
+
<meta name="theme-color" content="#000000" />
158
158
159
159
-
<link rel="icon" type="image/x-icon" href="./favicon.ico">
160
160
-
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png">
161
161
-
<link rel="icon" type="image/png" sizes="16x16" href="./favicon-16x16.png">
162
162
-
<link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon.png">
163
163
-
<link rel="manifest" href="./site.webmanifest">
159
159
+
<link rel="icon" type="image/x-icon" href="./favicon.ico">
160
160
+
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png">
161
161
+
<link rel="icon" type="image/png" sizes="16x16" href="./favicon-16x16.png">
162
162
+
<link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon.png">
163
163
+
<link rel="manifest" href="./site.webmanifest">
164
164
165
165
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
166
166
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
167
167
-
<link
168
168
-
href="https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500;700&display=swap"
169
169
-
rel="stylesheet"
170
170
-
/>
171
171
-
<style>
172
172
-
* {
173
173
-
margin: 0;
174
174
-
padding: 0;
175
175
-
box-sizing: border-box;
176
176
-
}
165
165
+
<link rel="preconnect" href="https://fonts.googleapis.com">
166
166
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
167
167
+
<link href="https://fonts.googleapis.com/css2?family=Fira+Mono:wght@400;500;700&display=swap" rel="stylesheet">
168
168
+
169
169
+
<style>
170
170
+
* {
171
171
+
margin: 0;
172
172
+
padding: 0;
173
173
+
box-sizing: border-box;
174
174
+
}
177
175
176
176
+
:root {
177
177
+
--bg: #fafafa;
178
178
+
--text: #000;
179
179
+
--text-muted: #666;
180
180
+
--text-subtle: #999;
181
181
+
--border: #ddd;
182
182
+
--cta-bg: #000;
183
183
+
--cta-text: #fff;
184
184
+
--cta-hover-bg: #fff;
185
185
+
--cta-hover-text: #000;
186
186
+
--code-bg: #000;
187
187
+
--code-text: #0f0;
188
188
+
--link: #000;
189
189
+
}
190
190
+
191
191
+
@media (prefers-color-scheme: dark) {
178
192
:root {
179
179
-
--bg: #ffffff;
180
180
-
--text: #1a1a1a;
181
181
-
--text-muted: #666;
182
182
-
--link: #0066cc;
183
183
-
--link-hover: #0052a3;
184
184
-
--terminal-bg: #1a1a1a;
185
185
-
--terminal-text: #e0e0e0;
186
186
-
--terminal-cyan: #5fdfdf;
193
193
+
--bg: #0a0a0a;
194
194
+
--text: #fafafa;
195
195
+
--text-muted: #999;
196
196
+
--text-subtle: #666;
197
197
+
--border: #333;
198
198
+
--cta-bg: #fff;
199
199
+
--cta-text: #000;
200
200
+
--cta-hover-bg: #0a0a0a;
201
201
+
--cta-hover-text: #fff;
202
202
+
--code-bg: #111;
203
203
+
--code-text: #0f0;
204
204
+
--link: #fff;
187
205
}
206
206
+
}
188
207
189
189
-
@media (prefers-color-scheme: dark) {
190
190
-
:root {
191
191
-
--bg: #121212;
192
192
-
--text: #e0e0e0;
193
193
-
--text-muted: #888;
194
194
-
--link: #5fdfdf;
195
195
-
--link-hover: #7fffff;
196
196
-
--terminal-bg: #0a0a0a;
197
197
-
--terminal-text: #e0e0e0;
198
198
-
}
199
199
-
}
208
208
+
body {
209
209
+
font-family: "Fira Mono", monospace;
210
210
+
font-weight: 400;
211
211
+
background: var(--bg);
212
212
+
color: var(--text);
213
213
+
min-height: 100vh;
214
214
+
display: flex;
215
215
+
flex-direction: column;
216
216
+
padding-top: 6rem;
217
217
+
}
200
218
201
201
-
body {
202
202
-
font-family: "Fira Mono", monospace;
203
203
-
font-weight: 400;
204
204
-
font-style: normal;
205
205
-
font-size: 18px;
206
206
-
line-height: 1.6;
207
207
-
padding: 60px 40px;
208
208
-
max-width: 80%;
209
209
-
color: var(--text);
210
210
-
background: var(--bg);
211
211
-
transition:
212
212
-
background 0.2s,
213
213
-
color 0.2s;
214
214
-
}
219
219
+
.container {
220
220
+
max-width: 800px;
221
221
+
margin: 0 auto;
222
222
+
padding: 0 2rem;
223
223
+
width: 100%;
224
224
+
}
225
225
+
226
226
+
main {
227
227
+
flex: 1;
228
228
+
display: flex;
229
229
+
align-items: center;
230
230
+
justify-content: center;
231
231
+
}
232
232
+
233
233
+
.hero {
234
234
+
text-align: center;
235
235
+
padding: 4rem 0;
236
236
+
}
215
237
238
238
+
h1 {
239
239
+
font-size: 5rem;
240
240
+
font-weight: 700;
241
241
+
margin-bottom: 4rem;
242
242
+
letter-spacing: -0.02em;
243
243
+
color: #4a4a4a;
244
244
+
text-shadow:
245
245
+
1px 1px 0 #fff,
246
246
+
-1px -1px 0 #2a2a2a,
247
247
+
2px 2px 3px rgba(0, 0, 0, 0.3);
248
248
+
}
249
249
+
250
250
+
@media (prefers-color-scheme: dark) {
216
251
h1 {
217
217
-
font-size: 1.1em;
218
218
-
font-weight: normal;
219
219
-
margin-bottom: 2em;
252
252
+
color: #888;
253
253
+
text-shadow:
254
254
+
1px 1px 0 #222,
255
255
+
-1px -1px 0 #000,
256
256
+
2px 2px 3px rgba(0, 0, 0, 0.5);
220
257
}
258
258
+
}
221
259
222
222
-
.cursor {
223
223
-
display: inline-block;
224
224
-
width: 2px;
225
225
-
height: 1.1em;
226
226
-
background: var(--text);
227
227
-
margin-left: 2px;
228
228
-
vertical-align: text-bottom;
229
229
-
animation: blink 1s step-end infinite;
230
230
-
}
260
260
+
h1::after {
261
261
+
content: '_';
262
262
+
animation: blink 1s infinite;
263
263
+
}
231
264
232
232
-
@keyframes blink {
233
233
-
0%,
234
234
-
100% {
235
235
-
opacity: 1;
236
236
-
}
237
237
-
50% {
238
238
-
opacity: 0;
239
239
-
}
240
240
-
}
265
265
+
@keyframes blink {
266
266
+
0%, 50% { opacity: 1; }
267
267
+
51%, 100% { opacity: 0; }
268
268
+
}
241
269
242
242
-
p {
243
243
-
margin-bottom: 0.3em;
244
244
-
}
270
270
+
.cta {
271
271
+
display: inline-block;
272
272
+
background: var(--cta-bg);
273
273
+
color: var(--cta-text);
274
274
+
padding: 2rem 4rem;
275
275
+
font-size: 1.5rem;
276
276
+
text-decoration: none;
277
277
+
border: 3px solid var(--cta-bg);
278
278
+
transition: all 0.1s;
279
279
+
font-weight: 700;
280
280
+
margin-bottom: 3rem;
281
281
+
}
245
282
246
246
-
section {
247
247
-
margin-bottom: 2.5em;
248
248
-
}
283
283
+
.cta:hover {
284
284
+
background: var(--cta-hover-bg);
285
285
+
color: var(--cta-hover-text);
286
286
+
border-color: var(--cta-bg);
287
287
+
}
249
288
250
250
-
a {
251
251
-
color: var(--link);
252
252
-
text-decoration: underline;
253
253
-
text-underline-offset: 2px;
254
254
-
}
289
289
+
.tagline {
290
290
+
font-size: 1.2rem;
291
291
+
color: var(--text-muted);
292
292
+
margin-bottom: 6rem;
293
293
+
}
255
294
256
256
-
a:hover {
257
257
-
color: var(--link-hover);
258
258
-
}
295
295
+
.secondary {
296
296
+
border-top: 1px solid var(--border);
297
297
+
padding-top: 3rem;
298
298
+
margin-top: 4rem;
299
299
+
}
259
300
260
260
-
.click-hint {
261
261
-
color: var(--link);
262
262
-
margin-left: 0.5em;
263
263
-
display: inline-flex;
264
264
-
align-items: center;
265
265
-
}
301
301
+
.secondary h2 {
302
302
+
font-size: 1rem;
303
303
+
margin-bottom: 1.5rem;
304
304
+
font-weight: 700;
305
305
+
text-transform: lowercase;
306
306
+
}
266
307
267
267
-
.click-hint .arrow {
268
268
-
display: inline-block;
269
269
-
width: 1.2em;
270
270
-
text-align: center;
271
271
-
animation: nudge 1.2s ease-in-out infinite;
272
272
-
}
308
308
+
.code-block {
309
309
+
background: var(--code-bg);
310
310
+
color: var(--code-text);
311
311
+
padding: 1.5rem;
312
312
+
margin: 1rem 0;
313
313
+
font-size: 0.9rem;
314
314
+
overflow-x: auto;
315
315
+
}
273
316
274
274
-
@keyframes nudge {
275
275
-
0%,
276
276
-
100% {
277
277
-
transform: translateX(0);
278
278
-
}
279
279
-
50% {
280
280
-
transform: translateX(-4px);
281
281
-
}
282
282
-
}
317
317
+
.code-block code {
318
318
+
font-family: "Fira Mono", monospace;
319
319
+
}
283
320
284
284
-
.terminal-section {
285
285
-
margin-top: 2em;
286
286
-
}
321
321
+
.secondary p {
322
322
+
color: var(--text-muted);
323
323
+
margin-bottom: 1rem;
324
324
+
font-size: 0.95rem;
325
325
+
}
287
326
288
288
-
.terminal-label {
289
289
-
margin-bottom: 0.8em;
290
290
-
}
327
327
+
.secondary a {
328
328
+
color: var(--link);
329
329
+
text-decoration: none;
330
330
+
border-bottom: 1px solid var(--link);
331
331
+
}
291
332
292
292
-
.cmd {
293
293
-
font-family:
294
294
-
ui-monospace, "SF Mono", "Cascadia Code", "Source Code Pro",
295
295
-
Menlo, Consolas, monospace;
296
296
-
font-size: 0.85em;
297
297
-
background: var(--terminal-bg);
298
298
-
color: var(--terminal-text);
299
299
-
border-radius: 4px;
300
300
-
padding: 12px 16px;
301
301
-
display: table;
302
302
-
white-space: nowrap;
303
303
-
margin-bottom: 0.5em;
304
304
-
}
333
333
+
.secondary a:hover {
334
334
+
border-bottom: 2px solid var(--link);
335
335
+
}
305
336
306
306
-
.cmd .highlight {
307
307
-
color: var(--terminal-cyan);
337
337
+
footer {
338
338
+
border-top: 1px solid var(--border);
339
339
+
padding: 3rem 0;
340
340
+
text-align: center;
341
341
+
margin-top: 6rem;
342
342
+
}
343
343
+
344
344
+
.quote {
345
345
+
font-size: 0.85rem;
346
346
+
color: var(--text-subtle);
347
347
+
font-style: italic;
348
348
+
}
349
349
+
350
350
+
.links {
351
351
+
margin-top: 2rem;
352
352
+
font-size: 0.85rem;
353
353
+
}
354
354
+
355
355
+
.links a {
356
356
+
color: var(--text-muted);
357
357
+
text-decoration: none;
358
358
+
margin: 0 1rem;
359
359
+
}
360
360
+
361
361
+
.links a:hover {
362
362
+
color: var(--text);
363
363
+
}
364
364
+
365
365
+
@media (max-width: 768px) {
366
366
+
h1 {
367
367
+
font-size: 3rem;
308
368
}
309
369
310
310
-
.hosting-options {
311
311
-
margin-top: 2.5em;
370
370
+
.cta {
371
371
+
padding: 1.5rem 3rem;
372
372
+
font-size: 1.2rem;
312
373
}
313
374
314
314
-
.hosting-options p {
315
315
-
margin-bottom: 0.2em;
375
375
+
.tagline {
376
376
+
font-size: 1rem;
316
377
}
317
317
-
</style>
318
318
-
</head>
319
319
-
<body>
320
320
-
<h1>wisp.place<span class="cursor"></span></h1>
378
378
+
}
379
379
+
</style>
380
380
+
</head>
381
381
+
<body>
382
382
+
<main>
383
383
+
<div class="container">
384
384
+
<div class="hero">
385
385
+
<h1>wisp.place</h1>
321
386
322
322
-
<section>
323
323
-
<p>the easiest way to get static html going</p>
324
324
-
<p>
325
325
-
just drag n' drop into the dashboard with your
326
326
-
<a href="${atprotoLoginUrl}">AT Protocol account</a>.
327
327
-
<span class="click-hint"
328
328
-
><span class="arrow">←</span> click me!</span
329
329
-
>
330
330
-
</p>
331
331
-
</section>
387
387
+
<a href="${atprotoLoginUrl}" class="cta">SIGN IN WITH AT PROTOCOL</a>
332
388
333
333
-
<section class="terminal-section">
334
334
-
<p class="terminal-label">are you a terminal nerd?</p>
335
335
-
<code class="cmd"
336
336
-
>curl
337
337
-
<span class="highlight"
338
338
-
>https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux</span
339
339
-
>
340
340
-
-o wisp-cli</code
341
341
-
>
342
342
-
<code class="cmd"
343
343
-
>wisp-cli
344
344
-
<span class="highlight">alice.bsky.social</span> --site
345
345
-
MyBlog</code
346
346
-
>
347
347
-
</section>
389
389
+
<p class="tagline">Drop files. They're live.</p>
348
390
349
349
-
<div class="hosting-options">
350
350
-
<p>host on our infrastructure for free</p>
351
351
-
<p>
352
352
-
or use wisp-cli to host on your own infra with seamless
353
353
-
deployments
354
354
-
</p>
355
355
-
<p>need docs? <a href="https://docs.wisp.place">docs.wisp.place</a></p>
391
391
+
<div class="secondary">
392
392
+
<h2>are you a terminal nerd?</h2>
393
393
+
<div class="code-block">
394
394
+
<code>curl https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-linux -o wisp-cli</code>
395
395
+
</div>
396
396
+
<div class="code-block">
397
397
+
<code>wisp-cli alice.bsky.social --site MyBlog</code>
398
398
+
</div>
399
399
+
<p>host on our infrastructure for free<br>
400
400
+
or use wisp-cli to host on your own infra with seamless deployments</p>
401
401
+
<p>need docs? <a href="https://docs.wisp.place">docs.wisp.place</a></p>
402
402
+
</div>
403
403
+
</div>
404
404
+
</div>
405
405
+
</main>
406
406
+
407
407
+
<footer>
408
408
+
<div class="container">
409
409
+
<p class="quote">"The easiest way to get static HTML going."</p>
410
410
+
<div class="links">
411
411
+
<a href="https://docs.wisp.place">docs</a>
412
412
+
</div>
356
413
</div>
357
357
-
</body>
414
414
+
</footer>
415
415
+
</body>
358
416
</html>`
359
417
})
360
418
.use(authRoutes(client, cookieSecret))