tangled
alpha
login
or
join now
microcosm.blue
/
hubble-website
0
fork
atom
top secret
0
fork
atom
overview
issues
pulls
pipelines
clean things up a bit
bad-example.com
3 weeks ago
94dcd5ca
f96132fb
+102
-528
6 changed files
expand all
collapse all
unified
split
src
components
Footer.astro
Nav.astro
StarField.astro
layouts
Base.astro
pages
index.astro
styles
global.css
+5
-12
src/components/Footer.astro
···
2
2
---
3
3
4
4
<footer class="footer">
5
5
-
<div class="container footer__inner">
6
6
-
<p class="footer__copy">
7
7
-
<!-- Hubble is created and operated by <a href="https://microcosm.blue" target="_blank">microcosm</a>, with support from <a href="https://bsky.app" target="_blank">Bluesky</a>. -->
5
5
+
<div class="container footer-inner">
6
6
+
<p class="footer-copy">
7
7
+
(footer)
8
8
</p>
9
9
</div>
10
10
</footer>
···
15
15
margin-top: auto;
16
16
}
17
17
18
18
-
.footer__inner {
18
18
+
.footer-inner {
19
19
display: flex;
20
20
align-items: center;
21
21
justify-content: space-between;
···
23
23
gap: var(--space-4);
24
24
}
25
25
26
26
-
.footer__copy {
26
26
+
.footer-copy {
27
27
font-size: 0.875rem;
28
28
color: var(--color-muted);
29
29
margin-bottom: 0;
30
30
max-width: none;
31
31
}
32
32
33
33
-
.footer__links {
34
34
-
display: flex;
35
35
-
gap: var(--space-4);
36
36
-
list-style: none;
37
37
-
}
38
38
-
39
33
.footer a {
40
34
font-size: 0.875rem;
41
35
color: var(--color-muted);
42
36
text-decoration: none;
43
37
transition: color var(--transition);
44
38
}
45
45
-
46
39
.footer a:hover {
47
40
color: var(--color-accent2);
48
41
}
+15
-15
src/components/Nav.astro
···
9
9
---
10
10
11
11
<header class="nav">
12
12
-
<div class="container nav__inner">
13
13
-
<a class="nav__brand" href="/" aria-label="Hubble home">
12
12
+
<div class="container nav-inner">
13
13
+
<a class="nav-brand" href="/" aria-label="Hubble home">
14
14
<img
15
15
src="/molten-ring.png"
16
16
height="64"
17
17
width="64"
18
18
-
alt="(logo) the molten ring galaxy cluster, captured by hubble"
18
18
+
alt="(logo) the molten ring galaxy cluster, captured by the hubble space telescope"
19
19
/>
20
20
-
<span class="nav__brand-name">Hubble</span>
20
20
+
<span class="nav-brand-name">Hubble</span>
21
21
</a>
22
22
23
23
<nav aria-label="Primary navigation">
24
24
-
<ul class="nav__links" role="list">
24
24
+
<ul class="nav-links" role="list">
25
25
{navLinks.map(({ href, label, pop }) => {
26
26
const isActive = pathname === href || (href !== '/' && pathname.startsWith(href));
27
27
return (
28
28
<li>
29
29
<a
30
30
href={href}
31
31
-
class:list={['nav__link', { 'nav__link--active': isActive }]}
31
31
+
class:list={['nav-link', { 'nav-link--active': isActive }]}
32
32
aria-current={isActive ? 'page' : undefined}
33
33
target={pop ? '_blank' : undefined}
34
34
>
···
43
43
</header>
44
44
45
45
<style>
46
46
-
.nav__inner {
46
46
+
.nav-inner {
47
47
display: flex;
48
48
align-items: center;
49
49
justify-content: space-between;
50
50
height: 4rem;
51
51
}
52
52
53
53
-
.nav__brand {
53
53
+
.nav-brand {
54
54
display: inline-flex;
55
55
align-items: center;
56
56
margin: 0;
···
62
62
align-items: center;
63
63
}
64
64
65
65
-
.nav__brand:hover {
65
65
+
.nav-brand:hover {
66
66
color: var(--color-accent2);
67
67
}
68
68
69
69
-
.nav__brand-name {
69
69
+
.nav-brand-name {
70
70
font-family: var(--font-heading);
71
71
font-weight: 300;
72
72
font-size: 1.25rem;
73
73
}
74
74
75
75
-
.nav__links {
75
75
+
.nav-links {
76
76
display: flex;
77
77
align-items: center;
78
78
gap: 0.2rem;
79
79
list-style: none;
80
80
}
81
81
82
82
-
.nav__link {
82
82
+
.nav-link {
83
83
display: block;
84
84
padding: 0.2rem 0.75rem;
85
85
border-radius: var(--radius-md);
···
90
90
transition: color var(--transition), background-color var(--transition);
91
91
}
92
92
93
93
-
.nav__link:hover {
93
93
+
.nav-link:hover {
94
94
color: var(--color-text);
95
95
background: var(--color-border);
96
96
}
97
97
98
98
-
.nav__link--active {
98
98
+
.nav-link--active {
99
99
color: var(--color-accent2);
100
100
background: color-mix(in srgb, var(--color-accent2) 12%, transparent);
101
101
}
102
102
103
103
-
.nav__link--active:hover {
103
103
+
.nav-link--active:hover {
104
104
color: var(--color-accent2);
105
105
background: color-mix(in srgb, var(--color-accent2) 18%, transparent);
106
106
}
+15
-27
src/components/StarField.astro
···
1
1
---
2
2
-
const STAR_COUNT = 150;
3
3
-
4
4
-
interface Star {
5
5
-
top: number;
6
6
-
left: number;
7
7
-
opacity: number;
8
8
-
duration: number;
9
9
-
delay: number;
10
10
-
size: number;
2
2
+
function style_range(min: number, max: number, f: number = 2): number {
3
3
+
return (Math.random() * (max - min) + min).toFixed(f);
11
4
}
12
5
13
13
-
function rnd(min: number, max: number): number {
14
14
-
return Math.random() * (max - min) + min;
15
15
-
}
16
16
-
17
17
-
const stars: Star[] = Array.from({ length: STAR_COUNT }, () => ({
18
18
-
top: rnd(0, 100),
19
19
-
left: rnd(0, 100),
20
20
-
opacity: rnd(0.2, 0.8),
21
21
-
duration: rnd(2, 6),
22
22
-
delay: rnd(0, 5),
23
23
-
size: rnd(1, 3),
24
24
-
}));
6
6
+
const stars: string[] = Array.from({ length: 150 }, () => {
7
7
+
const size = style_range(1, 3, 1);
8
8
+
let style = "";
9
9
+
style += `width:${size}px;`;
10
10
+
style += `height:${size}px;`;
11
11
+
style += `top:${style_range(0, 100)}%;`;
12
12
+
style += `left:${style_range(0, 100)}%;`;
13
13
+
style += `--star-opacity:${style_range(0.25, 0.81)};`;
14
14
+
style += `--star-duration:${style_range(1.6, 7)}s;`;
15
15
+
style += `--star-delay:${style_range(0, 4)}s;`;
16
16
+
return style;
17
17
+
});
25
18
---
26
19
27
20
<div class="starfield" aria-hidden="true">
28
28
-
{stars.map((s) => (
29
29
-
<span
30
30
-
class="star"
31
31
-
style={`top:${s.top.toFixed(2)}%;left:${s.left.toFixed(2)}%;--star-opacity:${s.opacity.toFixed(2)};--star-duration:${s.duration.toFixed(2)}s;--star-delay:${s.delay.toFixed(2)}s;width:${s.size.toFixed(1)}px;height:${s.size.toFixed(1)}px;`}
32
32
-
/>
33
33
-
))}
21
21
+
{stars.map((s) => <span class="star" style={s} />)}
34
22
</div>
+5
-6
src/layouts/Base.astro
···
8
8
title?: string;
9
9
description?: string;
10
10
}
11
11
-
12
11
const {
13
12
title: providedTitle,
14
14
-
description = 'Hubble — explore the universe of knowledge.',
13
13
+
description = 'Hubble synchronizes an authenticated copy of data repositories in the atmosphere, so you can recover your content even if your PDS loses it.',
15
14
} = Astro.props;
16
15
17
17
-
const title = providedTitle ? `${providedTitle} | Hubble` : 'Hubble';
18
18
-
const canonicalURL = new URL(Astro.url.pathname, Astro.site ?? 'https://hubble.example.com');
16
16
+
const title = providedTitle ? `${providedTitle} | Hubble` : 'Hubble: full-network atmosphere mirror';
17
17
+
const canonicalURL = new URL(Astro.url.pathname, Astro.site ?? 'https://hubble.microcosm.blue');
19
18
---
20
19
21
20
<!doctype html>
···
23
22
<head>
24
23
<meta charset="utf-8" />
25
24
<meta name="viewport" content="width=device-width, initial-scale=1" />
25
25
+
<title>{title}</title>
26
26
<meta name="description" content={description} />
27
27
-
<link rel="canonical" href={canonicalURL} />
28
27
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
29
29
-
<title>{title}</title>
28
28
+
<link rel="canonical" href={canonicalURL} />
30
29
</head>
31
30
<body>
32
31
<StarField />
+27
-379
src/pages/index.astro
···
1
1
---
2
2
import Base from '../layouts/Base.astro';
3
3
-
4
4
-
const features = [
5
5
-
{
6
6
-
title: 'A neat thing!',
7
7
-
description: 'A short thing about it. idk if i like these, but here they are and they can stay for the moment.',
8
8
-
},
9
9
-
{
10
10
-
title: 'Something cool',
11
11
-
description: 'With more words to write. maybe i should make ai write some slop for these.',
12
12
-
},
13
13
-
{
14
14
-
title: 'Open-Source',
15
15
-
description: 'Is this how stuff like that will be presented? a lil box just to say "MIT/Apache" or whatever?',
16
16
-
},
17
17
-
{
18
18
-
title: 'Blah blah blah',
19
19
-
description: 'blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah.',
20
20
-
},
21
21
-
{
22
22
-
title: 'Hopefully i remember',
23
23
-
description: 'to change the words in this box befor sharing anything publicly',
24
24
-
},
25
25
-
{
26
26
-
title: 'Okay we made it to six!',
27
27
-
description: 'Six boxes. So the grid layout can be tested. Is it toooo bland and generic?',
28
28
-
},
29
29
-
];
30
30
-
31
31
-
const faqs = [
32
32
-
{
33
33
-
question: 'What is Hubble?',
34
34
-
answer: 'alskdjflkaj flaj sflkj aslkdjf lakj',
35
35
-
},
36
36
-
{
37
37
-
question: 'Who is Hubble for?',
38
38
-
answer: 'alskdjflkaj flaj sflkj aslkdjf lakj',
39
39
-
},
40
40
-
{
41
41
-
question: 'Is Hubble free to use?',
42
42
-
answer: 'alskdjflkaj flaj sflkj aslkdjf lakj',
43
43
-
},
44
44
-
{
45
45
-
question: 'How is content created on Hubble?',
46
46
-
answer: 'alskdjflkaj flaj sflkj aslkdjf lakj',
47
47
-
},
48
48
-
{
49
49
-
question: 'Can I contribute to Hubble?',
50
50
-
answer: 'alskdjflkaj flaj sflkj aslkdjf lakj',
51
51
-
},
52
52
-
{
53
53
-
question: 'Does Hubble track me or show ads?',
54
54
-
answer: 'alskdjflkaj flaj sflkj aslkdjf lakj',
55
55
-
},
56
56
-
{
57
57
-
question: 'How do I report an error or suggest an improvement?',
58
58
-
answer: 'alskdjflkaj flaj sflkj aslkdjf lakj',
59
59
-
},
60
60
-
{
61
61
-
question: 'What technology does Hubble use?',
62
62
-
answer: 'alskdjflkaj flaj sflkj aslkdjf lakj',
63
63
-
},
64
64
-
];
65
3
---
66
4
67
5
<Base
68
6
description="Hubble: full-network atmosphere mirror (in development!)"
69
7
>
70
70
-
<!-- Hero -->
71
71
-
<section class="hero section--lg">
72
72
-
<div class="container hero__inner">
73
73
-
<h1 class="hero__title">
74
74
-
Hubble<br>
75
75
-
<span class="hero__titlemore">full-network atmosphere mirror</span>
76
76
-
</h1>
77
77
-
<p class="hero__subtitle">
78
78
-
In development!
79
79
-
</p>
80
80
-
81
81
-
<!-- decorative background blobby shit -->
82
82
-
<div class="hero__orb" aria-hidden="true">
83
83
-
<div class="orb orb--1"></div>
84
84
-
<div class="orb orb--2"></div>
85
85
-
</div>
86
86
-
</div>
8
8
+
<!-- helloooo -->
9
9
+
<section class="hello section-lg">
10
10
+
<h1 class="hello-title">
11
11
+
Hubble<br>
12
12
+
<span class="hello-titlemore">full-network atmosphere mirror</span>
13
13
+
</h1>
14
14
+
<p class="hello-subtitle">
15
15
+
In development!
16
16
+
</p>
87
17
</section>
88
18
89
89
-
<section class="section docs-content">
90
90
-
<div class="container docs-prose">
91
91
-
92
92
-
<div class="docs-section" id="what-is-hubble">
19
19
+
<!-- add more info-sections for hr-separated bits -->
20
20
+
<section class="section info-content">
21
21
+
<div class="container info-prose">
22
22
+
<div class="info-section">
93
23
<h2>Infrastructure for network resilience</h2>
94
94
-
<p>
95
95
-
Public data in atproto lives on Personal Data Servers (PDS), which can sometimes crash, have networking issues, or be unavailable for other reasons.
96
96
-
</p>
97
97
-
<p>
98
98
-
Hubble synchronizes an authenticated copy of every data repository in the atmosphere, so you can recover your content even if your PDS loses it.
99
99
-
</p>
100
100
-
<p>
101
101
-
We need to make some choices around language still. Like, is "archive" a good word to have on the website?
102
102
-
I think it decently conveys the intended meaning, but I'm worried it might suggest that Hubble keeps <em>historical</em> archives of past repository states, which is not what we're doing here!!
103
103
-
</p>
104
104
-
<p>
105
105
-
"Mirror" feels more immediate, but for some reason I don't like it that much. Maybe it's ok and I just need the right phrasing around it. "Sync" and "synchronizing" feel a little better but will need some care to avoid being confusing with "sync" in the sense of being tap-like or otherwise a firehose library.
24
24
+
<p>Public data in atproto lives on Personal Data Servers (PDS), which can sometimes crash, have networking issues, or be unavailable for other reasons.</p>
25
25
+
<p>Hubble synchronizes an authenticated copy of data repositories in the atmosphere, so you can recover your content even if your PDS loses it.
26
26
+
<strong>Authenticated Transfer</strong> (the AT in AT Protocol) ensures accurate replication on Hubble, and prevents Hubble itself from tampering.</p>
27
27
+
<p>Hubble is a new project from
28
28
+
<a href="https://microcosm.blue" style="color: var(--color-accent-microcosm)" target="_blank">microcosm</a> and
29
29
+
<a style="color: var(--color-accent-bsky)">Bluesky</a>, deploying by end of June.
106
30
</p>
107
31
</div>
108
108
-
109
109
-
<div class="docs-section" id="what-is-hubble">
110
110
-
<h2>Heading off concerns</h2>
111
111
-
<p>
112
112
-
Are there some obvious concerns that people are going to have about this, that should be communicated early on this page (like right here)?.
113
113
-
</p>
114
114
-
<p>
115
115
-
Probably some assurance about repo state being respected (deactivated, takendown, etc.) and also moderation (the service can moderate) and maybe some expectation-setting about access (designed personal access to your own repo).
116
116
-
</p>
117
117
-
</div>
118
118
-
119
119
-
<div class="docs-section" id="what-is-hubble">
120
120
-
<h2>Purpose</h2>
121
121
-
<p>
122
122
-
Probably goes even earlier here, but, why does this exist and what can you do with it?
123
123
-
</p>
124
124
-
</div>
125
125
-
</div>
126
126
-
</section>
127
127
-
128
128
-
<!-- About -->
129
129
-
<section class="section about">
130
130
-
<div class="container">
131
131
-
<div class="boxes__header">
132
132
-
<span class="eyebrow">Blah blah</span>
133
133
-
<h2>Section title thing</h2>
134
134
-
<p class="boxes__lead">
135
135
-
Some contextual info before the 2-grid boxes...
136
136
-
</p>
137
137
-
</div>
138
138
-
139
139
-
<div class="about__two-col">
140
140
-
<div class="card feature-card">
141
141
-
<h3>Public full-network archive service</h3>
142
142
-
<p>
143
143
-
Microcosm will run Hubble atmosphere-wide, synchronizing all records in the network.
144
144
-
</p>
145
145
-
</div>
146
146
-
<div class="card feature-card">
147
147
-
<h3>Self-host</h3>
148
148
-
<p>
149
149
-
Hubble is open-source! While synchronizing the entire atmosphere is resource-intensive, you can
150
150
-
</p>
151
151
-
</div>
152
152
-
</div>
153
153
-
</div>
154
154
-
</section>
155
155
-
156
156
-
<!-- Features -->
157
157
-
<section class="section features">
158
158
-
<div class="container">
159
159
-
<div class="boxes__header">
160
160
-
<span class="eyebrow">So much content</span>
161
161
-
<h2>Another part with another title</h2>
162
162
-
</div>
163
163
-
164
164
-
<ul class="features__grid" role="list">
165
165
-
{features.map(({ title, description }) => (
166
166
-
<li class="card feature-card">
167
167
-
<h3>{title}</h3>
168
168
-
<p>{description}</p>
169
169
-
</li>
170
170
-
))}
171
171
-
</ul>
172
172
-
</div>
173
173
-
</section>
174
174
-
175
175
-
<section class="section faq-section">
176
176
-
<div class="container faq-container">
177
177
-
<div class="boxes__header">
178
178
-
<h2>whishful thinking ("faq")</h2>
179
179
-
</div>
180
180
-
<dl class="faq-list">
181
181
-
{faqs.map(({ question, answer }) => (
182
182
-
<details class="faq-item">
183
183
-
<summary class="faq-question">
184
184
-
<span>{question}</span>
185
185
-
<span class="faq-chevron" aria-hidden="true">
186
186
-
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" width="16" height="16">
187
187
-
<path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
188
188
-
</svg>
189
189
-
</span>
190
190
-
</summary>
191
191
-
<div class="faq-answer">
192
192
-
<p>{answer}</p>
193
193
-
</div>
194
194
-
</details>
195
195
-
))}
196
196
-
</dl>
197
32
</div>
198
33
</section>
199
34
</Base>
200
35
201
36
<style>
202
202
-
/* hero thing */
203
203
-
.hero {
37
37
+
.hello {
204
38
text-align: center;
205
205
-
position: relative;
206
206
-
overflow: hidden;
207
39
}
208
208
-
209
209
-
.hero__inner {
210
210
-
position: relative;
211
211
-
z-index: 1;
212
212
-
}
213
213
-
214
214
-
.hero__title {
215
215
-
font-family: var(--font-heading);
216
216
-
font-weight: 900;
40
40
+
.hello-title {
217
41
margin-bottom: var(--space-3);
218
42
}
219
219
-
220
220
-
.hero__titlemore {
43
43
+
.hello-titlemore {
221
44
color: var(--color-accent3);
222
45
font-weight: 300;
223
46
}
224
224
-
225
225
-
.hero__subtitle {
47
47
+
.hello-subtitle {
226
48
font-size: clamp(1rem, 2vw, 1.25rem);
227
49
color: var(--color-muted);
228
50
max-width: 55ch;
229
51
margin-inline: auto;
230
230
-
margin-bottom: var(--space-8);
231
231
-
}
232
232
-
233
233
-
.hero__orb {
234
234
-
position: absolute;
235
235
-
inset: 0;
236
236
-
pointer-events: none;
237
237
-
z-index: 0;
238
238
-
}
239
239
-
240
240
-
.orb {
241
241
-
position: absolute;
242
242
-
border-radius: 50%;
243
243
-
opacity: 0.12;
244
244
-
}
245
245
-
246
246
-
.orb--1 {
247
247
-
width: 400px;
248
248
-
height: 400px;
249
249
-
background: radial-gradient(circle, var(--color-accent4), transparent 70%);
250
250
-
top: -120px;
251
251
-
right: -80px;
252
252
-
}
253
253
-
254
254
-
.orb--2 {
255
255
-
width: 300px;
256
256
-
height: 300px;
257
257
-
background: radial-gradient(circle, var(--color-accent2), transparent 70%);
258
258
-
bottom: -80px;
259
259
-
left: -60px;
260
52
}
261
53
262
262
-
/* about */
263
263
-
.boxes__header {
264
264
-
max-width: 56ch;
265
265
-
margin-bottom: var(--space-8);
266
266
-
}
267
267
-
268
268
-
.boxes__header h2 {
269
269
-
font-family: var(--font-heading);
270
270
-
font-weight: 400;
271
271
-
}
272
272
-
273
273
-
.boxes__lead {
274
274
-
font-size: 1.1rem;
275
275
-
color: var(--color-muted);
276
276
-
margin-top: var(--space-4);
277
277
-
}
278
278
-
279
279
-
.about__two-col {
280
280
-
display: grid;
281
281
-
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
282
282
-
gap: var(--space-6);
283
283
-
}
284
284
-
285
285
-
/* Prose content */
286
286
-
.docs-prose {
54
54
+
.info-prose {
287
55
max-width: 720px;
288
56
}
289
289
-
290
290
-
.docs-section {
57
57
+
.info-section {
291
58
margin-bottom: var(--space-12);
292
59
padding-bottom: var(--space-12);
293
60
border-bottom: 1px solid var(--color-border);
294
61
}
295
295
-
296
296
-
.docs-section:last-child {
62
62
+
.info-section:last-child {
297
63
border-bottom: none;
298
64
}
299
299
-
300
300
-
.docs-section h2 {
301
301
-
font-family: var(--font-heading);
302
302
-
font-weight: 400;
303
303
-
margin-bottom: var(--space-4);
304
304
-
}
305
305
-
306
306
-
.docs-section p {
65
65
+
.info-section p {
307
66
max-width: none;
308
308
-
}
309
309
-
310
310
-
311
311
-
.section__header {
312
312
-
margin-bottom: var(--space-8);
313
313
-
}
314
314
-
315
315
-
.features__grid {
316
316
-
display: grid;
317
317
-
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
318
318
-
gap: var(--space-6);
319
319
-
list-style: none;
320
320
-
}
321
321
-
322
322
-
.feature-card__icon {
323
323
-
font-size: 1.75rem;
324
324
-
margin-bottom: var(--space-4);
325
325
-
}
326
326
-
327
327
-
.feature-card h3 {
328
328
-
font-family: var(--font-heading);
329
329
-
font-weight: 200;
330
330
-
margin-bottom: var(--space-2);
331
331
-
}
332
332
-
333
333
-
.feature-card p {
334
334
-
color: var(--color-muted);
335
335
-
font-size: 0.95rem;
336
336
-
}
337
337
-
338
338
-
/* faq */
339
339
-
.faq-container {
340
340
-
max-width: 760px;
341
341
-
}
342
342
-
343
343
-
.faq-list {
344
344
-
display: flex;
345
345
-
flex-direction: column;
346
346
-
gap: var(--space-3);
347
347
-
margin-bottom: var(--space-8);
348
348
-
}
349
349
-
350
350
-
.faq-item {
351
351
-
background: var(--color-surface);
352
352
-
border: 1px solid var(--color-border);
353
353
-
border-radius: var(--radius-md);
354
354
-
transition: border-color var(--transition);
355
355
-
overflow: hidden;
356
356
-
}
357
357
-
358
358
-
.faq-item[open] {
359
359
-
border-color: var(--color-accent2);
360
360
-
}
361
361
-
362
362
-
.faq-question {
363
363
-
display: flex;
364
364
-
align-items: center;
365
365
-
justify-content: space-between;
366
366
-
gap: var(--space-4);
367
367
-
padding: var(--space-4) var(--space-6);
368
368
-
cursor: pointer;
369
369
-
list-style: none;
370
370
-
font-family: var(--font-heading);
371
371
-
font-weight: 400;
372
372
-
font-size: 1rem;
373
373
-
color: var(--color-text);
374
374
-
transition: color var(--transition);
375
375
-
user-select: none;
376
376
-
}
377
377
-
378
378
-
/* Remove default triangle in Safari/Firefox */
379
379
-
.faq-question::-webkit-details-marker { display: none; }
380
380
-
.faq-question::marker { display: none; }
381
381
-
382
382
-
.faq-question:hover {
383
383
-
color: var(--color-accent2);
384
384
-
}
385
385
-
386
386
-
.faq-chevron {
387
387
-
flex-shrink: 0;
388
388
-
color: var(--color-muted);
389
389
-
transition: transform var(--transition), color var(--transition);
390
390
-
}
391
391
-
392
392
-
.faq-item[open] .faq-chevron {
393
393
-
transform: rotate(180deg);
394
394
-
color: var(--color-accent2);
395
395
-
}
396
396
-
397
397
-
.faq-answer {
398
398
-
padding: 0 var(--space-6) var(--space-6);
399
399
-
color: var(--color-muted);
400
400
-
line-height: 1.7;
401
401
-
border-top: 1px solid var(--color-border);
402
402
-
}
403
403
-
404
404
-
.faq-answer p {
405
405
-
margin-top: var(--space-4);
406
406
-
max-width: none;
407
407
-
}
408
408
-
409
409
-
410
410
-
/* Shared */
411
411
-
.eyebrow {
412
412
-
display: block;
413
413
-
font-weight: 400;
414
414
-
font-size: 0.8rem;
415
415
-
letter-spacing: 0.1em;
416
416
-
text-transform: uppercase;
417
417
-
color: var(--color-accent2);
418
418
-
margin-bottom: var(--space-3);
419
67
}
420
68
</style>
+35
-89
src/styles/global.css
···
1
1
-
/* ============================================================
2
2
-
Fonts
3
3
-
============================================================ */
4
1
@import '@fontsource/londrina-solid/300.css';
5
2
@import '@fontsource/londrina-solid/400.css';
6
3
@import '@fontsource/londrina-solid/900.css';
7
4
8
8
-
/* ============================================================
9
9
-
Design tokens — Light mode (default)
10
10
-
============================================================ */
5
5
+
/* default/light mode */
11
6
:root {
12
12
-
/* Colors */
13
7
--color-bg: #F5F2EB;
14
8
--color-surface: #FFFFFF;
15
9
--color-border: #D9D4C7;
···
17
11
--color-muted: #5C6080;
18
12
--color-accent1: #E07A5F;
19
13
--color-accent2: #3AAFA9;
20
20
-
--color-accent3: #F2CC60;
14
14
+
--color-accent3: #E2BC50;
21
15
--color-accent4: #9B8EC4;
22
22
-
23
23
-
--color-star: hsla(94, 10%, 60%, 0.3);
16
16
+
--color-star: hsla(192, 81%, 44%, 0.667);
17
17
+
--color-accent-microcosm: #ce9df1;
18
18
+
--color-accent-bsky: rgb(0, 106, 255);
24
19
25
25
-
/* Typography */
26
20
--font-heading: 'Londrina Solid', system-ui, -apple-system, 'Segoe UI', sans-serif;
27
21
--font-body: system-ui, -apple-system, 'Segoe UI', sans-serif;
28
22
--font-mono: ui-monospace, 'Cascadia Code', Menlo, Consolas, monospace;
29
23
30
30
-
/* Spacing scale */
31
24
--space-1: 0.25rem;
32
25
--space-2: 0.5rem;
33
26
--space-3: 0.75rem;
···
38
31
--space-16: 4rem;
39
32
--space-24: 6rem;
40
33
41
41
-
/* Radius */
42
34
--radius-sm: 0.25rem;
43
35
--radius-md: 0.5rem;
44
36
--radius-lg: 1rem;
45
37
--radius-full: 9999px;
46
38
47
47
-
/* Transitions */
48
39
--transition: 200ms ease;
49
40
}
50
41
51
51
-
/* ============================================================
52
52
-
Dark mode — system preference
53
53
-
============================================================ */
42
42
+
/* dark mode overrides */
54
43
@media (prefers-color-scheme: dark) {
55
44
:root {
56
45
--color-bg: #0D0F1C;
···
62
51
--color-accent2: #4DBFB9;
63
52
--color-accent3: #F5D570;
64
53
--color-accent4: #B0A3D4;
54
54
+
--color-star: hsla(289, 80%, 72%, 0.4);
65
55
}
66
56
}
67
57
68
68
-
/* Dark mode — manual override via data attribute */
69
69
-
[data-theme="dark"] {
70
70
-
--color-bg: #0D0F1C;
71
71
-
--color-surface: #161929;
72
72
-
--color-border: #252840;
73
73
-
--color-text: #EAE6D9;
74
74
-
--color-muted: #8B8FA8;
75
75
-
--color-accent1: #E88B73;
76
76
-
--color-accent2: #4DBFB9;
77
77
-
--color-accent3: #F5D570;
78
78
-
--color-accent4: #B0A3D4;
79
79
-
}
80
80
-
81
81
-
[data-theme="light"] {
82
82
-
--color-bg: #F5F2EB;
83
83
-
--color-surface: #FFFFFF;
84
84
-
--color-border: #D9D4C7;
85
85
-
--color-text: #1C1F3A;
86
86
-
--color-muted: #5C6080;
87
87
-
--color-accent1: #E07A5F;
88
88
-
--color-accent2: #3AAFA9;
89
89
-
--color-accent3: #F2CC60;
90
90
-
--color-accent4: #9B8EC4;
91
91
-
}
92
92
-
93
93
-
/* ============================================================
94
94
-
Reset
95
95
-
============================================================ */
58
58
+
/* reset/basics */
96
59
*, *::before, *::after {
97
60
box-sizing: border-box;
98
61
margin: 0;
99
62
padding: 0;
100
63
}
101
101
-
102
64
html {
103
65
scroll-behavior: smooth;
104
66
-webkit-text-size-adjust: 100%;
105
67
}
106
106
-
107
68
body {
108
69
font-family: var(--font-body);
109
70
background-color: var(--color-bg);
110
71
color: var(--color-text);
111
72
line-height: 1.65;
112
73
min-height: 100vh;
113
113
-
transition: background-color var(--transition), color var(--transition);
114
74
}
115
115
-
116
75
img, svg, video {
117
76
display: block;
118
77
max-width: 100%;
119
78
}
120
120
-
121
79
button, input, select, textarea {
122
80
font: inherit;
123
81
}
124
82
125
125
-
/* ============================================================
126
126
-
Typography scale
127
127
-
============================================================ */
83
83
+
/* type */
128
84
h1, h2, h3, h4, h5, h6 {
129
129
-
line-height: 1.2;
130
85
color: var(--color-text);
86
86
+
line-height: 1.2;
87
87
+
font-family: var(--font-heading);
88
88
+
font-weight: 400;
89
89
+
margin-bottom: 0.5em;
131
90
}
132
132
-
133
133
-
h1 { font-size: clamp(2rem, 5vw, 3.5rem); font-weight: 800; }
134
134
-
h1.hero__title { font-size: clamp(3rem, 5vw, 4.5rem); font-weight: 900; }
91
91
+
h1 {
92
92
+
font-size: clamp(3rem, 5vw, 4.5rem);
93
93
+
font-weight: 900;
94
94
+
font-size: clamp(2rem, 5vw, 3.5rem); font-weight: 800;
95
95
+
}
135
96
h2 { font-size: clamp(1.5rem, 3vw, 2.25rem); }
136
97
h3 { font-size: clamp(1.15rem, 2vw, 1.5rem); }
137
98
h4 { font-size: 1.125rem; }
138
99
139
100
p {
140
101
max-width: 65ch;
141
141
-
margin-bottom: var(--space-4);
102
102
+
margin-bottom: 1em;
142
103
}
143
143
-
144
144
-
p:last-child {
145
145
-
margin-bottom: 0;
146
146
-
}
104
104
+
p:first-child { margin-top: 0 }
105
105
+
p:last-child { margin-bottom: 0 }
147
106
148
107
a {
149
108
color: var(--color-accent2);
150
109
text-decoration: underline;
151
110
text-decoration-thickness: 1px;
152
111
text-underline-offset: 2px;
153
153
-
transition: color var(--transition);
112
112
+
transition: color var(--transition), text-decoration-thickness var(--transition);
154
113
}
155
155
-
156
114
a:hover {
157
115
color: var(--color-accent1);
116
116
+
text-decoration-thickness: 2px;
158
117
}
159
118
160
119
code {
···
184
143
font-size: inherit;
185
144
}
186
145
187
187
-
/* ============================================================
188
188
-
Layout utilities
189
189
-
============================================================ */
146
146
+
/* layout */
190
147
.container {
191
148
width: 100%;
192
149
max-width: 1100px;
···
198
155
padding-block: var(--space-12);
199
156
}
200
157
201
201
-
.section--lg {
158
158
+
.section-lg {
202
159
padding-block: var(--space-24);
203
160
}
204
161
205
205
-
/* ============================================================
206
206
-
Star field canvas
207
207
-
============================================================ */
162
162
+
/* bg stars */
208
163
.starfield {
209
164
position: fixed;
210
165
inset: 0;
···
217
172
position: absolute;
218
173
width: 2px;
219
174
height: 2px;
220
220
-
border-radius: var(--radius-full);
175
175
+
border-radius: 2px;
221
176
background: var(--color-star);
222
177
animation: twinkle var(--star-duration, 3s) ease-in-out infinite;
223
178
animation-delay: var(--star-delay, 0s);
···
225
180
226
181
@keyframes twinkle {
227
182
0%, 100% { opacity: var(--star-opacity, 0.6); transform: scale(1); }
228
228
-
50% { opacity: 0.1; transform: scale(0.7); }
183
183
+
50% { opacity: 0.1; transform: scale(0.6); }
229
184
}
230
185
231
231
-
/* ============================================================
232
232
-
Main content above star field
233
233
-
============================================================ */
186
186
+
/* content */
234
187
.site-wrapper {
235
188
position: relative;
236
189
z-index: 1;
···
238
191
flex-direction: column;
239
192
min-height: 100vh;
240
193
}
241
241
-
242
194
.site-main {
243
195
flex: 1;
244
196
}
245
197
246
246
-
/* ============================================================
247
247
-
Buttons
248
248
-
============================================================ */
198
198
+
/* everyone's favourite: buttons */
249
199
.btn {
250
200
display: inline-flex;
251
201
align-items: center;
252
202
gap: var(--space-2);
253
203
padding: var(--space-3) var(--space-6);
254
204
border-radius: var(--radius-full);
255
255
-
font-family: var(--font-heading);
256
205
font-weight: 700;
257
206
font-size: 1rem;
258
207
text-decoration: none;
···
260
209
cursor: pointer;
261
210
transition: background-color var(--transition), color var(--transition), border-color var(--transition), transform var(--transition);
262
211
}
263
263
-
264
212
.btn:hover {
265
213
transform: translateY(-1px);
266
214
}
267
215
268
268
-
.btn--primary {
216
216
+
.btn.primary {
269
217
background: var(--color-accent1);
270
218
color: #fff;
271
219
border-color: var(--color-accent1);
272
220
}
273
221
274
274
-
.btn--primary:hover {
222
222
+
.btn.primary:hover {
275
223
background: transparent;
276
224
color: var(--color-accent1);
277
225
}
278
226
279
279
-
.btn--outline {
227
227
+
.btn.outline {
280
228
background: transparent;
281
229
color: var(--color-accent2);
282
230
border-color: var(--color-accent2);
283
231
}
284
232
285
285
-
.btn--outline:hover {
233
233
+
.btn.outline:hover {
286
234
background: var(--color-accent2);
287
235
color: #fff;
288
236
}
289
237
290
290
-
/* ============================================================
291
291
-
Cards
292
292
-
============================================================ */
238
238
+
/* cards, why not */
293
239
.card {
294
240
background: var(--color-surface);
295
241
border: 1px solid var(--color-border);