tangled
alpha
login
or
join now
pds.ls
/
pdsls
399
fork
atom
atmosphere explorer
pds.ls
tool
typescript
atproto
399
fork
atom
overview
issues
1
pulls
pipelines
new homepage
handle.invalid
2 weeks ago
687eb3ea
0658d7cc
verified
This commit was signed with the committer's
known signature
.
handle.invalid
SSH Key Fingerprint:
SHA256:mBrT4x0JdzLpbVR95g1hjI1aaErfC02kmLRkPXwsYCk=
+193
-231
4 changed files
expand all
collapse all
unified
split
src
components
dropdown.tsx
search.tsx
layout.tsx
views
home.tsx
+9
-3
src/components/dropdown.tsx
···
37
class="flex items-center gap-2 rounded-md p-1.5 whitespace-nowrap hover:bg-neutral-200/50 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
38
>
39
<Show when={props.icon}>
40
-
<span class={"iconify shrink-0 " + props.icon}></span>
0
0
41
</Show>
42
<span class="whitespace-nowrap">{props.label}</span>
43
</button>
···
64
>
65
<div class="flex items-center gap-2">
66
<Show when={props.icon}>
67
-
<span class={"iconify shrink-0 " + props.icon}></span>
0
0
68
</Show>
69
<span class="whitespace-nowrap">{props.label}</span>
70
</div>
···
92
class="flex items-center gap-2 rounded-md p-1.5 whitespace-nowrap hover:bg-neutral-200/50 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
93
>
94
<Show when={props.icon}>
95
-
<span class={"iconify shrink-0 " + props.icon}></span>
0
0
96
</Show>
97
<span class="whitespace-nowrap">{props.label}</span>
98
</button>
···
37
class="flex items-center gap-2 rounded-md p-1.5 whitespace-nowrap hover:bg-neutral-200/50 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
38
>
39
<Show when={props.icon}>
40
+
<span
41
+
class={"iconify shrink-0 text-neutral-500 dark:text-neutral-400 " + props.icon}
42
+
></span>
43
</Show>
44
<span class="whitespace-nowrap">{props.label}</span>
45
</button>
···
66
>
67
<div class="flex items-center gap-2">
68
<Show when={props.icon}>
69
+
<span
70
+
class={"iconify shrink-0 text-neutral-500 dark:text-neutral-400 " + props.icon}
71
+
></span>
72
</Show>
73
<span class="whitespace-nowrap">{props.label}</span>
74
</div>
···
96
class="flex items-center gap-2 rounded-md p-1.5 whitespace-nowrap hover:bg-neutral-200/50 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
97
>
98
<Show when={props.icon}>
99
+
<span
100
+
class={"iconify shrink-0 text-neutral-500 dark:text-neutral-400 " + props.icon}
101
+
></span>
102
</Show>
103
<span class="whitespace-nowrap">{props.label}</span>
104
</button>
+59
-22
src/components/search.tsx
···
50
51
export const [showSearch, setShowSearch] = createSignal(false);
52
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
53
const SEARCH_PREFIXES: { prefix: string; description: string }[] = [
54
{ prefix: "@", description: "example.com" },
55
{ prefix: "did:", description: "web:example.com" },
···
343
/>
344
</div>
345
346
-
<Show when={getRecentSuggestions().length > 0 || search()?.length}>
0
0
0
0
0
0
347
<div
348
-
class={`flex w-full flex-col overflow-hidden border-t border-neutral-200 dark:border-neutral-700 ${input() ? "rounded-b-md" : ""}`}
349
onMouseDown={(e) => e.preventDefault()}
350
>
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
351
{/* Recent searches */}
352
<Show when={getRecentSuggestions().length > 0}>
353
<div class="mt-2 mb-1 flex items-center justify-between px-3">
···
446
);
447
}}
448
</For>
449
-
</div>
450
-
</Show>
451
-
<Show when={!input()}>
452
-
<div class="flex flex-col gap-1 border-t border-neutral-200 px-3 py-2 text-xs text-neutral-500 dark:border-neutral-700 dark:text-neutral-400">
453
-
<div class="flex flex-wrap gap-1.5">
454
-
<div>
455
-
@<span class="text-neutral-400 dark:text-neutral-500">retr0.id</span>
456
-
</div>
457
-
<div>did:</div>
458
-
<div>at://</div>
459
-
<div>
460
-
lex:
461
-
<span class="text-neutral-400 dark:text-neutral-500">app.bsky.feed.post</span>
462
-
</div>
463
-
<div>
464
-
pds:
465
-
<span class="text-neutral-400 dark:text-neutral-500">tngl.sh</span>
466
-
</div>
467
-
</div>
468
-
<span>Bluesky, Tangled, Pinksea, Popfeed, or Blento links</span>
469
</div>
470
</Show>
471
</form>
···
50
51
export const [showSearch, setShowSearch] = createSignal(false);
52
53
+
const EXAMPLES: (RecentSearch & { prefix: string })[] = [
54
+
{
55
+
path: "/at://did:plc:vwzwgnygau7ed7b7wt5ux7y2",
56
+
label: "retr0.id",
57
+
type: "handle",
58
+
prefix: "@",
59
+
},
60
+
{
61
+
path: "/at://did:plc:ia76kvnndjutgedggx2ibrem/app.bsky.actor.profile/self",
62
+
label: "mary.my.id/app.bsky.actor.profile/self",
63
+
type: "at-uri",
64
+
prefix: "at://",
65
+
},
66
+
{ path: "/npmx.social", label: "npmx.social", type: "pds", prefix: "pds:" },
67
+
{
68
+
path: "/at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.post#schema",
69
+
label: "app.bsky.feed.post",
70
+
type: "lexicon",
71
+
prefix: "lex:",
72
+
},
73
+
{
74
+
path: "/at://did:plc:oisofpd7lj26yvgiivf3lxsi/app.bsky.feed.post/3mfflamxxvk2t",
75
+
label: "bsky.app/profile/hailey.at/post/3mfflamxxvk2t",
76
+
type: "at-uri",
77
+
prefix: "https://",
78
+
},
79
+
];
80
+
81
const SEARCH_PREFIXES: { prefix: string; description: string }[] = [
82
{ prefix: "@", description: "example.com" },
83
{ prefix: "did:", description: "web:example.com" },
···
371
/>
372
</div>
373
374
+
<Show
375
+
when={
376
+
getRecentSuggestions().length > 0 ||
377
+
search()?.length ||
378
+
(!input() && recentSearches().length === 0)
379
+
}
380
+
>
381
<div
382
+
class="flex w-full flex-col overflow-hidden rounded-b-md border-t border-neutral-200 dark:border-neutral-700"
383
onMouseDown={(e) => e.preventDefault()}
384
>
385
+
{/* Suggestions (shown when no recents and no input) */}
386
+
<Show when={!input() && recentSearches().length === 0}>
387
+
<div class="mt-2 mb-1 flex px-3">
388
+
<span class="text-xs font-medium text-neutral-500 dark:text-neutral-400">
389
+
Examples
390
+
</span>
391
+
</div>
392
+
<For each={EXAMPLES}>
393
+
{(example) => (
394
+
<A
395
+
href={example.path}
396
+
class="dark:hover:bg-dark-100 flex items-center gap-2 px-3 py-2 text-sm hover:bg-neutral-100"
397
+
onClick={() => setShowSearch(false)}
398
+
>
399
+
<span class="truncate">
400
+
<span class="text-neutral-500 dark:text-neutral-400">{example.prefix}</span>
401
+
{example.label}
402
+
</span>
403
+
</A>
404
+
)}
405
+
</For>
406
+
</Show>
407
+
408
{/* Recent searches */}
409
<Show when={getRecentSuggestions().length > 0}>
410
<div class="mt-2 mb-1 flex items-center justify-between px-3">
···
503
);
504
}}
505
</For>
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
506
</div>
507
</Show>
508
</form>
+1
-1
src/layout.tsx
···
159
<DropdownMenu icon="lucide--menu text-lg" buttonClass="rounded-md p-1.5">
160
<NavMenu href="/jetstream" label="Jetstream" icon="lucide--radio-tower" />
161
<NavMenu href="/firehose" label="Firehose" icon="lucide--rss" />
162
-
<NavMenu href="/spacedust" label="Spacedust" icon="lucide--orbit" />
163
<MenuSeparator />
164
<NavMenu href="/labels" label="Labels" icon="lucide--tag" />
165
<NavMenu href="/car" label="Archive tools" icon="lucide--folder-archive" />
···
159
<DropdownMenu icon="lucide--menu text-lg" buttonClass="rounded-md p-1.5">
160
<NavMenu href="/jetstream" label="Jetstream" icon="lucide--radio-tower" />
161
<NavMenu href="/firehose" label="Firehose" icon="lucide--rss" />
162
+
<NavMenu href="/spacedust" label="Spacedust" icon="lucide--sparkles" />
163
<MenuSeparator />
164
<NavMenu href="/labels" label="Labels" icon="lucide--tag" />
165
<NavMenu href="/car" label="Archive tools" icon="lucide--folder-archive" />
+124
-205
src/views/home.tsx
···
1
import { A } from "@solidjs/router";
2
-
import { createSignal, For, JSX, onCleanup, onMount } from "solid-js";
3
import { setOpenManager, setShowAddAccount } from "../auth/state";
4
-
import { Button } from "../components/button";
5
-
import { Favicon } from "../components/favicon";
6
-
import { JSONValue } from "../components/json";
7
-
import { SearchButton } from "../components/search";
8
9
-
const SLIDES = ["Repository", "Record", "PDS"] as const;
0
10
11
-
const slideLinks = [
12
-
"/at://did:plc:vwzwgnygau7ed7b7wt5ux7y2",
13
-
"/at://did:plc:ia76kvnndjutgedggx2ibrem/app.bsky.feed.post/3kenlltlvus2u",
14
-
"/npmx.social",
15
-
] as const;
16
17
-
const exampleRecord = {
18
-
text: "ma'am do you know where the petard is, i'd like to hoist myself with it",
19
-
$type: "app.bsky.feed.post",
20
-
langs: ["en"],
21
-
createdAt: "2023-11-20T21:44:21.000Z",
0
22
};
23
24
-
const exampleCollections = [
25
-
{ authority: "app.bsky", nsids: ["actor.profile", "feed.post", "feed.like", "graph.follow"] },
26
-
{ authority: "sh.tangled", nsids: ["actor.profile", "repo.pull"] },
27
-
{ authority: "place.stream", nsids: ["chat.message"] },
28
-
];
29
30
-
const exampleRepos = [
31
-
"did:plc:ty2jdjtqqq4jn7kk7p3mpwae",
32
-
"did:plc:byfvayavc7z2ldyu6bu5myz2",
33
-
"did:plc:n34gdj7o3o6ktuxp5qfbgllu",
34
-
"did:plc:vh7y4mqklsu2uui5tlwl42dy",
35
-
"did:plc:uz76j2yr2ps7apdxtlgqljwk",
36
-
];
0
0
0
0
0
0
0
37
38
-
const ExplorerShowcase = () => {
39
-
const [slide, setSlide] = createSignal(0);
0
0
0
0
0
0
0
0
0
0
0
0
0
0
40
41
-
onMount(() => {
42
-
const id = setInterval(() => setSlide((s) => (s + 1) % SLIDES.length), 5000);
43
-
onCleanup(() => clearInterval(id));
44
-
});
45
-
46
-
return (
47
-
<div class="flex flex-col gap-1.5">
48
-
<A
49
-
href={slideLinks[slide()]}
50
-
class="relative block h-42 overflow-hidden rounded-lg border border-neutral-200 bg-neutral-50 transition-colors hover:border-neutral-300 dark:border-neutral-700 dark:bg-neutral-800 dark:hover:border-neutral-600"
51
-
>
52
-
{/* Collections slide */}
53
-
<div
54
-
class="pointer-events-none absolute inset-0 flex flex-col gap-1 overflow-hidden px-3 py-2 text-sm wrap-anywhere transition-opacity duration-700"
55
-
classList={{ "opacity-0": slide() !== 0 }}
56
-
>
57
-
<For each={exampleCollections}>
58
-
{({ authority, nsids }) => (
59
-
<div class="flex items-start gap-2">
60
-
<Favicon authority={authority} />
61
-
<div class="flex flex-col">
62
-
<For each={nsids}>
63
-
{(nsid) => (
64
-
<span>
65
-
<span class="text-neutral-500 dark:text-neutral-400">{authority}.</span>
66
-
<span>{nsid}</span>
67
-
</span>
68
-
)}
69
-
</For>
70
-
</div>
71
-
</div>
72
-
)}
73
-
</For>
74
-
</div>
75
-
76
-
{/* Record slide */}
77
-
<div
78
-
class="pointer-events-none absolute inset-0 overflow-hidden px-3 py-2 font-mono text-xs wrap-anywhere whitespace-pre-wrap transition-opacity duration-700 sm:text-sm"
79
-
classList={{ "opacity-0": slide() !== 1 }}
80
-
>
81
-
<JSONValue data={exampleRecord as any} repo="did:plc:ia76kvnndjutgedggx2ibrem" />
82
-
</div>
83
-
84
-
{/* Repos slide */}
85
-
<div
86
-
class="pointer-events-none absolute inset-0 overflow-hidden py-0.5 transition-opacity duration-700"
87
-
classList={{ "opacity-0": slide() !== 2 }}
88
-
>
89
-
<For each={exampleRepos}>
90
-
{(did) => (
91
-
<div class="flex min-w-0 items-center gap-2 p-1.5 font-mono text-sm">
92
-
<span class="flex shrink-0 items-center text-neutral-400 dark:text-neutral-500">
93
-
<span class="iconify lucide--chevron-right" />
94
-
</span>
95
-
<span class="truncate text-blue-500 dark:text-blue-400">{did}</span>
96
-
</div>
97
-
)}
98
-
</For>
99
-
</div>
100
-
</A>
101
-
102
-
{/* Slide indicator */}
103
-
<div class="flex items-center justify-between px-0.5">
104
-
<span class="text-xs text-neutral-400 dark:text-neutral-500">{SLIDES[slide()]}</span>
105
-
<div class="flex gap-1">
106
-
<For each={SLIDES}>
107
-
{(_, i) => (
108
-
<button
109
-
onClick={() => setSlide(i())}
110
-
class="h-1 rounded-full transition-all duration-300"
111
-
classList={{
112
-
"w-4 bg-neutral-400 dark:bg-neutral-500": slide() === i(),
113
-
"w-1.5 bg-neutral-300 dark:bg-neutral-600": slide() !== i(),
114
-
}}
115
-
/>
116
-
)}
117
-
</For>
118
-
</div>
119
-
</div>
120
-
</div>
121
-
);
122
-
};
123
124
export const Home = () => {
125
const FooterLink = (props: {
···
138
);
139
140
return (
141
-
<div class="flex w-full flex-col gap-5 px-2 wrap-break-word">
142
{/* Welcome Section */}
143
-
<div class="flex flex-col gap-4">
144
-
<div class="flex flex-col gap-1">
145
-
<h1 class="text-lg font-medium">Atmosphere Explorer</h1>
146
-
<div class="text-sm text-neutral-600 dark:text-neutral-300">
147
-
<p>
148
-
Browse the public data on the{" "}
149
-
<a
150
-
href="https://atproto.com"
151
-
target="_blank"
152
-
class="underline decoration-neutral-400 transition-colors hover:text-blue-500 hover:decoration-blue-500 dark:decoration-neutral-500 dark:hover:text-blue-400"
153
-
>
154
-
AT Protocol
155
-
</a>
156
-
</p>
157
-
</div>
158
</div>
159
-
160
-
<ExplorerShowcase />
161
162
-
<div class="flex items-center gap-1.5 text-xs text-neutral-500 dark:text-neutral-400">
163
-
<SearchButton />
164
-
<span>to find any account</span>
165
-
</div>
166
-
<div class="flex items-center gap-1.5 text-xs text-neutral-500 dark:text-neutral-400">
167
-
<Button
0
0
0
0
168
onClick={() => {
169
setOpenManager(true);
170
setShowAddAccount(true);
171
}}
172
-
>
173
-
<span class="iconify lucide--user-round"></span>
174
-
Sign in
175
-
</Button>
176
-
<span>to manage records</span>
177
</div>
178
-
</div>
179
180
-
<div class="flex flex-col gap-4 text-sm">
181
-
<div class="flex flex-col gap-2">
182
-
<A
183
href="/jetstream"
184
-
class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400"
185
-
>
186
-
<div class="iconify lucide--radio-tower" />
187
-
<span class="underline decoration-transparent group-hover:decoration-current">
188
-
Jetstream
189
-
</span>
190
-
<div />
191
-
<span class="text-xs text-neutral-500 dark:text-neutral-400">
192
-
Event stream with filtering
193
-
</span>
194
-
</A>
195
-
<A
196
href="/firehose"
197
-
class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400"
198
-
>
199
-
<div class="iconify lucide--rss" />
200
-
<span class="underline decoration-transparent group-hover:decoration-current">
201
-
Firehose
202
-
</span>
203
-
<div />
204
-
<span class="text-xs text-neutral-500 dark:text-neutral-400">
205
-
Raw relay event stream
206
-
</span>
207
-
</A>
208
-
<A
209
href="/spacedust"
210
-
class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400"
211
-
>
212
-
<div class="iconify lucide--orbit" />
213
-
<span class="underline decoration-transparent group-hover:decoration-current">
214
-
Spacedust
215
-
</span>
216
-
<div />
217
-
<span class="text-xs text-neutral-500 dark:text-neutral-400">
218
-
Interaction links stream
219
-
</span>
220
-
</A>
221
</div>
222
223
-
<div class="flex flex-col gap-2">
224
-
<A
225
href="/labels"
226
-
class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400"
227
-
>
228
-
<div class="iconify lucide--tag" />
229
-
<span class="underline decoration-transparent group-hover:decoration-current">
230
-
Labels
231
-
</span>
232
-
<div />
233
-
<span class="text-xs text-neutral-500 dark:text-neutral-400">
234
-
Query labeler services
235
-
</span>
236
-
</A>
237
-
<A
238
href="/car"
239
-
class="group grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5 text-neutral-700 transition-colors hover:text-blue-500 dark:text-neutral-300 dark:hover:text-blue-400"
240
-
>
241
-
<div class="iconify lucide--folder-archive" />
242
-
<span class="underline decoration-transparent group-hover:decoration-current">
243
-
Archive
244
-
</span>
245
-
<div />
246
-
<span class="text-xs text-neutral-500 dark:text-neutral-400">
247
-
Explore and unpack CAR files
248
-
</span>
249
-
</A>
250
</div>
251
</div>
252
···
1
import { A } from "@solidjs/router";
2
+
import { JSX } from "solid-js";
3
import { setOpenManager, setShowAddAccount } from "../auth/state";
4
+
import { setShowSearch } from "../components/search";
0
0
0
5
6
+
const baseCardClass =
7
+
"group flex flex-col gap-1 rounded-lg border border-neutral-200 bg-neutral-50 p-3 text-neutral-700 transition-colors dark:border-neutral-700 dark:bg-neutral-800/50 dark:text-neutral-300 hover:bg-neutral-50/50 dark:hover:bg-neutral-800";
8
9
+
const accentCard = {
10
+
blue: `${baseCardClass} hover:border-blue-500 dark:hover:border-blue-400`,
11
+
orange: `${baseCardClass} hover:border-red-500 dark:hover:border-red-400`,
12
+
violet: `${baseCardClass} hover:border-emerald-500 dark:hover:border-emerald-400`,
13
+
};
14
15
+
const accentIcon = {
16
+
blue: "text-neutral-400 dark:text-neutral-500 group-hover:text-blue-500 dark:group-hover:text-blue-400",
17
+
orange:
18
+
"text-neutral-400 dark:text-neutral-500 group-hover:text-red-500 dark:group-hover:text-red-400",
19
+
violet:
20
+
"text-neutral-400 dark:text-neutral-500 group-hover:text-emerald-500 dark:group-hover:text-emerald-400",
21
};
22
23
+
type Accent = "blue" | "orange" | "violet";
0
0
0
0
24
25
+
const CardContent = (props: {
26
+
icon: string;
27
+
title: string;
28
+
description: string;
29
+
accent: Accent;
30
+
}) => (
31
+
<>
32
+
<span class="flex items-center gap-1.5 text-xs sm:text-sm">
33
+
<span class={`${props.icon} iconify shrink-0 ${accentIcon[props.accent]}`} />
34
+
<span class="font-medium">{props.title}</span>
35
+
</span>
36
+
<span class="text-xs text-neutral-500 dark:text-neutral-400">{props.description}</span>
37
+
</>
38
+
);
39
40
+
const ButtonCard = (props: {
41
+
onClick: () => void;
42
+
icon: string;
43
+
title: string;
44
+
description: string;
45
+
accent: Accent;
46
+
}) => (
47
+
<button onClick={props.onClick} class={`${accentCard[props.accent]} text-left`}>
48
+
<CardContent
49
+
icon={props.icon}
50
+
title={props.title}
51
+
description={props.description}
52
+
accent={props.accent}
53
+
/>
54
+
</button>
55
+
);
56
57
+
const LinkCard = (props: {
58
+
href: string;
59
+
icon: string;
60
+
title: string;
61
+
description: string;
62
+
accent: Accent;
63
+
}) => (
64
+
<A href={props.href} class={accentCard[props.accent]}>
65
+
<CardContent
66
+
icon={props.icon}
67
+
title={props.title}
68
+
description={props.description}
69
+
accent={props.accent}
70
+
/>
71
+
</A>
72
+
);
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
73
74
export const Home = () => {
75
const FooterLink = (props: {
···
88
);
89
90
return (
91
+
<div class="flex w-full flex-col gap-6 px-2 wrap-break-word">
92
{/* Welcome Section */}
93
+
<div class="flex flex-col gap-1">
94
+
<h1 class="text-lg font-medium">Atmosphere Explorer</h1>
95
+
<div class="text-sm text-neutral-600 dark:text-neutral-300/80">
96
+
<p>
97
+
Browse the public data on the{" "}
98
+
<a
99
+
href="https://atproto.com"
100
+
target="_blank"
101
+
class="underline decoration-neutral-400 transition-colors hover:text-blue-500 hover:decoration-blue-500 dark:decoration-neutral-500 dark:hover:text-blue-400"
102
+
>
103
+
AT Protocol
104
+
</a>
105
+
</p>
0
0
106
</div>
107
+
</div>
0
108
109
+
<div class="flex flex-col gap-3 text-sm">
110
+
<div class="grid grid-cols-2 gap-2 text-sm">
111
+
<ButtonCard
112
+
onClick={() => setShowSearch(true)}
113
+
icon="lucide--search"
114
+
title="Search"
115
+
description="Find any user or record"
116
+
accent="blue"
117
+
/>
118
+
<ButtonCard
119
onClick={() => {
120
setOpenManager(true);
121
setShowAddAccount(true);
122
}}
123
+
icon="lucide--user-round"
124
+
title="Sign in"
125
+
description="Manage records"
126
+
accent="blue"
127
+
/>
128
</div>
0
129
130
+
<div class="grid grid-cols-3 gap-2">
131
+
<LinkCard
0
132
href="/jetstream"
133
+
icon="lucide--radio-tower"
134
+
title="Jetstream"
135
+
description="Simplified stream"
136
+
accent="orange"
137
+
/>
138
+
<LinkCard
0
0
0
0
0
0
139
href="/firehose"
140
+
icon="lucide--rss"
141
+
title="Firehose"
142
+
description="Raw event stream"
143
+
accent="orange"
144
+
/>
145
+
<LinkCard
0
0
0
0
0
0
146
href="/spacedust"
147
+
icon="lucide--sparkles"
148
+
title="Spacedust"
149
+
description="Backlinks stream"
150
+
accent="orange"
151
+
/>
0
0
0
0
0
0
152
</div>
153
154
+
<div class="grid grid-cols-2 gap-2">
155
+
<LinkCard
156
href="/labels"
157
+
icon="lucide--tag"
158
+
title="Labels"
159
+
description="Query labeler services"
160
+
accent="violet"
161
+
/>
162
+
<LinkCard
0
0
0
0
0
0
163
href="/car"
164
+
icon="lucide--folder-archive"
165
+
title="Archive"
166
+
description="Explore CAR files"
167
+
accent="violet"
168
+
/>
0
0
0
0
0
0
169
</div>
170
</div>
171