···
121
121
} as CSSProperties
122
122
}
123
123
>
124
124
+
<ProfileMasthead />
125
125
+
124
126
<MasonryGrid>
125
127
{profile ? (
126
128
<BlueskyProfile
···
169
171
const [query, setQuery] = useState("");
170
172
const { actors, isSearching } = useActorSearch(query);
171
173
const { isOpen, openPanel, searchAreaRef } = useSearchPanel();
172
172
-
const readingLabel = getSigilReadingLabel(query, actors);
173
174
174
175
function visitPresence(event: FormEvent<HTMLFormElement>) {
175
176
event.preventDefault();
···
182
183
183
184
return (
184
185
<main className="home-page min-h-screen p-4 sm:p-8">
185
185
-
<div className="home-content mx-auto max-w-2xl">
186
186
-
<p className="home-index-label">haunt.at // an atmosphere compendium</p>
187
187
-
186
186
+
<div className="mx-auto max-w-2xl">
188
187
<section className="home-invocation" aria-labelledby="home-title">
189
189
-
<div className="home-invocation-sigil">
190
190
-
<HauntSigil seed={query.trim() || "haunt.at"} />
191
191
-
<p>{readingLabel}</p>
192
192
-
</div>
193
193
-
194
188
<h1 id="home-title" className="sr-only">
195
189
Haunt compendium
196
190
</h1>
191
191
+
192
192
+
<div className="home-invocation-sigil">
193
193
+
<HauntSigil seed={query.trim() || "haunt.at"} />
194
194
+
</div>
197
195
198
196
<div
199
197
ref={searchAreaRef}
···
240
238
);
241
239
}
242
240
241
241
+
function ProfileMasthead() {
242
242
+
return (
243
243
+
<a className="profile-home-link mx-auto block max-w-6xl" href="/">
244
244
+
haunt.at
245
245
+
</a>
246
246
+
);
247
247
+
}
248
248
+
243
249
function useSearchPanel() {
244
250
const searchAreaRef = useRef<HTMLDivElement>(null);
245
251
const [isOpen, setIsOpen] = useState(false);
···
259
265
}, []);
260
266
261
267
return { isOpen, openPanel: () => setIsOpen(true), searchAreaRef };
262
262
-
}
263
263
-
264
264
-
function getSigilReadingLabel(
265
265
-
query: string,
266
266
-
actors: BlueskyActorSearchResult[],
267
267
-
): string {
268
268
-
const identifier = query.trim().replace(/^@/, "");
269
269
-
const normalizedIdentifier = identifier.toLowerCase();
270
270
-
const matchingActor = actors.find(
271
271
-
(actor) =>
272
272
-
actor.handle.toLowerCase() === normalizedIdentifier ||
273
273
-
actor.did.toLowerCase() === normalizedIdentifier,
274
274
-
);
275
275
-
276
276
-
return matchingActor ? `reading: ${matchingActor.handle}` : "unresolved";
277
268
}
278
269
279
270
type HauntSigilProps = {
···
25
25
background-size: 108% 108%;
26
26
}
27
27
28
28
+
.profile-home-link,
29
29
+
.state-masthead {
30
30
+
color: color-mix(in srgb, currentColor 50%, transparent);
31
31
+
font-size: 0.75rem;
32
32
+
letter-spacing: 0.12em;
33
33
+
margin-bottom: 1.5rem;
34
34
+
text-transform: uppercase;
35
35
+
}
36
36
+
37
37
+
.profile-home-link:hover,
38
38
+
.profile-home-link:focus-visible {
39
39
+
color: var(--profile-accent);
40
40
+
}
41
41
+
42
42
+
.profile-home-link:focus-visible {
43
43
+
outline: 2px solid var(--profile-accent);
44
44
+
outline-offset: 3px;
45
45
+
}
46
46
+
28
47
.home-page,
29
48
.state-page {
30
49
background-color: var(--home-background);
···
34
53
color: var(--home-text);
35
54
}
36
55
37
37
-
.home-content,
38
56
.state-content {
39
57
padding-top: clamp(1.5rem, 4vw, 3rem);
40
58
}
41
59
42
42
-
.home-index-label,
43
43
-
.state-masthead {
44
44
-
color: color-mix(in srgb, currentColor 50%, transparent);
45
45
-
font-size: 0.75rem;
46
46
-
letter-spacing: 0.12em;
47
47
-
text-transform: uppercase;
48
48
-
}
49
49
-
50
60
.state-masthead:focus-visible,
51
61
.state-action:focus-visible {
52
62
outline: 2px solid var(--home-highlight);
···
71
81
width: var(--home-sigil-size);
72
82
}
73
83
74
74
-
.home-invocation-sigil p,
75
84
.state-label {
76
85
color: color-mix(in srgb, currentColor 48%, transparent);
77
86
font-size: 0.6875rem;
78
87
letter-spacing: 0.08em;
79
79
-
text-transform: uppercase;
80
80
-
}
81
81
-
82
82
-
.state-label {
83
88
margin-top: 0.875rem;
84
84
-
}
85
85
-
86
86
-
.home-invocation-sigil p {
87
87
-
line-height: 1.3;
88
88
-
white-space: nowrap;
89
89
+
text-transform: uppercase;
89
90
}
90
91
91
92
.state-reading h1 {
···
126
127
127
128
.home-invocation-sigil {
128
129
display: grid;
129
129
-
gap: 0.35rem;
130
130
+
gap: 0.5rem;
130
131
justify-items: center;
131
132
}
132
133
···
405
406
grid-template-columns: 1fr;
406
407
}
407
408
}
409
409
+
408
410
409
411
@keyframes atmosphere-drift {
410
412
to {