mobile bluesky app made with flutter
lazurite.stormlightlabs.org/
mobile
bluesky
flutter
1<!doctype html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
6 <title>Dev Tools - Lazurite</title>
7 <link rel="preconnect" href="https://fonts.googleapis.com" />
8 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9 <link
10 href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap"
11 rel="stylesheet" />
12 <link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet">
13 <link rel="stylesheet" href="styles.css" />
14 <style>
15 .devtools-container {
16 padding-bottom: 88px;
17 }
18
19 .devtools-search {
20 padding: 16px;
21 border-bottom: 1px solid var(--border);
22 }
23
24 .devtools-search-row {
25 display: flex;
26 gap: 8px;
27 }
28
29 .devtools-search-row .input {
30 flex: 1;
31 font-family: "JetBrains Mono", monospace;
32 font-size: 13px;
33 }
34
35 .devtools-search-btn {
36 padding: 12px 16px;
37 border-radius: 8px;
38 border: none;
39 background-color: var(--accent-primary);
40 color: white;
41 font-weight: 600;
42 font-size: 14px;
43 cursor: pointer;
44 transition: background-color 0.2s ease;
45 white-space: nowrap;
46 }
47
48 .devtools-search-btn:hover {
49 background-color: var(--accent-primary-hover);
50 }
51
52 .devtools-tabs {
53 display: flex;
54 border-bottom: 1px solid var(--border);
55 background-color: var(--bg);
56 }
57
58 .devtools-tab {
59 flex: 1;
60 padding: 12px;
61 text-align: center;
62 font-weight: 600;
63 font-size: 13px;
64 color: var(--text-secondary);
65 cursor: pointer;
66 border-bottom: 2px solid transparent;
67 transition: all 0.2s ease;
68 background: none;
69 border-top: none;
70 border-left: none;
71 border-right: none;
72 }
73
74 .devtools-tab:hover {
75 background-color: var(--surface);
76 color: var(--text-primary);
77 }
78
79 .devtools-tab.active {
80 color: var(--text-primary);
81 border-bottom-color: var(--accent-primary);
82 }
83
84 /* Repo Overview */
85 .repo-header {
86 padding: 16px;
87 border-bottom: 1px solid var(--border);
88 background-color: var(--surface);
89 }
90
91 .repo-identity {
92 display: flex;
93 align-items: center;
94 gap: 12px;
95 margin-bottom: 12px;
96 }
97
98 .repo-avatar {
99 width: 40px;
100 height: 40px;
101 border-radius: 50%;
102 background-color: var(--surface-variant);
103 display: flex;
104 align-items: center;
105 justify-content: center;
106 font-weight: 600;
107 font-size: 14px;
108 color: var(--text-secondary);
109 flex-shrink: 0;
110 }
111
112 .repo-names {
113 flex: 1;
114 min-width: 0;
115 }
116
117 .repo-handle {
118 font-weight: 600;
119 font-size: 15px;
120 color: var(--text-primary);
121 }
122
123 .repo-did {
124 font-family: "JetBrains Mono", monospace;
125 font-size: 11px;
126 color: var(--text-muted);
127 overflow: hidden;
128 text-overflow: ellipsis;
129 white-space: nowrap;
130 }
131
132 .repo-stats {
133 display: flex;
134 gap: 16px;
135 }
136
137 .repo-stat {
138 font-size: 13px;
139 color: var(--text-secondary);
140 }
141
142 .repo-stat strong {
143 color: var(--text-primary);
144 font-weight: 600;
145 }
146
147 /* Collection List */
148 .collection-item {
149 display: flex;
150 align-items: center;
151 justify-content: space-between;
152 padding: 14px 16px;
153 border-bottom: 1px solid var(--border);
154 cursor: pointer;
155 transition: background-color 0.2s ease;
156 }
157
158 .collection-item:hover {
159 background-color: var(--surface);
160 }
161
162 .collection-item-left {
163 display: flex;
164 align-items: center;
165 gap: 12px;
166 min-width: 0;
167 flex: 1;
168 }
169
170 .collection-icon {
171 width: 32px;
172 height: 32px;
173 border-radius: 6px;
174 background-color: var(--surface);
175 border: 1px solid var(--border);
176 display: flex;
177 align-items: center;
178 justify-content: center;
179 flex-shrink: 0;
180 }
181
182 .collection-icon svg {
183 width: 16px;
184 height: 16px;
185 color: var(--text-secondary);
186 }
187
188 .collection-name {
189 font-family: "JetBrains Mono", monospace;
190 font-size: 13px;
191 font-weight: 500;
192 color: var(--text-primary);
193 overflow: hidden;
194 text-overflow: ellipsis;
195 white-space: nowrap;
196 }
197
198 .collection-count {
199 font-size: 12px;
200 color: var(--text-muted);
201 background-color: var(--surface);
202 padding: 2px 8px;
203 border-radius: 9999px;
204 flex-shrink: 0;
205 }
206
207 .collection-chevron {
208 color: var(--text-muted);
209 flex-shrink: 0;
210 margin-left: 8px;
211 }
212
213 .collection-chevron svg {
214 width: 16px;
215 height: 16px;
216 }
217
218 /* Record Inspector */
219 .record-header {
220 padding: 12px 16px;
221 background-color: var(--surface);
222 border-bottom: 1px solid var(--border);
223 display: flex;
224 align-items: center;
225 justify-content: space-between;
226 }
227
228 .record-breadcrumb {
229 font-family: "JetBrains Mono", monospace;
230 font-size: 12px;
231 color: var(--text-secondary);
232 overflow: hidden;
233 text-overflow: ellipsis;
234 white-space: nowrap;
235 }
236
237 .record-breadcrumb span {
238 color: var(--accent-primary);
239 }
240
241 .record-copy-btn {
242 padding: 4px 10px;
243 border-radius: 4px;
244 border: 1px solid var(--border);
245 background-color: var(--bg);
246 color: var(--text-secondary);
247 font-size: 11px;
248 font-weight: 500;
249 cursor: pointer;
250 transition: all 0.2s ease;
251 }
252
253 .record-copy-btn:hover {
254 background-color: var(--surface-variant);
255 color: var(--text-primary);
256 }
257
258 .json-viewer {
259 padding: 16px;
260 font-family: "JetBrains Mono", monospace;
261 font-size: 12px;
262 line-height: 1.8;
263 overflow-x: auto;
264 }
265
266 .json-key {
267 color: var(--accent-primary);
268 }
269 .json-string {
270 color: var(--accent-success);
271 }
272 .json-number {
273 color: var(--accent-secondary);
274 }
275 .json-bool {
276 color: var(--accent-warning);
277 }
278 .json-null {
279 color: var(--text-muted);
280 }
281 .json-brace {
282 color: var(--text-secondary);
283 }
284
285 /* Section Label */
286 .section-label {
287 padding: 8px 16px;
288 font-size: 12px;
289 font-weight: 600;
290 color: var(--text-muted);
291 text-transform: uppercase;
292 letter-spacing: 0.5px;
293 background-color: var(--surface);
294 border-bottom: 1px solid var(--border);
295 }
296 </style>
297 </head>
298 <body>
299 <div class="mobile-container">
300 <!-- Header -->
301 <header class="header">
302 <button class="header-action">← Back</button>
303 <h1 class="header-title">PDS Explorer</h1>
304 <button class="header-action">⋯</button>
305 </header>
306
307 <!-- Search / AT-URI Input -->
308 <div class="devtools-search">
309 <div class="devtools-search-row">
310 <input class="input" type="text" placeholder="Handle, DID, or at:// URI" value="alice.bsky.social" />
311 <button class="devtools-search-btn">Resolve</button>
312 </div>
313 </div>
314
315 <!-- Tabs -->
316 <div class="devtools-tabs">
317 <button class="devtools-tab active">Repo</button>
318 <button class="devtools-tab">Records</button>
319 <button class="devtools-tab">JSON</button>
320 </div>
321
322 <div class="devtools-container">
323 <!-- Repo Overview -->
324 <div class="repo-header">
325 <div class="repo-identity">
326 <div class="repo-avatar">AS</div>
327 <div class="repo-names">
328 <div class="repo-handle">alice.bsky.social</div>
329 <div class="repo-did">did:plc:z72i7hdynmk6r22z27h6tvur</div>
330 </div>
331 </div>
332 <div class="repo-stats">
333 <div class="repo-stat"><strong>12</strong> collections</div>
334 <div class="repo-stat"><strong>1,847</strong> records</div>
335 </div>
336 </div>
337
338 <!-- Collections -->
339 <div class="section-label">Collections</div>
340
341 <div class="collection-item">
342 <div class="collection-item-left">
343 <div class="collection-icon">
344 <svg
345 viewBox="0 0 24 24"
346 fill="none"
347 stroke="currentColor"
348 stroke-width="2"
349 stroke-linecap="round"
350 stroke-linejoin="round">
351 <path
352 d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
353 </svg>
354 </div>
355 <div class="collection-name">app.bsky.feed.post</div>
356 </div>
357 <span class="collection-count">482</span>
358 <div class="collection-chevron">
359 <svg
360 viewBox="0 0 24 24"
361 fill="none"
362 stroke="currentColor"
363 stroke-width="2"
364 stroke-linecap="round"
365 stroke-linejoin="round">
366 <polyline points="9 18 15 12 9 6" />
367 </svg>
368 </div>
369 </div>
370
371 <div class="collection-item">
372 <div class="collection-item-left">
373 <div class="collection-icon">
374 <svg
375 viewBox="0 0 24 24"
376 fill="none"
377 stroke="currentColor"
378 stroke-width="2"
379 stroke-linecap="round"
380 stroke-linejoin="round">
381 <path
382 d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
383 </svg>
384 </div>
385 <div class="collection-name">app.bsky.feed.like</div>
386 </div>
387 <span class="collection-count">1,203</span>
388 <div class="collection-chevron">
389 <svg
390 viewBox="0 0 24 24"
391 fill="none"
392 stroke="currentColor"
393 stroke-width="2"
394 stroke-linecap="round"
395 stroke-linejoin="round">
396 <polyline points="9 18 15 12 9 6" />
397 </svg>
398 </div>
399 </div>
400
401 <div class="collection-item">
402 <div class="collection-item-left">
403 <div class="collection-icon">
404 <svg
405 viewBox="0 0 24 24"
406 fill="none"
407 stroke="currentColor"
408 stroke-width="2"
409 stroke-linecap="round"
410 stroke-linejoin="round">
411 <polyline points="17 1 21 5 17 9" />
412 <path d="M3 11V9a4 4 0 0 1 4-4h14" />
413 <polyline points="7 23 3 19 7 15" />
414 <path d="M21 13v2a4 4 0 0 1-4 4H3" />
415 </svg>
416 </div>
417 <div class="collection-name">app.bsky.feed.repost</div>
418 </div>
419 <span class="collection-count">89</span>
420 <div class="collection-chevron">
421 <svg
422 viewBox="0 0 24 24"
423 fill="none"
424 stroke="currentColor"
425 stroke-width="2"
426 stroke-linecap="round"
427 stroke-linejoin="round">
428 <polyline points="9 18 15 12 9 6" />
429 </svg>
430 </div>
431 </div>
432
433 <div class="collection-item">
434 <div class="collection-item-left">
435 <div class="collection-icon">
436 <svg
437 viewBox="0 0 24 24"
438 fill="none"
439 stroke="currentColor"
440 stroke-width="2"
441 stroke-linecap="round"
442 stroke-linejoin="round">
443 <path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
444 <circle cx="8.5" cy="7" r="4" />
445 <line x1="20" y1="8" x2="20" y2="14" />
446 <line x1="23" y1="11" x2="17" y2="11" />
447 </svg>
448 </div>
449 <div class="collection-name">app.bsky.graph.follow</div>
450 </div>
451 <span class="collection-count">256</span>
452 <div class="collection-chevron">
453 <svg
454 viewBox="0 0 24 24"
455 fill="none"
456 stroke="currentColor"
457 stroke-width="2"
458 stroke-linecap="round"
459 stroke-linejoin="round">
460 <polyline points="9 18 15 12 9 6" />
461 </svg>
462 </div>
463 </div>
464
465 <div class="collection-item">
466 <div class="collection-item-left">
467 <div class="collection-icon">
468 <svg
469 viewBox="0 0 24 24"
470 fill="none"
471 stroke="currentColor"
472 stroke-width="2"
473 stroke-linecap="round"
474 stroke-linejoin="round">
475 <circle cx="12" cy="12" r="10" />
476 <line x1="2" y1="12" x2="22" y2="12" />
477 <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
478 </svg>
479 </div>
480 <div class="collection-name">app.bsky.feed.generator</div>
481 </div>
482 <span class="collection-count">2</span>
483 <div class="collection-chevron">
484 <svg
485 viewBox="0 0 24 24"
486 fill="none"
487 stroke="currentColor"
488 stroke-width="2"
489 stroke-linecap="round"
490 stroke-linejoin="round">
491 <polyline points="9 18 15 12 9 6" />
492 </svg>
493 </div>
494 </div>
495
496 <div class="collection-item">
497 <div class="collection-item-left">
498 <div class="collection-icon">
499 <svg
500 viewBox="0 0 24 24"
501 fill="none"
502 stroke="currentColor"
503 stroke-width="2"
504 stroke-linecap="round"
505 stroke-linejoin="round">
506 <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
507 <circle cx="12" cy="7" r="4" />
508 </svg>
509 </div>
510 <div class="collection-name">app.bsky.actor.profile</div>
511 </div>
512 <span class="collection-count">1</span>
513 <div class="collection-chevron">
514 <svg
515 viewBox="0 0 24 24"
516 fill="none"
517 stroke="currentColor"
518 stroke-width="2"
519 stroke-linecap="round"
520 stroke-linejoin="round">
521 <polyline points="9 18 15 12 9 6" />
522 </svg>
523 </div>
524 </div>
525
526 <!-- Record Inspector Preview (shown when tapping a record) -->
527 <div class="section-label">Record Preview</div>
528
529 <div class="record-header">
530 <div class="record-breadcrumb"><span>app.bsky.feed.post</span> / 3lbr7yz2gfk2j</div>
531 <button class="record-copy-btn">Copy JSON</button>
532 </div>
533
534 <div class="json-viewer">
535 <span class="json-brace">{</span><br />
536 <span class="json-key">"$type"</span>:
537 <span class="json-string">"app.bsky.feed.post"</span>,<br />
538 <span class="json-key">"text"</span>:
539 <span class="json-string">"Just launched my new project! 🚀"</span>,<br />
540 <span class="json-key">"createdAt"</span>:
541 <span class="json-string">"2024-03-15T14:30:00.000Z"</span>,<br />
542 <span class="json-key">"langs"</span>: <span class="json-brace">[</span
543 ><span class="json-string">"en"</span><span class="json-brace">]</span>,<br />
544 <span class="json-key">"facets"</span>: <span class="json-brace">[</span><br />
545 <span class="json-brace">{</span><br />
546 <span class="json-key">"index"</span>: <span class="json-brace">{</span
547 ><br />
548 <span class="json-key">"byteStart"</span>:
549 <span class="json-number">0</span>,<br />
550 <span class="json-key">"byteEnd"</span>:
551 <span class="json-number">34</span><br />
552 <span class="json-brace">}</span>,<br />
553 <span class="json-key">"features"</span>: <span class="json-brace">[</span
554 ><span class="json-brace">{</span><br />
555 <span class="json-key">"$type"</span>:
556 <span class="json-string">"...facet#tag"</span>,<br />
557 <span class="json-key">"tag"</span>:
558 <span class="json-string">"buildinpublic"</span><br />
559 <span class="json-brace">}</span><span class="json-brace">]</span><br />
560 <span class="json-brace">}</span><br />
561 <span class="json-brace">]</span><br />
562 <span class="json-brace">}</span>
563 </div>
564 </div>
565
566 <!-- Bottom Navigation -->
567 <nav class="nav-bar">
568 <a href="home.html" class="nav-item">
569 <svg
570 viewBox="0 0 24 24"
571 fill="none"
572 stroke="currentColor"
573 stroke-width="2"
574 stroke-linecap="round"
575 stroke-linejoin="round">
576 <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
577 <polyline points="9 22 9 12 15 12 15 22" />
578 </svg>
579 <span>Home</span>
580 </a>
581
582 <a href="search.html" class="nav-item">
583 <svg
584 viewBox="0 0 24 24"
585 fill="none"
586 stroke="currentColor"
587 stroke-width="2"
588 stroke-linecap="round"
589 stroke-linejoin="round">
590 <circle cx="11" cy="11" r="8" />
591 <line x1="21" y1="21" x2="16.65" y2="16.65" />
592 </svg>
593 <span>Search</span>
594 </a>
595
596 <a href="profile.html" class="nav-item">
597 <svg
598 viewBox="0 0 24 24"
599 fill="none"
600 stroke="currentColor"
601 stroke-width="2"
602 stroke-linecap="round"
603 stroke-linejoin="round">
604 <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
605 <circle cx="12" cy="7" r="4" />
606 </svg>
607 <span>Profile</span>
608 </a>
609
610 <a href="settings.html" class="nav-item">
611 <svg
612 viewBox="0 0 24 24"
613 fill="none"
614 stroke="currentColor"
615 stroke-width="2"
616 stroke-linecap="round"
617 stroke-linejoin="round">
618 <circle cx="12" cy="12" r="3" />
619 <path
620 d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
621 </svg>
622 <span>Settings</span>
623 </a>
624 </nav>
625 </div>
626
627 <script>
628 if (localStorage.getItem("theme") === "dark") {
629 document.documentElement.setAttribute("data-theme", "dark");
630 }
631 </script>
632 </body>
633</html>