tangled
alpha
login
or
join now
aottr.dev
/
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
make css a package
nekomimi.pet
1 month ago
37467798
98529561
+1501
-615
13 changed files
expand all
collapse all
unified
split
apps
main-app
package.json
public
editor
acceptable-use.html
onboarding.html
landingpage.html
src
index.ts
bun.lock
packages
@wisp
css
README.md
index.html
index.ts
package.json
wisp.css
observability
src
middleware
elysia.ts
scripts
codegen.sh
+1
apps/main-app/package.json
···
32
32
"@radix-ui/react-tabs": "^1.1.13",
33
33
"@tanstack/react-query": "^5.90.2",
34
34
"@wisp/atproto-utils": "workspace:*",
35
35
+
"@wisp/css": "workspace:*",
35
36
"@wisp/constants": "workspace:*",
36
37
"@wisp/database": "workspace:*",
37
38
"@wisp/fs-utils": "workspace:*",
+5
-217
apps/main-app/public/editor/acceptable-use.html
···
8
8
<link rel="preconnect" href="https://fonts.googleapis.com">
9
9
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
10
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
11
11
+
<link rel="stylesheet" href="/wisp.css">
11
12
<style>
12
12
-
* { margin: 0; padding: 0; box-sizing: border-box; }
13
13
-
14
14
-
:root {
15
15
-
--bg: oklch(0.92 0.012 35);
16
16
-
--bg-alt: oklch(0.88 0.01 35);
17
17
-
--text: oklch(0.15 0.015 30);
18
18
-
--text-muted: oklch(0.35 0.02 30);
19
19
-
--text-subtle: oklch(0.50 0.02 30);
20
20
-
--border: oklch(0.30 0.025 35);
21
21
-
--border-light: oklch(0.65 0.02 30);
22
22
-
--accent: oklch(0.65 0.18 345);
23
23
-
--cta-bg: oklch(0.30 0.025 35);
24
24
-
--cta-text: oklch(0.96 0.008 35);
25
25
-
--code-bg: oklch(0.95 0.008 35);
26
26
-
--success: oklch(0.65 0.20 145);
27
27
-
--red: oklch(0.60 0.20 25);
28
28
-
--yellow: oklch(0.70 0.15 85);
29
29
-
--blue: oklch(0.60 0.15 240);
30
30
-
}
31
31
-
32
32
-
@media (prefers-color-scheme: dark) {
33
33
-
:root {
34
34
-
--bg: oklch(0.23 0.015 285);
35
35
-
--bg-alt: oklch(0.20 0.015 285);
36
36
-
--text: oklch(0.90 0.005 285);
37
37
-
--text-muted: oklch(0.72 0.01 285);
38
38
-
--text-subtle: oklch(0.55 0.01 285);
39
39
-
--border: oklch(0.90 0.005 285);
40
40
-
--border-light: oklch(0.38 0.02 285);
41
41
-
--accent: oklch(0.85 0.08 5);
42
42
-
--cta-bg: oklch(0.70 0.10 295);
43
43
-
--cta-text: oklch(0.23 0.015 285);
44
44
-
--code-bg: oklch(0.28 0.015 285);
45
45
-
--success: oklch(0.65 0.20 145);
46
46
-
--red: oklch(0.65 0.20 25);
47
47
-
--yellow: oklch(0.75 0.15 85);
48
48
-
--blue: oklch(0.65 0.18 240);
49
49
-
}
50
50
-
}
51
51
-
52
52
-
body {
53
53
-
font-family: "JetBrains Mono", monospace;
54
54
-
background: var(--bg);
55
55
-
color: var(--text);
56
56
-
line-height: 1.6;
57
57
-
min-height: 100vh;
58
58
-
display: flex;
59
59
-
flex-direction: column;
60
60
-
}
61
61
-
62
62
-
header {
63
63
-
border-bottom: 1px solid var(--border-light);
64
64
-
padding: 1rem 2rem;
65
65
-
background: var(--bg);
66
66
-
position: sticky;
67
67
-
top: 0;
68
68
-
z-index: 100;
69
69
-
}
70
70
-
71
71
-
.header-inner {
72
72
-
max-width: 1100px;
73
73
-
margin: 0 auto;
74
74
-
display: flex;
75
75
-
justify-content: space-between;
76
76
-
align-items: center;
77
77
-
}
78
78
-
79
79
-
.logo {
80
80
-
font-size: 1.125rem;
81
81
-
font-weight: 700;
82
82
-
color: var(--text);
83
83
-
letter-spacing: -0.02em;
84
84
-
display: flex;
85
85
-
align-items: center;
86
86
-
gap: 0.5rem;
87
87
-
}
88
88
-
89
89
-
.logo img { width: 2rem; height: 2rem; }
90
90
-
91
91
-
.back-link {
92
92
-
color: var(--text-muted);
93
93
-
text-decoration: none;
94
94
-
font-size: 0.875rem;
95
95
-
}
96
96
-
97
97
-
.back-link:hover { color: var(--text); }
98
98
-
99
13
.hero {
100
14
background: linear-gradient(to bottom, var(--code-bg), var(--bg));
101
15
border-bottom: 1px solid var(--border-light);
···
116
30
margin-bottom: 1.5rem;
117
31
}
118
32
119
119
-
h1 {
33
33
+
.hero h1 {
120
34
font-size: 2.5rem;
121
121
-
margin-bottom: 1.5rem;
122
122
-
color: var(--text);
123
35
}
124
36
125
37
.meta {
···
135
47
width: 1px;
136
48
height: 1rem;
137
49
background: var(--border-light);
138
138
-
}
139
139
-
140
140
-
.container {
141
141
-
max-width: 900px;
142
142
-
margin: 0 auto;
143
143
-
padding: 3rem 1rem;
144
50
}
145
51
146
52
article { margin-bottom: 3rem; }
147
147
-
148
53
section { margin-bottom: 3rem; }
54
54
+
.card { margin-bottom: 2rem; }
149
55
150
56
h2 {
151
151
-
font-size: 1.875rem;
152
152
-
margin-bottom: 1.5rem;
153
153
-
color: var(--text);
154
57
display: flex;
155
58
align-items: center;
156
59
gap: 0.75rem;
157
60
}
158
61
159
159
-
h3 {
160
160
-
font-size: 1.5rem;
161
161
-
margin-bottom: 1rem;
162
162
-
color: var(--text);
163
163
-
}
164
164
-
165
165
-
p {
166
166
-
color: var(--text-muted);
167
167
-
margin-bottom: 1rem;
168
168
-
font-size: 1rem;
169
169
-
line-height: 1.7;
170
170
-
}
171
171
-
172
172
-
strong { color: var(--text); font-weight: 600; }
173
173
-
174
174
-
.card {
175
175
-
background: var(--bg-alt);
176
176
-
border: 1px solid var(--border-light);
177
177
-
border-radius: 0.5rem;
178
178
-
padding: 1.5rem;
179
179
-
margin-bottom: 2rem;
180
180
-
}
181
181
-
182
182
-
.card.success {
183
183
-
background: color-mix(in oklch, var(--success) 5%, var(--bg-alt));
184
184
-
border-color: color-mix(in oklch, var(--success) 20%, transparent);
185
185
-
}
186
186
-
187
187
-
.card.danger {
188
188
-
background: color-mix(in oklch, var(--red) 5%, var(--bg-alt));
189
189
-
border-color: color-mix(in oklch, var(--red) 30%, transparent);
190
190
-
border-width: 2px;
191
191
-
}
192
192
-
193
193
-
.card.info {
194
194
-
background: color-mix(in oklch, var(--blue) 5%, var(--bg-alt));
195
195
-
border-color: color-mix(in oklch, var(--blue) 20%, transparent);
196
196
-
}
197
197
-
198
198
-
.card.thick { border-width: 2px; }
199
199
-
200
200
-
.card-flex {
201
201
-
display: flex;
202
202
-
gap: 1rem;
203
203
-
align-items: start;
204
204
-
}
205
205
-
206
206
-
.card-icon {
207
207
-
flex-shrink: 0;
208
208
-
width: 2rem;
209
209
-
height: 2rem;
210
210
-
}
211
211
-
212
212
-
ul {
213
213
-
list-style: none;
214
214
-
margin: 1rem 0;
215
215
-
}
216
216
-
217
62
li {
218
63
display: flex;
219
64
align-items: start;
···
222
67
color: var(--text-muted);
223
68
}
224
69
225
225
-
.bullet {
226
226
-
flex-shrink: 0;
227
227
-
margin-top: 0.25rem;
228
228
-
}
229
229
-
230
230
-
.bullet.red { color: var(--red); }
231
231
-
.bullet.green { color: var(--success); }
232
232
-
.bullet.yellow { color: var(--yellow); }
233
233
-
.bullet.accent { color: var(--accent); }
234
234
-
235
235
-
.alert {
236
236
-
background: var(--code-bg);
237
237
-
border-left: 4px solid var(--red);
238
238
-
padding: 1rem;
239
239
-
border-radius: 0.25rem;
240
240
-
margin: 1rem 0;
241
241
-
}
242
242
-
243
70
.alert p { margin: 0; }
244
71
245
245
-
ol {
246
246
-
list-style: none;
247
247
-
counter-reset: item;
248
248
-
margin: 1rem 0;
249
249
-
}
250
250
-
251
251
-
ol li {
252
252
-
counter-increment: item;
253
253
-
}
254
254
-
255
255
-
ol li::before {
256
256
-
content: counter(item) ".";
257
257
-
font-weight: 700;
258
258
-
color: var(--accent);
259
259
-
margin-right: 0.75rem;
260
260
-
}
261
261
-
262
262
-
a {
263
263
-
color: var(--accent);
264
264
-
text-decoration: none;
265
265
-
font-weight: 500;
266
266
-
}
267
267
-
268
268
-
a:hover { opacity: 0.8; }
269
269
-
270
270
-
footer {
271
271
-
border-top: 1px solid var(--border-light);
272
272
-
background: var(--bg-alt);
273
273
-
padding: 2rem 1rem;
274
274
-
margin-top: auto;
275
275
-
text-align: center;
276
276
-
font-size: 0.875rem;
277
277
-
color: var(--text-muted);
278
278
-
}
279
279
-
280
280
-
footer p { margin-bottom: 0.5rem; }
281
281
-
282
72
@media (max-width: 768px) {
283
283
-
h1 { font-size: 2rem; }
284
284
-
h2 { font-size: 1.5rem; }
285
285
-
h3 { font-size: 1.25rem; }
73
73
+
.hero h1 { font-size: 2rem; }
286
74
.meta { flex-direction: column; gap: 0.5rem; }
287
75
.meta-divider { display: none; }
288
76
}
···
538
326
</article>
539
327
</div>
540
328
541
541
-
<footer>
329
329
+
<footer class="simple">
542
330
<p>
543
331
Built by <a href="https://bsky.app/profile/nekomimi.pet" target="_blank">@nekomimi.pet</a> •
544
332
Contact: <a href="mailto:contact@wisp.place">contact@wisp.place</a> •
+5
-116
apps/main-app/public/editor/onboarding.html
···
8
8
<link rel="preconnect" href="https://fonts.googleapis.com">
9
9
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
10
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
11
11
+
<link rel="stylesheet" href="/wisp.css">
11
12
<style>
12
12
-
* { margin: 0; padding: 0; box-sizing: border-box; }
13
13
-
14
14
-
:root {
15
15
-
--bg: oklch(0.92 0.012 35);
16
16
-
--bg-alt: oklch(0.88 0.01 35);
17
17
-
--text: oklch(0.15 0.015 30);
18
18
-
--text-muted: oklch(0.35 0.02 30);
19
19
-
--text-subtle: oklch(0.50 0.02 30);
20
20
-
--border: oklch(0.30 0.025 35);
21
21
-
--border-light: oklch(0.65 0.02 30);
22
22
-
--accent: oklch(0.65 0.18 345);
23
23
-
--cta-bg: oklch(0.30 0.025 35);
24
24
-
--cta-text: oklch(0.96 0.008 35);
25
25
-
--code-bg: oklch(0.95 0.008 35);
26
26
-
--success: oklch(0.65 0.20 145);
27
27
-
}
28
28
-
29
29
-
@media (prefers-color-scheme: dark) {
30
30
-
:root {
31
31
-
--bg: oklch(0.23 0.015 285);
32
32
-
--bg-alt: oklch(0.20 0.015 285);
33
33
-
--text: oklch(0.90 0.005 285);
34
34
-
--text-muted: oklch(0.72 0.01 285);
35
35
-
--text-subtle: oklch(0.55 0.01 285);
36
36
-
--border: oklch(0.90 0.005 285);
37
37
-
--border-light: oklch(0.38 0.02 285);
38
38
-
--accent: oklch(0.85 0.08 5);
39
39
-
--cta-bg: oklch(0.70 0.10 295);
40
40
-
--cta-text: oklch(0.23 0.015 285);
41
41
-
--code-bg: oklch(0.28 0.015 285);
42
42
-
}
43
43
-
}
44
44
-
45
45
-
body {
46
46
-
font-family: "JetBrains Mono", monospace;
47
47
-
background: var(--bg);
48
48
-
color: var(--text);
49
49
-
line-height: 1.6;
50
50
-
}
51
51
-
.container { max-width: 600px; margin: 0 auto; padding: 3rem 1rem; }
52
52
-
header {
53
53
-
border-bottom: 1px solid var(--border-light);
54
54
-
padding: 1rem 2rem;
55
55
-
background: var(--bg);
56
56
-
position: sticky;
57
57
-
top: 0;
58
58
-
z-index: 100;
59
59
-
}
60
60
-
.logo { font-size: 1.125rem; font-weight: 700; color: var(--text); letter-spacing: -0.02em; }
13
13
+
.container { max-width: 600px; }
61
14
.progress { display: flex; justify-content: center; align-items: center; gap: 1rem; margin-bottom: 2rem; }
62
15
.step-indicator {
63
16
width: 2rem; height: 2rem; border-radius: 50%;
···
68
21
.step-indicator.active { background: var(--cta-bg); color: var(--cta-text); }
69
22
.step-indicator.complete { background: var(--success); color: var(--bg); }
70
23
.progress-line { width: 4rem; height: 2px; background: var(--border-light); }
71
71
-
.card {
72
72
-
background: var(--bg-alt);
73
73
-
border: 1px solid var(--border-light);
74
74
-
border-radius: 0.5rem;
75
75
-
padding: 1.5rem;
76
76
-
}
77
77
-
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; text-align: center; color: var(--text); }
78
78
-
h2 { font-size: 1.25rem; margin-bottom: 1rem; color: var(--text); }
79
79
-
p { color: var(--text-muted); margin-bottom: 1rem; }
80
80
-
.text-center { text-align: center; }
81
81
-
.text-muted { color: var(--text-muted); font-size: 0.875rem; }
82
82
-
label { display: block; margin-bottom: 0.5rem; font-size: 0.875rem; font-weight: 500; color: var(--text); }
83
83
-
input {
84
84
-
width: 100%;
85
85
-
padding: 0.5rem 0.75rem;
86
86
-
background: var(--bg);
87
87
-
border: 1px solid var(--border-light);
88
88
-
border-radius: 0.375rem;
89
89
-
color: var(--text);
90
90
-
font-size: 0.875rem;
91
91
-
font-family: "JetBrains Mono", monospace;
92
92
-
}
93
93
-
input:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
94
94
-
button {
95
95
-
width: 100%;
96
96
-
padding: 0.5rem 1rem;
97
97
-
background: var(--cta-bg);
98
98
-
color: var(--cta-text);
99
99
-
border: none;
100
100
-
border-radius: 0.375rem;
101
101
-
font-weight: 600;
102
102
-
cursor: pointer;
103
103
-
font-size: 0.875rem;
104
104
-
font-family: "JetBrains Mono", monospace;
105
105
-
}
106
106
-
button:hover { opacity: 0.9; }
107
107
-
button:disabled { opacity: 0.5; cursor: not-allowed; }
108
108
-
button.outline {
109
109
-
background: transparent;
110
110
-
color: var(--text);
111
111
-
border: 1px solid var(--border-light);
112
112
-
}
24
24
+
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; text-align: center; }
25
25
+
h2 { font-size: 1.25rem; margin-bottom: 1rem; }
26
26
+
button { width: 100%; }
113
27
.input-wrapper { position: relative; margin-bottom: 1rem; }
114
28
.input-icon {
115
29
position: absolute;
···
117
31
top: 50%;
118
32
transform: translateY(-50%);
119
33
}
120
120
-
.spinner {
121
121
-
display: inline-block;
122
122
-
width: 1rem; height: 1rem;
123
123
-
border: 2px solid var(--border-light);
124
124
-
border-top-color: var(--accent);
125
125
-
border-radius: 50%;
126
126
-
animation: spin 0.6s linear infinite;
127
127
-
}
128
128
-
@keyframes spin { to { transform: rotate(360deg); } }
129
129
-
.success { color: var(--success); }
130
130
-
.error { color: var(--accent); }
131
131
-
.hidden { display: none; }
132
132
-
.mb-1 { margin-bottom: 0.5rem; }
133
133
-
.mb-2 { margin-bottom: 1rem; }
134
134
-
.mb-4 { margin-bottom: 1.5rem; }
135
135
-
.mt-4 { margin-top: 1.5rem; }
136
136
-
.flex { display: flex; }
137
137
-
.gap-2 { gap: 0.5rem; }
138
34
.upload-zone {
139
35
border: 2px dashed var(--border-light);
140
36
border-radius: 0.5rem;
···
144
40
transition: border-color 0.2s;
145
41
}
146
42
.upload-zone:hover { border-color: var(--accent); }
147
147
-
.alert {
148
148
-
padding: 1rem;
149
149
-
border-radius: 0.5rem;
150
150
-
margin-bottom: 1rem;
151
151
-
}
152
152
-
.alert-success { background: var(--code-bg); border: 1px solid var(--border-light); color: var(--success); }
153
153
-
.alert-info { background: var(--code-bg); color: var(--text-muted); }
154
43
</style>
155
44
</head>
156
45
<body>
+2
-267
apps/main-app/public/landingpage.html
···
31
31
<link rel="preconnect" href="https://fonts.googleapis.com">
32
32
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
33
33
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
34
34
+
<link rel="stylesheet" href="/wisp.css">
34
35
35
36
<style>
36
36
-
* {
37
37
-
margin: 0;
38
38
-
padding: 0;
39
39
-
box-sizing: border-box;
40
40
-
}
41
41
-
42
42
-
:root {
43
43
-
--bg: oklch(0.92 0.012 35);
44
44
-
--bg-alt: oklch(0.88 0.01 35);
45
45
-
--text: oklch(0.15 0.015 30);
46
46
-
--text-muted: oklch(0.35 0.02 30);
47
47
-
--text-subtle: oklch(0.50 0.02 30);
48
48
-
--border: oklch(0.30 0.025 35);
49
49
-
--border-light: oklch(0.65 0.02 30);
50
50
-
--accent: oklch(0.65 0.18 345);
51
51
-
--accent-muted: oklch(0.75 0.14 345);
52
52
-
--cta-bg: oklch(0.30 0.025 35);
53
53
-
--cta-text: oklch(0.96 0.008 35);
54
54
-
--code-bg: oklch(0.95 0.008 35);
55
55
-
--success: oklch(0.65 0.20 145);
56
56
-
}
57
57
-
58
58
-
@media (prefers-color-scheme: dark) {
59
59
-
:root {
60
60
-
--bg: oklch(0.23 0.015 285);
61
61
-
--bg-alt: oklch(0.20 0.015 285);
62
62
-
--text: oklch(0.90 0.005 285);
63
63
-
--text-muted: oklch(0.72 0.01 285);
64
64
-
--text-subtle: oklch(0.55 0.01 285);
65
65
-
--border: oklch(0.90 0.005 285);
66
66
-
--border-light: oklch(0.38 0.02 285);
67
67
-
--accent: oklch(0.85 0.08 5);
68
68
-
--accent-muted: oklch(0.70 0.10 295);
69
69
-
--cta-bg: oklch(0.70 0.10 295);
70
70
-
--cta-text: oklch(0.23 0.015 285);
71
71
-
--code-bg: oklch(0.28 0.015 285);
72
72
-
}
73
73
-
}
74
74
-
75
75
-
html {
76
76
-
scroll-behavior: smooth;
77
77
-
}
78
78
-
79
79
-
body {
80
80
-
font-family: "JetBrains Mono", monospace;
81
81
-
background: var(--bg);
82
82
-
color: var(--text);
83
83
-
min-height: 100vh;
84
84
-
display: flex;
85
85
-
flex-direction: column;
86
86
-
line-height: 1.6;
87
87
-
}
88
88
-
89
89
-
/* Header */
90
90
-
header {
91
91
-
position: sticky;
92
92
-
top: 0;
93
93
-
background: var(--bg);
94
94
-
border-bottom: 1px solid var(--border-light);
95
95
-
padding: 1rem 2rem;
96
96
-
z-index: 100;
97
97
-
}
98
98
-
99
99
-
.header-inner {
100
100
-
max-width: 1100px;
101
101
-
margin: 0 auto;
102
102
-
display: flex;
103
103
-
justify-content: space-between;
104
104
-
align-items: center;
105
105
-
}
106
106
-
107
107
-
.logo {
108
108
-
font-weight: 700;
109
109
-
font-size: 1.125rem;
110
110
-
text-decoration: none;
111
111
-
color: var(--text);
112
112
-
letter-spacing: -0.02em;
113
113
-
}
114
114
-
115
115
-
nav {
116
116
-
display: flex;
117
117
-
gap: 2rem;
118
118
-
align-items: center;
119
119
-
}
120
120
-
121
121
-
nav a {
122
122
-
color: var(--text-muted);
123
123
-
text-decoration: none;
124
124
-
font-size: 1rem;
125
125
-
transition: color 0.15s;
126
126
-
}
127
127
-
128
128
-
nav a:hover {
129
129
-
color: var(--text);
130
130
-
}
131
131
-
132
132
-
.nav-cta {
133
133
-
background: var(--cta-bg);
134
134
-
color: var(--cta-text);
135
135
-
padding: 0.5rem 1rem;
136
136
-
border: 1px solid var(--border);
137
137
-
font-weight: 600;
138
138
-
font-size: 0.875rem;
139
139
-
text-decoration: none;
140
140
-
text-transform: uppercase;
141
141
-
letter-spacing: 0.05em;
142
142
-
transition: all 0.15s;
143
143
-
}
144
144
-
145
145
-
.nav-cta:hover {
146
146
-
background: var(--bg);
147
147
-
color: var(--text);
148
148
-
}
149
149
-
150
37
/* Hero */
151
38
.hero {
152
39
padding: 8rem 2rem 6rem;
···
170
57
border: 1px solid var(--accent);
171
58
}
172
59
173
173
-
h1 {
60
60
+
.hero h1 {
174
61
font-size: clamp(2.75rem, 7vw, 4.25rem);
175
175
-
font-weight: 700;
176
176
-
line-height: 1.1;
177
177
-
margin-bottom: 1.5rem;
178
178
-
letter-spacing: -0.03em;
179
62
text-align: center;
180
63
}
181
64
···
201
84
justify-content: center;
202
85
}
203
86
204
204
-
.cta-primary {
205
205
-
background: var(--cta-bg);
206
206
-
color: var(--cta-text);
207
207
-
padding: 1rem 2rem;
208
208
-
border: 2px solid var(--border);
209
209
-
font-weight: 600;
210
210
-
font-size: 1rem;
211
211
-
text-decoration: none;
212
212
-
text-transform: uppercase;
213
213
-
letter-spacing: 0.05em;
214
214
-
transition: all 0.15s;
215
215
-
}
216
216
-
217
217
-
.cta-primary:hover {
218
218
-
background: var(--bg);
219
219
-
color: var(--text);
220
220
-
}
221
221
-
222
222
-
.cta-secondary {
223
223
-
background: transparent;
224
224
-
color: var(--text);
225
225
-
padding: 1rem 2rem;
226
226
-
border: 2px solid var(--border);
227
227
-
font-weight: 600;
228
228
-
font-size: 1rem;
229
229
-
text-decoration: none;
230
230
-
text-transform: uppercase;
231
231
-
letter-spacing: 0.05em;
232
232
-
transition: all 0.15s;
233
233
-
}
234
234
-
235
235
-
.cta-secondary:hover {
236
236
-
background: var(--cta-bg);
237
237
-
color: var(--cta-text);
238
238
-
}
239
239
-
240
87
.hero-cmd {
241
88
color: var(--text-subtle);
242
89
font-size: 1rem;
···
533
380
margin-bottom: 2rem;
534
381
}
535
382
536
536
-
/* Footer */
537
537
-
footer {
538
538
-
padding: 4rem 2rem 2rem;
539
539
-
border-top: 1px solid var(--border-light);
540
540
-
margin-top: auto;
541
541
-
}
542
542
-
543
543
-
.footer-inner {
544
544
-
max-width: 1100px;
545
545
-
margin: 0 auto;
546
546
-
display: grid;
547
547
-
grid-template-columns: 2fr repeat(3, 1fr);
548
548
-
gap: 3rem;
549
549
-
}
550
550
-
551
551
-
.footer-brand p:first-child {
552
552
-
font-weight: 700;
553
553
-
margin-bottom: 0.75rem;
554
554
-
}
555
555
-
556
556
-
.footer-brand p:last-child {
557
557
-
color: var(--text-muted);
558
558
-
font-size: 1rem;
559
559
-
line-height: 1.6;
560
560
-
}
561
561
-
562
562
-
.footer-col h4 {
563
563
-
font-size: 0.875rem;
564
564
-
font-weight: 600;
565
565
-
color: var(--text-subtle);
566
566
-
text-transform: uppercase;
567
567
-
letter-spacing: 0.05em;
568
568
-
margin-bottom: 1rem;
569
569
-
}
570
570
-
571
571
-
.footer-col ul {
572
572
-
list-style: none;
573
573
-
}
574
574
-
575
575
-
.footer-col li {
576
576
-
margin-bottom: 0.5rem;
577
577
-
}
578
578
-
579
579
-
.footer-col a {
580
580
-
color: var(--text-muted);
581
581
-
text-decoration: none;
582
582
-
font-size: 1rem;
583
583
-
transition: color 0.15s;
584
584
-
}
585
585
-
586
586
-
.footer-col a:hover {
587
587
-
color: var(--text);
588
588
-
}
589
589
-
590
590
-
.footer-bottom {
591
591
-
max-width: 1100px;
592
592
-
margin: 3rem auto 0;
593
593
-
padding-top: 2rem;
594
594
-
border-top: 1px solid var(--border-light);
595
595
-
display: flex;
596
596
-
justify-content: space-between;
597
597
-
align-items: center;
598
598
-
}
599
599
-
600
600
-
.footer-bottom p {
601
601
-
color: var(--text-subtle);
602
602
-
font-size: 0.875rem;
603
603
-
}
604
604
-
605
605
-
.footer-bottom nav {
606
606
-
gap: 1.5rem;
607
607
-
}
608
608
-
609
609
-
.footer-bottom a {
610
610
-
color: var(--text-subtle);
611
611
-
text-decoration: none;
612
612
-
font-size: 0.875rem;
613
613
-
transition: color 0.15s;
614
614
-
}
615
615
-
616
616
-
.footer-bottom a:hover {
617
617
-
color: var(--text);
618
618
-
}
619
619
-
620
383
/* Responsive */
621
384
@media (max-width: 768px) {
622
385
.hero {
623
386
padding: 5rem 1.5rem 4rem;
624
387
}
625
388
626
626
-
nav a:not(.nav-cta) {
627
627
-
display: none;
628
628
-
}
629
629
-
630
389
.steps {
631
390
grid-template-columns: 1fr;
632
391
gap: 2rem;
···
640
399
grid-template-columns: 1fr;
641
400
}
642
401
643
643
-
.footer-inner {
644
644
-
grid-template-columns: 1fr 1fr;
645
645
-
}
646
646
-
647
647
-
.footer-brand {
648
648
-
grid-column: span 2;
649
649
-
}
650
650
-
651
651
-
.footer-bottom {
652
652
-
flex-direction: column;
653
653
-
gap: 1rem;
654
654
-
text-align: center;
655
655
-
}
656
656
-
657
402
.cta-buttons {
658
403
flex-direction: column;
659
404
width: 100%;
···
662
407
.cta-primary, .cta-secondary {
663
408
width: 100%;
664
409
text-align: center;
665
665
-
}
666
666
-
}
667
667
-
668
668
-
@media (max-width: 480px) {
669
669
-
.footer-inner {
670
670
-
grid-template-columns: 1fr;
671
671
-
}
672
672
-
673
673
-
.footer-brand {
674
674
-
grid-column: span 1;
675
410
}
676
411
}
677
412
</style>
+6
apps/main-app/src/index.ts
···
25
25
import { DNSVerificationWorker } from './lib/dns-verification-worker'
26
26
import { createLogger, logCollector, initializeGrafanaExporters } from '@wisp/observability'
27
27
import { observabilityMiddleware } from '@wisp/observability/middleware/elysia'
28
28
+
import { css as wispCss } from '@wisp/css'
28
29
import { promptAdminSetup } from './lib/admin-auth'
29
30
import { adminRoutes } from './routes/admin'
30
31
···
225
226
})
226
227
: (app) => app
227
228
)
229
229
+
.get('/wisp.css', ({ set }) => {
230
230
+
set.headers['Content-Type'] = 'text/css; charset=utf-8'
231
231
+
set.headers['Cache-Control'] = 'public, max-age=86400'
232
232
+
return wispCss
233
233
+
})
228
234
.get('/acceptable-use', async ({ set }) => {
229
235
set.headers['Content-Type'] = 'text/html; charset=utf-8'
230
236
return await Bun.file('./apps/main-app/public/editor/acceptable-use.html').text()
+8
-1
bun.lock
···
71
71
"@tanstack/react-query": "^5.90.2",
72
72
"@wisp/atproto-utils": "workspace:*",
73
73
"@wisp/constants": "workspace:*",
74
74
+
"@wisp/css": "workspace:*",
74
75
"@wisp/database": "workspace:*",
75
76
"@wisp/fs-utils": "workspace:*",
76
77
"@wisp/lexicons": "workspace:*",
···
154
155
},
155
156
"packages/@wisp/constants": {
156
157
"name": "@wisp/constants",
158
158
+
"version": "1.0.0",
159
159
+
},
160
160
+
"packages/@wisp/css": {
161
161
+
"name": "@wisp/css",
157
162
"version": "1.0.0",
158
163
},
159
164
"packages/@wisp/database": {
···
837
842
838
843
"@wisp/constants": ["@wisp/constants@workspace:packages/@wisp/constants"],
839
844
845
845
+
"@wisp/css": ["@wisp/css@workspace:packages/@wisp/css"],
846
846
+
840
847
"@wisp/database": ["@wisp/database@workspace:packages/@wisp/database"],
841
848
842
849
"@wisp/fs-utils": ["@wisp/fs-utils@workspace:packages/@wisp/fs-utils"],
···
889
896
890
897
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="],
891
898
892
892
-
"bun-types": ["bun-types@1.3.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-qyschsA03Qz+gou+apt6HNl6HnI+sJJLL4wLDke4iugsE6584CMupOtTY1n+2YC9nGVrEKUlTs99jjRLKgWnjQ=="],
899
899
+
"bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
893
900
894
901
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
895
902
+143
packages/@wisp/css/README.md
···
1
1
+
# wisp.css
2
2
+
3
3
+
A minimal, monospace-first CSS stylesheet with automatic light/dark mode. See it live on https://wisp.place or https://sites.wisp.place/wisp.place/wisp.css for a demo.
4
4
+
5
5
+
## Quick Start
6
6
+
7
7
+
### CDN
8
8
+
```html
9
9
+
<link rel="stylesheet" href="https://sites.wisp.place/wisp.place/wisp.css/wisp.css">
10
10
+
```
11
11
+
12
12
+
### Self-hosted
13
13
+
14
14
+
Just copy `wisp.css` to your project and link to it:
15
15
+
16
16
+
```html
17
17
+
<link rel="stylesheet" href="/wisp.css">
18
18
+
```
19
19
+
20
20
+
## Features
21
21
+
22
22
+
- **Automatic dark mode** via `prefers-color-scheme`
23
23
+
- **CSS custom properties** for easy theming
24
24
+
- **JetBrains Mono** typography
25
25
+
- **Components**: cards, buttons, alerts, forms
26
26
+
- **Utility classes**: spacing, flexbox, text styling
27
27
+
- **Responsive** with mobile-first breakpoints
28
28
+
29
29
+
## CSS Variables
30
30
+
31
31
+
All variables use the `--wisp-` prefix. Unprefixed aliases are also available for convenience.
32
32
+
33
33
+
### Colors
34
34
+
35
35
+
```css
36
36
+
--wisp-bg /* Main background */
37
37
+
--wisp-bg-alt /* Alternate background (cards, etc) */
38
38
+
--wisp-text /* Primary text */
39
39
+
--wisp-text-muted /* Secondary text */
40
40
+
--wisp-text-subtle /* Tertiary text */
41
41
+
--wisp-border /* Strong borders */
42
42
+
--wisp-border-light /* Light borders */
43
43
+
--wisp-accent /* Accent/link color */
44
44
+
--wisp-cta-bg /* Button background */
45
45
+
--wisp-cta-text /* Button text */
46
46
+
--wisp-code-bg /* Code block background */
47
47
+
--wisp-success /* Success state */
48
48
+
--wisp-danger /* Error/danger state */
49
49
+
--wisp-warning /* Warning state */
50
50
+
--wisp-info /* Info state */
51
51
+
```
52
52
+
53
53
+
### Customizing
54
54
+
55
55
+
Override variables in your own CSS:
56
56
+
57
57
+
```css
58
58
+
:root {
59
59
+
--wisp-accent: oklch(0.65 0.20 200); /* Blue accent */
60
60
+
}
61
61
+
```
62
62
+
63
63
+
## Components
64
64
+
65
65
+
### Cards
66
66
+
67
67
+
```html
68
68
+
<div class="card">Basic card</div>
69
69
+
<div class="card success">Success card</div>
70
70
+
<div class="card danger">Danger card</div>
71
71
+
<div class="card info">Info card</div>
72
72
+
```
73
73
+
74
74
+
### Buttons
75
75
+
76
76
+
```html
77
77
+
<button>Primary button</button>
78
78
+
<button class="outline">Outline button</button>
79
79
+
<a class="cta-primary" href="#">CTA Primary</a>
80
80
+
<a class="cta-secondary" href="#">CTA Secondary</a>
81
81
+
```
82
82
+
83
83
+
### Alerts
84
84
+
85
85
+
```html
86
86
+
<div class="alert alert-success">Success message</div>
87
87
+
<div class="alert alert-danger">Error message</div>
88
88
+
<div class="alert alert-warning">Warning message</div>
89
89
+
<div class="alert alert-info">Info message</div>
90
90
+
```
91
91
+
92
92
+
### Forms
93
93
+
94
94
+
```html
95
95
+
<label for="email">Email</label>
96
96
+
<input type="email" id="email" placeholder="you@example.com">
97
97
+
```
98
98
+
99
99
+
## Utility Classes
100
100
+
101
101
+
### Spacing
102
102
+
103
103
+
- `.m-{0-4}` - margin
104
104
+
- `.mt-{0-6}`, `.mb-{0-6}`, `.ml-{0-4}`, `.mr-{0-4}` - directional margin
105
105
+
- `.p-{0-6}` - padding
106
106
+
- `.px-{1-4}`, `.py-{1-4}` - horizontal/vertical padding
107
107
+
- `.gap-{1-6}` - flex/grid gap
108
108
+
109
109
+
### Flexbox
110
110
+
111
111
+
- `.flex`, `.inline-flex`
112
112
+
- `.flex-col`, `.flex-wrap`
113
113
+
- `.items-start`, `.items-center`, `.items-end`
114
114
+
- `.justify-start`, `.justify-center`, `.justify-end`, `.justify-between`
115
115
+
116
116
+
### Text
117
117
+
118
118
+
- `.text-left`, `.text-center`, `.text-right`
119
119
+
- `.text-sm`, `.text-base`, `.text-lg`, `.text-xl`
120
120
+
- `.font-normal`, `.font-medium`, `.font-semibold`, `.font-bold`
121
121
+
- `.text-muted`, `.text-subtle`, `.text-accent`, `.text-success`, `.text-danger`
122
122
+
123
123
+
### Display
124
124
+
125
125
+
- `.hidden`, `.block`, `.inline`, `.inline-block`, `.flex`, `.grid`
126
126
+
- `.hide-mobile` - hidden on screens < 768px
127
127
+
- `.hide-desktop` - hidden on screens >= 769px
128
128
+
129
129
+
## Layout
130
130
+
131
131
+
```html
132
132
+
<div class="container">
133
133
+
<!-- max-width: 900px, centered with padding -->
134
134
+
</div>
135
135
+
136
136
+
<div class="container-wide">
137
137
+
<!-- max-width: 1100px, centered with padding -->
138
138
+
</div>
139
139
+
```
140
140
+
141
141
+
## License
142
142
+
143
143
+
MIT
+400
packages/@wisp/css/index.html
···
1
1
+
<!DOCTYPE html>
2
2
+
<html lang="en">
3
3
+
<head>
4
4
+
<meta charset="UTF-8">
5
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
+
<title>wisp.css - Example</title>
7
7
+
<link rel="preconnect" href="https://fonts.googleapis.com">
8
8
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
10
10
+
<link rel="stylesheet" href="./wisp.css">
11
11
+
</head>
12
12
+
<body>
13
13
+
<header>
14
14
+
<div class="header-inner">
15
15
+
<a href="#" class="logo">wisp.css</a>
16
16
+
<nav>
17
17
+
<a href="#typography">Typography</a>
18
18
+
<a href="#components">Components</a>
19
19
+
<a href="#utilities">Utilities</a>
20
20
+
<button id="theme-toggle" class="outline" style="padding: 0.4rem 0.75rem;">
21
21
+
<span id="theme-icon">◐</span> <span id="theme-label">System</span>
22
22
+
</button>
23
23
+
</nav>
24
24
+
</div>
25
25
+
</header>
26
26
+
27
27
+
<div class="container">
28
28
+
<!-- Hero Section -->
29
29
+
<section class="text-center mb-6">
30
30
+
<h1>wisp.css</h1>
31
31
+
<p class="text-lg">A minimal, monospace-first CSS stylesheet with automatic light/dark mode.</p>
32
32
+
<div class="flex justify-center gap-3 mt-4">
33
33
+
<a href="#" class="cta-primary">Get Started</a>
34
34
+
<a href="#" class="cta-secondary">View Source</a>
35
35
+
</div>
36
36
+
</section>
37
37
+
38
38
+
<!-- Typography -->
39
39
+
<section id="typography" class="mb-6">
40
40
+
<h2>Typography</h2>
41
41
+
42
42
+
<div class="card">
43
43
+
<h1>Heading 1</h1>
44
44
+
<h2>Heading 2</h2>
45
45
+
<h3>Heading 3</h3>
46
46
+
<h4>Heading 4</h4>
47
47
+
<p>This is a paragraph with <strong>bold text</strong> and <a href="#">a link</a>. The stylesheet uses JetBrains Mono for that clean monospace aesthetic.</p>
48
48
+
<p><small>This is small text for captions or metadata.</small></p>
49
49
+
<p>Here's some <code>inline code</code> in a sentence.</p>
50
50
+
<pre><code>// Code block
51
51
+
const greeting = "Hello, wisp.css!";
52
52
+
console.log(greeting);</code></pre>
53
53
+
</div>
54
54
+
</section>
55
55
+
56
56
+
<!-- Buttons -->
57
57
+
<section id="buttons" class="mb-6">
58
58
+
<h2>Buttons</h2>
59
59
+
60
60
+
<div class="card">
61
61
+
<h3>Standard Buttons</h3>
62
62
+
<div class="flex gap-3 flex-wrap mb-4">
63
63
+
<button>Primary</button>
64
64
+
<button class="outline">Outline</button>
65
65
+
<button disabled>Disabled</button>
66
66
+
</div>
67
67
+
68
68
+
<h3>CTA Buttons</h3>
69
69
+
<div class="flex gap-3 flex-wrap mb-4">
70
70
+
<a href="#" class="cta-primary">CTA Primary</a>
71
71
+
<a href="#" class="cta-secondary">CTA Secondary</a>
72
72
+
</div>
73
73
+
74
74
+
<h3>Button Sizes</h3>
75
75
+
<div class="flex gap-3 items-center flex-wrap">
76
76
+
<button>Default</button>
77
77
+
<button class="btn-lg">Large</button>
78
78
+
</div>
79
79
+
</div>
80
80
+
</section>
81
81
+
82
82
+
<!-- Cards -->
83
83
+
<section id="components" class="mb-6">
84
84
+
<h2>Cards</h2>
85
85
+
86
86
+
<div class="card">
87
87
+
<h3>Default Card</h3>
88
88
+
<p class="mb-0">This is a basic card component with some content inside.</p>
89
89
+
</div>
90
90
+
91
91
+
<div class="card success">
92
92
+
<h3>Success Card</h3>
93
93
+
<p class="mb-0">Use this for positive feedback or confirmations.</p>
94
94
+
</div>
95
95
+
96
96
+
<div class="card danger">
97
97
+
<h3>Danger Card</h3>
98
98
+
<p class="mb-0">Use this for errors or destructive actions.</p>
99
99
+
</div>
100
100
+
101
101
+
<div class="card warning">
102
102
+
<h3>Warning Card</h3>
103
103
+
<p class="mb-0">Use this for warnings or important notices.</p>
104
104
+
</div>
105
105
+
106
106
+
<div class="card info">
107
107
+
<h3>Info Card</h3>
108
108
+
<p class="mb-0">Use this for informational messages.</p>
109
109
+
</div>
110
110
+
111
111
+
<div class="card thick">
112
112
+
<div class="card-flex">
113
113
+
<div class="card-icon">💡</div>
114
114
+
<div>
115
115
+
<h3 class="mt-0">Card with Icon</h3>
116
116
+
<p class="mb-0">Use <code>card-flex</code> and <code>card-icon</code> for icon layouts.</p>
117
117
+
</div>
118
118
+
</div>
119
119
+
</div>
120
120
+
</section>
121
121
+
122
122
+
<!-- Alerts -->
123
123
+
<section id="alerts" class="mb-6">
124
124
+
<h2>Alerts</h2>
125
125
+
126
126
+
<div class="alert alert-success">
127
127
+
<strong>Success!</strong> Your changes have been saved.
128
128
+
</div>
129
129
+
130
130
+
<div class="alert alert-info">
131
131
+
<strong>Info:</strong> This is some helpful information.
132
132
+
</div>
133
133
+
134
134
+
<div class="alert alert-warning">
135
135
+
<strong>Warning:</strong> Please review before continuing.
136
136
+
</div>
137
137
+
138
138
+
<div class="alert alert-danger">
139
139
+
<strong>Error:</strong> Something went wrong. Please try again.
140
140
+
</div>
141
141
+
</section>
142
142
+
143
143
+
<!-- Forms -->
144
144
+
<section id="forms" class="mb-6">
145
145
+
<h2>Forms</h2>
146
146
+
147
147
+
<div class="card">
148
148
+
<div class="mb-4">
149
149
+
<label for="name">Name</label>
150
150
+
<input type="text" id="name" placeholder="Enter your name">
151
151
+
</div>
152
152
+
153
153
+
<div class="mb-4">
154
154
+
<label for="email">Email</label>
155
155
+
<input type="email" id="email" placeholder="you@example.com">
156
156
+
</div>
157
157
+
158
158
+
<div class="mb-4">
159
159
+
<label for="message">Message</label>
160
160
+
<textarea id="message" rows="4" placeholder="Write your message..."></textarea>
161
161
+
</div>
162
162
+
163
163
+
<div class="flex gap-3">
164
164
+
<button>Submit</button>
165
165
+
<button class="outline">Cancel</button>
166
166
+
</div>
167
167
+
</div>
168
168
+
</section>
169
169
+
170
170
+
<!-- Lists -->
171
171
+
<section id="lists" class="mb-6">
172
172
+
<h2>Lists</h2>
173
173
+
174
174
+
<div class="card">
175
175
+
<h3>Ordered List</h3>
176
176
+
<ol>
177
177
+
<li>First item with automatic numbering</li>
178
178
+
<li>Second item in the list</li>
179
179
+
<li>Third item completes the set</li>
180
180
+
</ol>
181
181
+
182
182
+
<h3>Unordered List with Bullets</h3>
183
183
+
<ul>
184
184
+
<li class="flex gap-2"><span class="bullet green">•</span> Success item</li>
185
185
+
<li class="flex gap-2"><span class="bullet red">•</span> Danger item</li>
186
186
+
<li class="flex gap-2"><span class="bullet yellow">•</span> Warning item</li>
187
187
+
<li class="flex gap-2"><span class="bullet accent">•</span> Accent item</li>
188
188
+
</ul>
189
189
+
</div>
190
190
+
</section>
191
191
+
192
192
+
<!-- Spinner -->
193
193
+
<section id="spinner" class="mb-6">
194
194
+
<h2>Spinners</h2>
195
195
+
196
196
+
<div class="card">
197
197
+
<div class="flex gap-4 items-center">
198
198
+
<span class="spinner"></span>
199
199
+
<span>Default spinner</span>
200
200
+
</div>
201
201
+
<div class="flex gap-4 items-center mt-4">
202
202
+
<span class="spinner spinner-lg"></span>
203
203
+
<span>Large spinner</span>
204
204
+
</div>
205
205
+
</div>
206
206
+
</section>
207
207
+
208
208
+
<!-- Utilities -->
209
209
+
<section id="utilities" class="mb-6">
210
210
+
<h2>Utility Classes</h2>
211
211
+
212
212
+
<div class="card">
213
213
+
<h3>Text Colors</h3>
214
214
+
<p class="text-muted mb-2">text-muted - Secondary text</p>
215
215
+
<p class="text-subtle mb-2">text-subtle - Tertiary text</p>
216
216
+
<p class="text-accent mb-2">text-accent - Accent color</p>
217
217
+
<p class="text-success mb-2">text-success - Success state</p>
218
218
+
<p class="text-danger mb-2">text-danger - Danger state</p>
219
219
+
<p class="text-warning mb-0">text-warning - Warning state</p>
220
220
+
</div>
221
221
+
222
222
+
<div class="card">
223
223
+
<h3>Text Alignment</h3>
224
224
+
<p class="text-left border p-2 mb-2">text-left</p>
225
225
+
<p class="text-center border p-2 mb-2">text-center</p>
226
226
+
<p class="text-right border p-2 mb-0">text-right</p>
227
227
+
</div>
228
228
+
229
229
+
<div class="card">
230
230
+
<h3>Flexbox</h3>
231
231
+
<div class="flex justify-between items-center border p-3 mb-3">
232
232
+
<span>justify-between</span>
233
233
+
<span>items-center</span>
234
234
+
</div>
235
235
+
<div class="flex flex-col gap-2 border p-3">
236
236
+
<span>flex-col</span>
237
237
+
<span>with gap-2</span>
238
238
+
</div>
239
239
+
</div>
240
240
+
241
241
+
<div class="card">
242
242
+
<h3>Spacing</h3>
243
243
+
<div class="bg-alt p-4 mb-2">p-4 (padding)</div>
244
244
+
<div class="bg-alt p-2 mb-4">p-2 with mb-4 (margin-bottom)</div>
245
245
+
<div class="flex gap-4">
246
246
+
<div class="bg-alt p-2">gap-4</div>
247
247
+
<div class="bg-alt p-2">between</div>
248
248
+
<div class="bg-alt p-2">items</div>
249
249
+
</div>
250
250
+
</div>
251
251
+
</section>
252
252
+
253
253
+
<!-- Color Palette -->
254
254
+
<section id="colors" class="mb-6">
255
255
+
<h2>Color Palette</h2>
256
256
+
<p>Colors automatically adapt to light/dark mode via CSS custom properties.</p>
257
257
+
258
258
+
<div class="grid gap-3" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));" id="color-grid">
259
259
+
<div class="card text-center" data-color="--wisp-bg">
260
260
+
<div class="p-4 rounded mb-2 color-swatch" style="background: var(--wisp-bg); border: 1px solid var(--wisp-border-light);">bg</div>
261
261
+
<small>--wisp-bg</small><br>
262
262
+
<code class="hex-code text-sm"></code>
263
263
+
</div>
264
264
+
<div class="card text-center" data-color="--wisp-bg-alt">
265
265
+
<div class="p-4 rounded mb-2 color-swatch" style="background: var(--wisp-bg-alt);">bg-alt</div>
266
266
+
<small>--wisp-bg-alt</small><br>
267
267
+
<code class="hex-code text-sm"></code>
268
268
+
</div>
269
269
+
<div class="card text-center" data-color="--wisp-text">
270
270
+
<div class="p-4 rounded mb-2 color-swatch" style="background: var(--wisp-text); color: var(--wisp-bg);">text</div>
271
271
+
<small>--wisp-text</small><br>
272
272
+
<code class="hex-code text-sm"></code>
273
273
+
</div>
274
274
+
<div class="card text-center" data-color="--wisp-accent">
275
275
+
<div class="p-4 rounded mb-2 color-swatch" style="background: var(--wisp-accent); color: white;">accent</div>
276
276
+
<small>--wisp-accent</small><br>
277
277
+
<code class="hex-code text-sm"></code>
278
278
+
</div>
279
279
+
<div class="card text-center" data-color="--wisp-success">
280
280
+
<div class="p-4 rounded mb-2 color-swatch" style="background: var(--wisp-success); color: white;">success</div>
281
281
+
<small>--wisp-success</small><br>
282
282
+
<code class="hex-code text-sm"></code>
283
283
+
</div>
284
284
+
<div class="card text-center" data-color="--wisp-danger">
285
285
+
<div class="p-4 rounded mb-2 color-swatch" style="background: var(--wisp-danger); color: white;">danger</div>
286
286
+
<small>--wisp-danger</small><br>
287
287
+
<code class="hex-code text-sm"></code>
288
288
+
</div>
289
289
+
<div class="card text-center" data-color="--wisp-warning">
290
290
+
<div class="p-4 rounded mb-2 color-swatch" style="background: var(--wisp-warning); color: black;">warning</div>
291
291
+
<small>--wisp-warning</small><br>
292
292
+
<code class="hex-code text-sm"></code>
293
293
+
</div>
294
294
+
<div class="card text-center" data-color="--wisp-info">
295
295
+
<div class="p-4 rounded mb-2 color-swatch" style="background: var(--wisp-info); color: white;">info</div>
296
296
+
<small>--wisp-info</small><br>
297
297
+
<code class="hex-code text-sm"></code>
298
298
+
</div>
299
299
+
</div>
300
300
+
</section>
301
301
+
</div>
302
302
+
303
303
+
<footer>
304
304
+
<div class="footer-inner">
305
305
+
<div class="footer-brand">
306
306
+
<p>wisp.css</p>
307
307
+
<p>A minimal CSS stylesheet for developers who appreciate monospace aesthetics.</p>
308
308
+
</div>
309
309
+
<div class="footer-col">
310
310
+
<h4>Docs</h4>
311
311
+
<ul>
312
312
+
<li><a href="#typography">Typography</a></li>
313
313
+
<li><a href="#components">Components</a></li>
314
314
+
<li><a href="#utilities">Utilities</a></li>
315
315
+
</ul>
316
316
+
</div>
317
317
+
<div class="footer-col">
318
318
+
<h4>Resources</h4>
319
319
+
<ul>
320
320
+
<li><a href="#">GitHub</a></li>
321
321
+
<li><a href="#">npm</a></li>
322
322
+
<li><a href="#">CDN</a></li>
323
323
+
</ul>
324
324
+
</div>
325
325
+
<div class="footer-col">
326
326
+
<h4>More</h4>
327
327
+
<ul>
328
328
+
<li><a href="#">wisp.place</a></li>
329
329
+
<li><a href="#">AT Protocol</a></li>
330
330
+
</ul>
331
331
+
</div>
332
332
+
</div>
333
333
+
<div class="footer-bottom">
334
334
+
<p>MIT License</p>
335
335
+
<nav>
336
336
+
<a href="#">GitHub</a>
337
337
+
</nav>
338
338
+
</div>
339
339
+
</footer>
340
340
+
341
341
+
<script>
342
342
+
// Theme switcher
343
343
+
const themes = ['system', 'light', 'dark']
344
344
+
const icons = { system: '◐', light: '☀', dark: '☾' }
345
345
+
const labels = { system: 'System', light: 'Light', dark: 'Dark' }
346
346
+
let currentTheme = localStorage.getItem('wisp-theme') || 'system'
347
347
+
348
348
+
function applyTheme(theme) {
349
349
+
if (theme === 'system') {
350
350
+
document.documentElement.removeAttribute('data-theme')
351
351
+
} else {
352
352
+
document.documentElement.setAttribute('data-theme', theme)
353
353
+
}
354
354
+
document.getElementById('theme-icon').textContent = icons[theme]
355
355
+
document.getElementById('theme-label').textContent = labels[theme]
356
356
+
localStorage.setItem('wisp-theme', theme)
357
357
+
setTimeout(updateHexCodes, 50)
358
358
+
}
359
359
+
360
360
+
document.getElementById('theme-toggle').addEventListener('click', () => {
361
361
+
const idx = themes.indexOf(currentTheme)
362
362
+
currentTheme = themes[(idx + 1) % themes.length]
363
363
+
applyTheme(currentTheme)
364
364
+
})
365
365
+
366
366
+
applyTheme(currentTheme)
367
367
+
368
368
+
// Hex code display
369
369
+
function rgbToHex(r, g, b) {
370
370
+
return '#' + [r, g, b].map(x => {
371
371
+
const hex = Math.round(x).toString(16)
372
372
+
return hex.length === 1 ? '0' + hex : hex
373
373
+
}).join('')
374
374
+
}
375
375
+
376
376
+
function parseColor(color) {
377
377
+
const canvas = document.createElement('canvas')
378
378
+
canvas.width = canvas.height = 1
379
379
+
const ctx = canvas.getContext('2d')
380
380
+
ctx.fillStyle = color
381
381
+
ctx.fillRect(0, 0, 1, 1)
382
382
+
const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data
383
383
+
return rgbToHex(r, g, b)
384
384
+
}
385
385
+
386
386
+
function updateHexCodes() {
387
387
+
document.querySelectorAll('[data-color]').forEach(card => {
388
388
+
const varName = card.dataset.color
389
389
+
const computed = getComputedStyle(document.documentElement).getPropertyValue(varName)
390
390
+
const hex = parseColor(computed.trim())
391
391
+
card.querySelector('.hex-code').textContent = hex.toUpperCase()
392
392
+
})
393
393
+
}
394
394
+
395
395
+
// Update hex codes on load and theme change
396
396
+
updateHexCodes()
397
397
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateHexCodes)
398
398
+
</script>
399
399
+
</body>
400
400
+
</html>
+6
packages/@wisp/css/index.ts
···
1
1
+
import { readFileSync } from 'node:fs'
2
2
+
import { fileURLToPath } from 'node:url'
3
3
+
import { dirname, join } from 'node:path'
4
4
+
5
5
+
const __dirname = dirname(fileURLToPath(import.meta.url))
6
6
+
export const css = readFileSync(join(__dirname, 'wisp.css'), 'utf-8')
+25
packages/@wisp/css/package.json
···
1
1
+
{
2
2
+
"name": "@wisp/css",
3
3
+
"version": "1.0.0",
4
4
+
"description": "A minimal, monospace-first CSS stylesheet with automatic light/dark mode",
5
5
+
"main": "index.ts",
6
6
+
"types": "index.ts",
7
7
+
"style": "wisp.css",
8
8
+
"files": [
9
9
+
"index.ts",
10
10
+
"wisp.css"
11
11
+
],
12
12
+
"keywords": [
13
13
+
"css",
14
14
+
"stylesheet",
15
15
+
"monospace",
16
16
+
"dark-mode",
17
17
+
"minimal"
18
18
+
],
19
19
+
"author": "nekomimi.pet",
20
20
+
"license": "MIT",
21
21
+
"repository": {
22
22
+
"type": "git",
23
23
+
"url": "https://github.com/nekomimi-pet/wisp.place-monorepo"
24
24
+
}
25
25
+
}
+896
packages/@wisp/css/wisp.css
···
1
1
+
/**
2
2
+
* wisp.css - A minimal, monospace-first CSS framework
3
3
+
*
4
4
+
* Usage:
5
5
+
* <link rel="stylesheet" href="https://sites.wisp.place/wisp.place/wisp.css/wisp.css">
6
6
+
*
7
7
+
* Requires JetBrains Mono font:
8
8
+
* <link rel="preconnect" href="https://fonts.googleapis.com">
9
9
+
* <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
10
+
* <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
11
11
+
*
12
12
+
* Features:
13
13
+
* - Light/dark mode via prefers-color-scheme
14
14
+
* - CSS custom properties for easy theming
15
15
+
* - Monospace typography with JetBrains Mono
16
16
+
* - Minimal component styles (cards, buttons, alerts)
17
17
+
* - Utility classes for spacing and layout
18
18
+
*
19
19
+
* License: MIT
20
20
+
*/
21
21
+
22
22
+
/* ==========================================================================
23
23
+
CSS Custom Properties (Theme)
24
24
+
========================================================================== */
25
25
+
26
26
+
:root {
27
27
+
/* Background colors */
28
28
+
--wisp-bg: oklch(0.92 0.012 35);
29
29
+
--wisp-bg-alt: oklch(0.88 0.01 35);
30
30
+
31
31
+
/* Text colors */
32
32
+
--wisp-text: oklch(0.15 0.015 30);
33
33
+
--wisp-text-muted: oklch(0.35 0.02 30);
34
34
+
--wisp-text-subtle: oklch(0.50 0.02 30);
35
35
+
36
36
+
/* Border colors */
37
37
+
--wisp-border: oklch(0.30 0.025 35);
38
38
+
--wisp-border-light: oklch(0.65 0.02 30);
39
39
+
40
40
+
/* Accent colors */
41
41
+
--wisp-accent: oklch(0.65 0.18 345);
42
42
+
--wisp-accent-muted: oklch(0.75 0.14 345);
43
43
+
44
44
+
/* CTA/Button colors */
45
45
+
--wisp-cta-bg: oklch(0.30 0.025 35);
46
46
+
--wisp-cta-text: oklch(0.96 0.008 35);
47
47
+
48
48
+
/* Semantic colors */
49
49
+
--wisp-code-bg: oklch(0.95 0.008 35);
50
50
+
--wisp-success: oklch(0.65 0.20 145);
51
51
+
--wisp-danger: oklch(0.60 0.20 25);
52
52
+
--wisp-warning: oklch(0.70 0.15 85);
53
53
+
--wisp-info: oklch(0.60 0.15 240);
54
54
+
55
55
+
/* Layout */
56
56
+
--wisp-max-width: 900px;
57
57
+
--wisp-header-max-width: 1100px;
58
58
+
59
59
+
/* Typography */
60
60
+
--wisp-font-family: "JetBrains Mono", ui-monospace, monospace;
61
61
+
--wisp-line-height: 1.6;
62
62
+
63
63
+
/* Spacing scale */
64
64
+
--wisp-space-1: 0.25rem;
65
65
+
--wisp-space-2: 0.5rem;
66
66
+
--wisp-space-3: 1rem;
67
67
+
--wisp-space-4: 1.5rem;
68
68
+
--wisp-space-5: 2rem;
69
69
+
--wisp-space-6: 3rem;
70
70
+
71
71
+
/* Border radius */
72
72
+
--wisp-radius-sm: 0.25rem;
73
73
+
--wisp-radius: 0.375rem;
74
74
+
--wisp-radius-lg: 0.5rem;
75
75
+
76
76
+
/* Backward-compatible aliases (unprefixed) */
77
77
+
--bg: var(--wisp-bg);
78
78
+
--bg-alt: var(--wisp-bg-alt);
79
79
+
--text: var(--wisp-text);
80
80
+
--text-muted: var(--wisp-text-muted);
81
81
+
--text-subtle: var(--wisp-text-subtle);
82
82
+
--border: var(--wisp-border);
83
83
+
--border-light: var(--wisp-border-light);
84
84
+
--accent: var(--wisp-accent);
85
85
+
--accent-muted: var(--wisp-accent-muted);
86
86
+
--cta-bg: var(--wisp-cta-bg);
87
87
+
--cta-text: var(--wisp-cta-text);
88
88
+
--code-bg: var(--wisp-code-bg);
89
89
+
--success: var(--wisp-success);
90
90
+
--red: var(--wisp-danger);
91
91
+
--yellow: var(--wisp-warning);
92
92
+
--blue: var(--wisp-info);
93
93
+
}
94
94
+
95
95
+
@media (prefers-color-scheme: dark) {
96
96
+
:root:not([data-theme="light"]) {
97
97
+
--wisp-bg: oklch(0.23 0.015 285);
98
98
+
--wisp-bg-alt: oklch(0.20 0.015 285);
99
99
+
--wisp-text: oklch(0.90 0.005 285);
100
100
+
--wisp-text-muted: oklch(0.72 0.01 285);
101
101
+
--wisp-text-subtle: oklch(0.55 0.01 285);
102
102
+
--wisp-border: oklch(0.90 0.005 285);
103
103
+
--wisp-border-light: oklch(0.38 0.02 285);
104
104
+
--wisp-accent: oklch(0.85 0.08 5);
105
105
+
--wisp-accent-muted: oklch(0.70 0.10 295);
106
106
+
--wisp-cta-bg: oklch(0.70 0.10 295);
107
107
+
--wisp-cta-text: oklch(0.23 0.015 285);
108
108
+
--wisp-code-bg: oklch(0.28 0.015 285);
109
109
+
--wisp-success: oklch(0.65 0.20 145);
110
110
+
--wisp-danger: oklch(0.65 0.20 25);
111
111
+
--wisp-warning: oklch(0.75 0.15 85);
112
112
+
--wisp-info: oklch(0.65 0.18 240);
113
113
+
114
114
+
/* Backward-compatible aliases */
115
115
+
--bg: var(--wisp-bg);
116
116
+
--bg-alt: var(--wisp-bg-alt);
117
117
+
--text: var(--wisp-text);
118
118
+
--text-muted: var(--wisp-text-muted);
119
119
+
--text-subtle: var(--wisp-text-subtle);
120
120
+
--border: var(--wisp-border);
121
121
+
--border-light: var(--wisp-border-light);
122
122
+
--accent: var(--wisp-accent);
123
123
+
--accent-muted: var(--wisp-accent-muted);
124
124
+
--cta-bg: var(--wisp-cta-bg);
125
125
+
--cta-text: var(--wisp-cta-text);
126
126
+
--code-bg: var(--wisp-code-bg);
127
127
+
--success: var(--wisp-success);
128
128
+
--red: var(--wisp-danger);
129
129
+
--yellow: var(--wisp-warning);
130
130
+
--blue: var(--wisp-info);
131
131
+
}
132
132
+
}
133
133
+
134
134
+
/* Manual dark theme override */
135
135
+
:root[data-theme="dark"] {
136
136
+
--wisp-bg: oklch(0.23 0.015 285);
137
137
+
--wisp-bg-alt: oklch(0.20 0.015 285);
138
138
+
--wisp-text: oklch(0.90 0.005 285);
139
139
+
--wisp-text-muted: oklch(0.72 0.01 285);
140
140
+
--wisp-text-subtle: oklch(0.55 0.01 285);
141
141
+
--wisp-border: oklch(0.90 0.005 285);
142
142
+
--wisp-border-light: oklch(0.38 0.02 285);
143
143
+
--wisp-accent: oklch(0.85 0.08 5);
144
144
+
--wisp-accent-muted: oklch(0.70 0.10 295);
145
145
+
--wisp-cta-bg: oklch(0.70 0.10 295);
146
146
+
--wisp-cta-text: oklch(0.23 0.015 285);
147
147
+
--wisp-code-bg: oklch(0.28 0.015 285);
148
148
+
--wisp-success: oklch(0.65 0.20 145);
149
149
+
--wisp-danger: oklch(0.65 0.20 25);
150
150
+
--wisp-warning: oklch(0.75 0.15 85);
151
151
+
--wisp-info: oklch(0.65 0.18 240);
152
152
+
153
153
+
/* Backward-compatible aliases */
154
154
+
--bg: var(--wisp-bg);
155
155
+
--bg-alt: var(--wisp-bg-alt);
156
156
+
--text: var(--wisp-text);
157
157
+
--text-muted: var(--wisp-text-muted);
158
158
+
--text-subtle: var(--wisp-text-subtle);
159
159
+
--border: var(--wisp-border);
160
160
+
--border-light: var(--wisp-border-light);
161
161
+
--accent: var(--wisp-accent);
162
162
+
--accent-muted: var(--wisp-accent-muted);
163
163
+
--cta-bg: var(--wisp-cta-bg);
164
164
+
--cta-text: var(--wisp-cta-text);
165
165
+
--code-bg: var(--wisp-code-bg);
166
166
+
--success: var(--wisp-success);
167
167
+
--red: var(--wisp-danger);
168
168
+
--yellow: var(--wisp-warning);
169
169
+
--blue: var(--wisp-info);
170
170
+
}
171
171
+
172
172
+
/* ==========================================================================
173
173
+
Base / Reset
174
174
+
========================================================================== */
175
175
+
176
176
+
*, *::before, *::after {
177
177
+
margin: 0;
178
178
+
padding: 0;
179
179
+
box-sizing: border-box;
180
180
+
}
181
181
+
182
182
+
html {
183
183
+
scroll-behavior: smooth;
184
184
+
-webkit-text-size-adjust: 100%;
185
185
+
}
186
186
+
187
187
+
body {
188
188
+
font-family: var(--wisp-font-family);
189
189
+
background: var(--wisp-bg);
190
190
+
color: var(--wisp-text);
191
191
+
min-height: 100vh;
192
192
+
display: flex;
193
193
+
flex-direction: column;
194
194
+
line-height: var(--wisp-line-height);
195
195
+
}
196
196
+
197
197
+
/* ==========================================================================
198
198
+
Typography
199
199
+
========================================================================== */
200
200
+
201
201
+
h1, h2, h3, h4, h5, h6 {
202
202
+
color: var(--wisp-text);
203
203
+
font-weight: 700;
204
204
+
line-height: 1.2;
205
205
+
}
206
206
+
207
207
+
h1 {
208
208
+
font-size: clamp(2rem, 5vw, 2.5rem);
209
209
+
margin-bottom: var(--wisp-space-4);
210
210
+
letter-spacing: -0.03em;
211
211
+
}
212
212
+
213
213
+
h2 {
214
214
+
font-size: 1.875rem;
215
215
+
margin-bottom: var(--wisp-space-4);
216
216
+
letter-spacing: -0.02em;
217
217
+
}
218
218
+
219
219
+
h3 {
220
220
+
font-size: 1.5rem;
221
221
+
font-weight: 600;
222
222
+
margin-bottom: var(--wisp-space-3);
223
223
+
}
224
224
+
225
225
+
h4 {
226
226
+
font-size: 1.25rem;
227
227
+
font-weight: 600;
228
228
+
margin-bottom: var(--wisp-space-2);
229
229
+
}
230
230
+
231
231
+
p {
232
232
+
color: var(--wisp-text-muted);
233
233
+
margin-bottom: var(--wisp-space-3);
234
234
+
line-height: 1.7;
235
235
+
}
236
236
+
237
237
+
strong, b {
238
238
+
color: var(--wisp-text);
239
239
+
font-weight: 600;
240
240
+
}
241
241
+
242
242
+
small {
243
243
+
font-size: 0.875rem;
244
244
+
color: var(--wisp-text-muted);
245
245
+
}
246
246
+
247
247
+
a {
248
248
+
color: var(--wisp-accent);
249
249
+
text-decoration: none;
250
250
+
transition: opacity 0.15s;
251
251
+
}
252
252
+
253
253
+
a:hover {
254
254
+
opacity: 0.8;
255
255
+
}
256
256
+
257
257
+
code {
258
258
+
font-family: var(--wisp-font-family);
259
259
+
background: var(--wisp-code-bg);
260
260
+
padding: 0.125rem 0.375rem;
261
261
+
border-radius: var(--wisp-radius-sm);
262
262
+
font-size: 0.875em;
263
263
+
}
264
264
+
265
265
+
pre {
266
266
+
background: var(--wisp-code-bg);
267
267
+
padding: var(--wisp-space-3);
268
268
+
border-radius: var(--wisp-radius);
269
269
+
overflow-x: auto;
270
270
+
margin-bottom: var(--wisp-space-3);
271
271
+
}
272
272
+
273
273
+
pre code {
274
274
+
background: none;
275
275
+
padding: 0;
276
276
+
}
277
277
+
278
278
+
/* ==========================================================================
279
279
+
Layout
280
280
+
========================================================================== */
281
281
+
282
282
+
.container {
283
283
+
max-width: var(--wisp-max-width);
284
284
+
margin: 0 auto;
285
285
+
padding: var(--wisp-space-6) var(--wisp-space-3);
286
286
+
}
287
287
+
288
288
+
.container-wide {
289
289
+
max-width: var(--wisp-header-max-width);
290
290
+
margin: 0 auto;
291
291
+
padding: var(--wisp-space-6) var(--wisp-space-3);
292
292
+
}
293
293
+
294
294
+
/* ==========================================================================
295
295
+
Header
296
296
+
========================================================================== */
297
297
+
298
298
+
header {
299
299
+
position: sticky;
300
300
+
top: 0;
301
301
+
background: var(--wisp-bg);
302
302
+
border-bottom: 1px solid var(--wisp-border-light);
303
303
+
padding: var(--wisp-space-3) var(--wisp-space-5);
304
304
+
z-index: 100;
305
305
+
}
306
306
+
307
307
+
.header-inner {
308
308
+
max-width: var(--wisp-header-max-width);
309
309
+
margin: 0 auto;
310
310
+
display: flex;
311
311
+
justify-content: space-between;
312
312
+
align-items: center;
313
313
+
}
314
314
+
315
315
+
.logo {
316
316
+
font-weight: 700;
317
317
+
font-size: 1.125rem;
318
318
+
text-decoration: none;
319
319
+
color: var(--wisp-text);
320
320
+
letter-spacing: -0.02em;
321
321
+
display: flex;
322
322
+
align-items: center;
323
323
+
gap: var(--wisp-space-2);
324
324
+
}
325
325
+
326
326
+
.logo:hover {
327
327
+
opacity: 1;
328
328
+
}
329
329
+
330
330
+
.logo img {
331
331
+
width: 2rem;
332
332
+
height: 2rem;
333
333
+
}
334
334
+
335
335
+
nav {
336
336
+
display: flex;
337
337
+
gap: var(--wisp-space-5);
338
338
+
align-items: center;
339
339
+
}
340
340
+
341
341
+
nav a {
342
342
+
color: var(--wisp-text-muted);
343
343
+
font-size: 1rem;
344
344
+
transition: color 0.15s;
345
345
+
}
346
346
+
347
347
+
nav a:hover {
348
348
+
color: var(--wisp-text);
349
349
+
opacity: 1;
350
350
+
}
351
351
+
352
352
+
/* ==========================================================================
353
353
+
Buttons
354
354
+
========================================================================== */
355
355
+
356
356
+
button, .btn {
357
357
+
display: inline-flex;
358
358
+
align-items: center;
359
359
+
justify-content: center;
360
360
+
gap: var(--wisp-space-2);
361
361
+
padding: var(--wisp-space-2) var(--wisp-space-3);
362
362
+
background: var(--wisp-cta-bg);
363
363
+
color: var(--wisp-cta-text);
364
364
+
border: none;
365
365
+
border-radius: var(--wisp-radius);
366
366
+
font-weight: 600;
367
367
+
font-size: 0.875rem;
368
368
+
font-family: var(--wisp-font-family);
369
369
+
cursor: pointer;
370
370
+
transition: opacity 0.15s;
371
371
+
text-decoration: none;
372
372
+
}
373
373
+
374
374
+
button:hover, .btn:hover {
375
375
+
opacity: 0.9;
376
376
+
}
377
377
+
378
378
+
button:disabled, .btn:disabled {
379
379
+
opacity: 0.5;
380
380
+
cursor: not-allowed;
381
381
+
}
382
382
+
383
383
+
button.outline, .btn.outline {
384
384
+
background: transparent;
385
385
+
color: var(--wisp-text);
386
386
+
border: 1px solid var(--wisp-border-light);
387
387
+
}
388
388
+
389
389
+
button.outline:hover, .btn.outline:hover {
390
390
+
background: var(--wisp-bg-alt);
391
391
+
opacity: 1;
392
392
+
}
393
393
+
394
394
+
.btn-lg {
395
395
+
padding: var(--wisp-space-3) var(--wisp-space-5);
396
396
+
font-size: 1rem;
397
397
+
}
398
398
+
399
399
+
.cta-primary {
400
400
+
background: var(--wisp-cta-bg);
401
401
+
color: var(--wisp-cta-text);
402
402
+
padding: var(--wisp-space-3) var(--wisp-space-5);
403
403
+
border: 2px solid var(--wisp-border);
404
404
+
font-weight: 600;
405
405
+
font-size: 1rem;
406
406
+
text-decoration: none;
407
407
+
text-transform: uppercase;
408
408
+
letter-spacing: 0.05em;
409
409
+
transition: all 0.15s;
410
410
+
}
411
411
+
412
412
+
.cta-primary:hover {
413
413
+
background: var(--wisp-bg);
414
414
+
color: var(--wisp-text);
415
415
+
opacity: 1;
416
416
+
}
417
417
+
418
418
+
.cta-secondary {
419
419
+
background: transparent;
420
420
+
color: var(--wisp-text);
421
421
+
padding: var(--wisp-space-3) var(--wisp-space-5);
422
422
+
border: 2px solid var(--wisp-border);
423
423
+
font-weight: 600;
424
424
+
font-size: 1rem;
425
425
+
text-decoration: none;
426
426
+
text-transform: uppercase;
427
427
+
letter-spacing: 0.05em;
428
428
+
transition: all 0.15s;
429
429
+
}
430
430
+
431
431
+
.cta-secondary:hover {
432
432
+
background: var(--wisp-cta-bg);
433
433
+
color: var(--wisp-cta-text);
434
434
+
opacity: 1;
435
435
+
}
436
436
+
437
437
+
.nav-cta {
438
438
+
background: var(--wisp-cta-bg);
439
439
+
color: var(--wisp-cta-text);
440
440
+
padding: var(--wisp-space-2) var(--wisp-space-3);
441
441
+
border: 1px solid var(--wisp-border);
442
442
+
font-weight: 600;
443
443
+
font-size: 0.875rem;
444
444
+
text-transform: uppercase;
445
445
+
letter-spacing: 0.05em;
446
446
+
transition: all 0.15s;
447
447
+
}
448
448
+
449
449
+
.nav-cta:hover {
450
450
+
background: var(--wisp-bg);
451
451
+
color: var(--wisp-text);
452
452
+
opacity: 1;
453
453
+
}
454
454
+
455
455
+
/* ==========================================================================
456
456
+
Forms
457
457
+
========================================================================== */
458
458
+
459
459
+
label {
460
460
+
display: block;
461
461
+
margin-bottom: var(--wisp-space-2);
462
462
+
font-size: 0.875rem;
463
463
+
font-weight: 500;
464
464
+
color: var(--wisp-text);
465
465
+
}
466
466
+
467
467
+
input, textarea, select {
468
468
+
width: 100%;
469
469
+
padding: var(--wisp-space-2) 0.75rem;
470
470
+
background: var(--wisp-bg);
471
471
+
border: 1px solid var(--wisp-border-light);
472
472
+
border-radius: var(--wisp-radius);
473
473
+
color: var(--wisp-text);
474
474
+
font-size: 0.875rem;
475
475
+
font-family: var(--wisp-font-family);
476
476
+
}
477
477
+
478
478
+
input:focus, textarea:focus, select:focus {
479
479
+
outline: 2px solid var(--wisp-accent);
480
480
+
outline-offset: 2px;
481
481
+
}
482
482
+
483
483
+
input::placeholder, textarea::placeholder {
484
484
+
color: var(--wisp-text-subtle);
485
485
+
}
486
486
+
487
487
+
/* ==========================================================================
488
488
+
Cards
489
489
+
========================================================================== */
490
490
+
491
491
+
.card {
492
492
+
background: var(--wisp-bg-alt);
493
493
+
border: 1px solid var(--wisp-border-light);
494
494
+
border-radius: var(--wisp-radius-lg);
495
495
+
padding: var(--wisp-space-4);
496
496
+
margin-bottom: var(--wisp-space-3);
497
497
+
}
498
498
+
499
499
+
.card.success {
500
500
+
background: color-mix(in oklch, var(--wisp-success) 5%, var(--wisp-bg-alt));
501
501
+
border-color: color-mix(in oklch, var(--wisp-success) 20%, transparent);
502
502
+
}
503
503
+
504
504
+
.card.danger {
505
505
+
background: color-mix(in oklch, var(--wisp-danger) 5%, var(--wisp-bg-alt));
506
506
+
border-color: color-mix(in oklch, var(--wisp-danger) 30%, transparent);
507
507
+
border-width: 2px;
508
508
+
}
509
509
+
510
510
+
.card.warning {
511
511
+
background: color-mix(in oklch, var(--wisp-warning) 5%, var(--wisp-bg-alt));
512
512
+
border-color: color-mix(in oklch, var(--wisp-warning) 20%, transparent);
513
513
+
}
514
514
+
515
515
+
.card.info {
516
516
+
background: color-mix(in oklch, var(--wisp-info) 5%, var(--wisp-bg-alt));
517
517
+
border-color: color-mix(in oklch, var(--wisp-info) 20%, transparent);
518
518
+
}
519
519
+
520
520
+
.card.thick {
521
521
+
border-width: 2px;
522
522
+
}
523
523
+
524
524
+
.card-flex {
525
525
+
display: flex;
526
526
+
gap: var(--wisp-space-3);
527
527
+
align-items: start;
528
528
+
}
529
529
+
530
530
+
.card-icon {
531
531
+
flex-shrink: 0;
532
532
+
width: 2rem;
533
533
+
height: 2rem;
534
534
+
}
535
535
+
536
536
+
/* ==========================================================================
537
537
+
Alerts
538
538
+
========================================================================== */
539
539
+
540
540
+
.alert {
541
541
+
padding: var(--wisp-space-3);
542
542
+
border-radius: var(--wisp-radius-lg);
543
543
+
margin-bottom: var(--wisp-space-3);
544
544
+
}
545
545
+
546
546
+
.alert-success {
547
547
+
background: var(--wisp-code-bg);
548
548
+
border: 1px solid var(--wisp-border-light);
549
549
+
color: var(--wisp-success);
550
550
+
}
551
551
+
552
552
+
.alert-info {
553
553
+
background: var(--wisp-code-bg);
554
554
+
color: var(--wisp-text-muted);
555
555
+
}
556
556
+
557
557
+
.alert-warning {
558
558
+
background: var(--wisp-code-bg);
559
559
+
border-left: 4px solid var(--wisp-warning);
560
560
+
}
561
561
+
562
562
+
.alert-danger {
563
563
+
background: var(--wisp-code-bg);
564
564
+
border-left: 4px solid var(--wisp-danger);
565
565
+
}
566
566
+
567
567
+
/* ==========================================================================
568
568
+
Lists
569
569
+
========================================================================== */
570
570
+
571
571
+
ul, ol {
572
572
+
list-style: none;
573
573
+
margin: var(--wisp-space-3) 0;
574
574
+
}
575
575
+
576
576
+
ol {
577
577
+
counter-reset: item;
578
578
+
}
579
579
+
580
580
+
ol li {
581
581
+
counter-increment: item;
582
582
+
}
583
583
+
584
584
+
ol li::before {
585
585
+
content: counter(item) ".";
586
586
+
font-weight: 700;
587
587
+
color: var(--wisp-accent);
588
588
+
margin-right: 0.75rem;
589
589
+
}
590
590
+
591
591
+
.bullet {
592
592
+
flex-shrink: 0;
593
593
+
margin-top: 0.25rem;
594
594
+
}
595
595
+
596
596
+
.bullet.red, .bullet.danger { color: var(--wisp-danger); }
597
597
+
.bullet.green, .bullet.success { color: var(--wisp-success); }
598
598
+
.bullet.yellow, .bullet.warning { color: var(--wisp-warning); }
599
599
+
.bullet.accent { color: var(--wisp-accent); }
600
600
+
601
601
+
/* ==========================================================================
602
602
+
Footer
603
603
+
========================================================================== */
604
604
+
605
605
+
footer {
606
606
+
padding: var(--wisp-space-6) var(--wisp-space-5) var(--wisp-space-5);
607
607
+
border-top: 1px solid var(--wisp-border-light);
608
608
+
margin-top: auto;
609
609
+
}
610
610
+
611
611
+
.footer-inner {
612
612
+
max-width: var(--wisp-header-max-width);
613
613
+
margin: 0 auto;
614
614
+
display: grid;
615
615
+
grid-template-columns: 2fr repeat(3, 1fr);
616
616
+
gap: var(--wisp-space-6);
617
617
+
}
618
618
+
619
619
+
.footer-brand p:first-child {
620
620
+
font-weight: 700;
621
621
+
margin-bottom: 0.75rem;
622
622
+
}
623
623
+
624
624
+
.footer-brand p:last-child {
625
625
+
color: var(--wisp-text-muted);
626
626
+
line-height: 1.6;
627
627
+
}
628
628
+
629
629
+
.footer-col h4 {
630
630
+
font-size: 0.875rem;
631
631
+
color: var(--wisp-text-subtle);
632
632
+
text-transform: uppercase;
633
633
+
letter-spacing: 0.05em;
634
634
+
margin-bottom: var(--wisp-space-3);
635
635
+
}
636
636
+
637
637
+
.footer-col ul {
638
638
+
margin: 0;
639
639
+
}
640
640
+
641
641
+
.footer-col li {
642
642
+
margin-bottom: var(--wisp-space-2);
643
643
+
}
644
644
+
645
645
+
.footer-col a {
646
646
+
color: var(--wisp-text-muted);
647
647
+
transition: color 0.15s;
648
648
+
}
649
649
+
650
650
+
.footer-col a:hover {
651
651
+
color: var(--wisp-text);
652
652
+
opacity: 1;
653
653
+
}
654
654
+
655
655
+
.footer-bottom {
656
656
+
max-width: var(--wisp-header-max-width);
657
657
+
margin: var(--wisp-space-6) auto 0;
658
658
+
padding-top: var(--wisp-space-5);
659
659
+
border-top: 1px solid var(--wisp-border-light);
660
660
+
display: flex;
661
661
+
justify-content: space-between;
662
662
+
align-items: center;
663
663
+
}
664
664
+
665
665
+
.footer-bottom p {
666
666
+
color: var(--wisp-text-subtle);
667
667
+
font-size: 0.875rem;
668
668
+
}
669
669
+
670
670
+
.footer-bottom nav {
671
671
+
gap: var(--wisp-space-4);
672
672
+
}
673
673
+
674
674
+
.footer-bottom a {
675
675
+
color: var(--wisp-text-subtle);
676
676
+
font-size: 0.875rem;
677
677
+
}
678
678
+
679
679
+
.footer-bottom a:hover {
680
680
+
color: var(--wisp-text);
681
681
+
opacity: 1;
682
682
+
}
683
683
+
684
684
+
footer.simple {
685
685
+
background: var(--wisp-bg-alt);
686
686
+
padding: var(--wisp-space-5) var(--wisp-space-3);
687
687
+
text-align: center;
688
688
+
font-size: 0.875rem;
689
689
+
color: var(--wisp-text-muted);
690
690
+
}
691
691
+
692
692
+
footer.simple p {
693
693
+
margin-bottom: var(--wisp-space-2);
694
694
+
}
695
695
+
696
696
+
/* ==========================================================================
697
697
+
Spinner
698
698
+
========================================================================== */
699
699
+
700
700
+
.spinner {
701
701
+
display: inline-block;
702
702
+
width: 1rem;
703
703
+
height: 1rem;
704
704
+
border: 2px solid var(--wisp-border-light);
705
705
+
border-top-color: var(--wisp-accent);
706
706
+
border-radius: 50%;
707
707
+
animation: wisp-spin 0.6s linear infinite;
708
708
+
}
709
709
+
710
710
+
.spinner-lg {
711
711
+
width: 2rem;
712
712
+
height: 2rem;
713
713
+
border-width: 3px;
714
714
+
}
715
715
+
716
716
+
@keyframes wisp-spin {
717
717
+
to { transform: rotate(360deg); }
718
718
+
}
719
719
+
720
720
+
/* ==========================================================================
721
721
+
Utility Classes
722
722
+
========================================================================== */
723
723
+
724
724
+
/* Display */
725
725
+
.hidden { display: none; }
726
726
+
.block { display: block; }
727
727
+
.inline { display: inline; }
728
728
+
.inline-block { display: inline-block; }
729
729
+
.flex { display: flex; }
730
730
+
.inline-flex { display: inline-flex; }
731
731
+
.grid { display: grid; }
732
732
+
733
733
+
/* Flexbox */
734
734
+
.flex-col { flex-direction: column; }
735
735
+
.flex-wrap { flex-wrap: wrap; }
736
736
+
.items-start { align-items: flex-start; }
737
737
+
.items-center { align-items: center; }
738
738
+
.items-end { align-items: flex-end; }
739
739
+
.justify-start { justify-content: flex-start; }
740
740
+
.justify-center { justify-content: center; }
741
741
+
.justify-end { justify-content: flex-end; }
742
742
+
.justify-between { justify-content: space-between; }
743
743
+
744
744
+
/* Gap */
745
745
+
.gap-1 { gap: var(--wisp-space-1); }
746
746
+
.gap-2 { gap: var(--wisp-space-2); }
747
747
+
.gap-3 { gap: var(--wisp-space-3); }
748
748
+
.gap-4 { gap: var(--wisp-space-4); }
749
749
+
.gap-5 { gap: var(--wisp-space-5); }
750
750
+
.gap-6 { gap: var(--wisp-space-6); }
751
751
+
752
752
+
/* Margin */
753
753
+
.m-0 { margin: 0; }
754
754
+
.m-1 { margin: var(--wisp-space-1); }
755
755
+
.m-2 { margin: var(--wisp-space-2); }
756
756
+
.m-3 { margin: var(--wisp-space-3); }
757
757
+
.m-4 { margin: var(--wisp-space-4); }
758
758
+
759
759
+
.mt-0 { margin-top: 0; }
760
760
+
.mt-1 { margin-top: var(--wisp-space-1); }
761
761
+
.mt-2 { margin-top: var(--wisp-space-2); }
762
762
+
.mt-3 { margin-top: var(--wisp-space-3); }
763
763
+
.mt-4 { margin-top: var(--wisp-space-4); }
764
764
+
.mt-5 { margin-top: var(--wisp-space-5); }
765
765
+
.mt-6 { margin-top: var(--wisp-space-6); }
766
766
+
767
767
+
.mb-0 { margin-bottom: 0; }
768
768
+
.mb-1 { margin-bottom: var(--wisp-space-1); }
769
769
+
.mb-2 { margin-bottom: var(--wisp-space-2); }
770
770
+
.mb-3 { margin-bottom: var(--wisp-space-3); }
771
771
+
.mb-4 { margin-bottom: var(--wisp-space-4); }
772
772
+
.mb-5 { margin-bottom: var(--wisp-space-5); }
773
773
+
.mb-6 { margin-bottom: var(--wisp-space-6); }
774
774
+
775
775
+
.ml-0 { margin-left: 0; }
776
776
+
.ml-1 { margin-left: var(--wisp-space-1); }
777
777
+
.ml-2 { margin-left: var(--wisp-space-2); }
778
778
+
.ml-3 { margin-left: var(--wisp-space-3); }
779
779
+
.ml-4 { margin-left: var(--wisp-space-4); }
780
780
+
781
781
+
.mr-0 { margin-right: 0; }
782
782
+
.mr-1 { margin-right: var(--wisp-space-1); }
783
783
+
.mr-2 { margin-right: var(--wisp-space-2); }
784
784
+
.mr-3 { margin-right: var(--wisp-space-3); }
785
785
+
.mr-4 { margin-right: var(--wisp-space-4); }
786
786
+
787
787
+
/* Padding */
788
788
+
.p-0 { padding: 0; }
789
789
+
.p-1 { padding: var(--wisp-space-1); }
790
790
+
.p-2 { padding: var(--wisp-space-2); }
791
791
+
.p-3 { padding: var(--wisp-space-3); }
792
792
+
.p-4 { padding: var(--wisp-space-4); }
793
793
+
.p-5 { padding: var(--wisp-space-5); }
794
794
+
.p-6 { padding: var(--wisp-space-6); }
795
795
+
796
796
+
.px-1 { padding-left: var(--wisp-space-1); padding-right: var(--wisp-space-1); }
797
797
+
.px-2 { padding-left: var(--wisp-space-2); padding-right: var(--wisp-space-2); }
798
798
+
.px-3 { padding-left: var(--wisp-space-3); padding-right: var(--wisp-space-3); }
799
799
+
.px-4 { padding-left: var(--wisp-space-4); padding-right: var(--wisp-space-4); }
800
800
+
801
801
+
.py-1 { padding-top: var(--wisp-space-1); padding-bottom: var(--wisp-space-1); }
802
802
+
.py-2 { padding-top: var(--wisp-space-2); padding-bottom: var(--wisp-space-2); }
803
803
+
.py-3 { padding-top: var(--wisp-space-3); padding-bottom: var(--wisp-space-3); }
804
804
+
.py-4 { padding-top: var(--wisp-space-4); padding-bottom: var(--wisp-space-4); }
805
805
+
806
806
+
/* Width */
807
807
+
.w-full { width: 100%; }
808
808
+
.w-auto { width: auto; }
809
809
+
.max-w-sm { max-width: 24rem; }
810
810
+
.max-w-md { max-width: 28rem; }
811
811
+
.max-w-lg { max-width: 32rem; }
812
812
+
.max-w-xl { max-width: 36rem; }
813
813
+
814
814
+
/* Text */
815
815
+
.text-left { text-align: left; }
816
816
+
.text-center { text-align: center; }
817
817
+
.text-right { text-align: right; }
818
818
+
819
819
+
.text-sm { font-size: 0.875rem; }
820
820
+
.text-base { font-size: 1rem; }
821
821
+
.text-lg { font-size: 1.125rem; }
822
822
+
.text-xl { font-size: 1.25rem; }
823
823
+
824
824
+
.font-normal { font-weight: 400; }
825
825
+
.font-medium { font-weight: 500; }
826
826
+
.font-semibold { font-weight: 600; }
827
827
+
.font-bold { font-weight: 700; }
828
828
+
829
829
+
.text-muted { color: var(--wisp-text-muted); }
830
830
+
.text-subtle { color: var(--wisp-text-subtle); }
831
831
+
.text-accent { color: var(--wisp-accent); }
832
832
+
.text-success { color: var(--wisp-success); }
833
833
+
.text-danger { color: var(--wisp-danger); }
834
834
+
.text-warning { color: var(--wisp-warning); }
835
835
+
.text-info { color: var(--wisp-info); }
836
836
+
837
837
+
/* Background */
838
838
+
.bg-alt { background: var(--wisp-bg-alt); }
839
839
+
.bg-code { background: var(--wisp-code-bg); }
840
840
+
841
841
+
/* Border */
842
842
+
.border { border: 1px solid var(--wisp-border-light); }
843
843
+
.border-2 { border: 2px solid var(--wisp-border-light); }
844
844
+
.rounded { border-radius: var(--wisp-radius); }
845
845
+
.rounded-lg { border-radius: var(--wisp-radius-lg); }
846
846
+
847
847
+
/* Other */
848
848
+
.cursor-pointer { cursor: pointer; }
849
849
+
.select-none { user-select: none; }
850
850
+
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
851
851
+
852
852
+
/* ==========================================================================
853
853
+
Responsive
854
854
+
========================================================================== */
855
855
+
856
856
+
@media (max-width: 768px) {
857
857
+
nav a:not(.nav-cta) {
858
858
+
display: none;
859
859
+
}
860
860
+
861
861
+
.footer-inner {
862
862
+
grid-template-columns: 1fr 1fr;
863
863
+
}
864
864
+
865
865
+
.footer-brand {
866
866
+
grid-column: span 2;
867
867
+
}
868
868
+
869
869
+
.footer-bottom {
870
870
+
flex-direction: column;
871
871
+
gap: var(--wisp-space-3);
872
872
+
text-align: center;
873
873
+
}
874
874
+
875
875
+
h1 { font-size: 2rem; }
876
876
+
h2 { font-size: 1.5rem; }
877
877
+
h3 { font-size: 1.25rem; }
878
878
+
879
879
+
.hide-mobile { display: none; }
880
880
+
}
881
881
+
882
882
+
@media (max-width: 480px) {
883
883
+
.footer-inner {
884
884
+
grid-template-columns: 1fr;
885
885
+
}
886
886
+
887
887
+
.footer-brand {
888
888
+
grid-column: span 1;
889
889
+
}
890
890
+
891
891
+
.hide-sm { display: none; }
892
892
+
}
893
893
+
894
894
+
@media (min-width: 769px) {
895
895
+
.hide-desktop { display: none; }
896
896
+
}
+3
-2
packages/@wisp/observability/src/middleware/elysia.ts
···
49
49
service
50
50
)
51
51
52
52
-
// Don't log 404 errors
53
53
-
if (statusCode !== 404) {
52
52
+
// Don't log 404 errors or expected auth failures
53
53
+
const isAuthError = error?.message === 'Authentication required'
54
54
+
if (statusCode !== 404 && !isAuthError) {
54
55
logCollector.error(
55
56
`Request failed: ${request.method} ${url.pathname}`,
56
57
service,
+1
-12
scripts/codegen.sh
···
7
7
# Parse arguments
8
8
AUTO_ACCEPT=""
9
9
if [[ "$1" == "-y" || "$1" == "--yes" ]]; then
10
10
-
AUTO_ACCEPT="yes |"
10
10
+
AUTO_ACCEPT="yes |"
11
11
fi
12
12
13
13
echo "=== Generating TypeScript lexicons ==="
14
14
cd "$ROOT_DIR/packages/@wisp/lexicons"
15
15
eval "$AUTO_ACCEPT npm run codegen"
16
16
-
17
17
-
echo "=== Generating Rust lexicons ==="
18
18
-
echo "Installing jacquard-lexgen..."
19
19
-
cargo install jacquard-lexgen --version 0.9.5 2>/dev/null || true
20
20
-
echo "Running jacquard-codegen..."
21
21
-
echo " Input: $ROOT_DIR/lexicons"
22
22
-
echo " Output: $ROOT_DIR/cli/crates/lexicons/src"
23
23
-
jacquard-codegen -i "$ROOT_DIR/lexicons" -o "$ROOT_DIR/cli/crates/lexicons/src"
24
24
-
25
25
-
# Add extern crate alloc for the macro to work
26
26
-
sed -i '' '1s/^/extern crate alloc;\n\n/' "$ROOT_DIR/cli/crates/lexicons/src/lib.rs"
27
16
28
17
echo "=== Done ==="