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>Search - 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=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap"
11 rel="stylesheet" />
12 <link rel="stylesheet" href="styles.css" />
13 <style>
14 .search-container {
15 padding-bottom: 88px;
16 }
17
18 .search-bar {
19 padding: 12px 16px;
20 border-bottom: 1px solid var(--border);
21 display: flex;
22 align-items: center;
23 gap: 12px;
24 }
25
26 .search-input-wrapper {
27 flex: 1;
28 position: relative;
29 }
30
31 .search-input-wrapper svg {
32 position: absolute;
33 left: 12px;
34 top: 50%;
35 transform: translateY(-50%);
36 width: 18px;
37 height: 18px;
38 color: var(--text-muted);
39 }
40
41 .search-input {
42 width: 100%;
43 padding: 10px 12px 10px 38px;
44 border: 1px solid var(--border);
45 border-radius: 9999px;
46 background-color: var(--surface);
47 color: var(--text-primary);
48 font-size: 15px;
49 transition: border-color 0.2s ease;
50 }
51
52 .search-input:focus {
53 outline: none;
54 border-color: var(--accent-primary);
55 }
56
57 .search-input::placeholder {
58 color: var(--text-muted);
59 }
60
61 .search-cancel {
62 background: none;
63 border: none;
64 color: var(--accent-primary);
65 font-size: 15px;
66 font-weight: 500;
67 cursor: pointer;
68 white-space: nowrap;
69 }
70
71 /* Tabs */
72 .search-tabs {
73 display: flex;
74 border-bottom: 1px solid var(--border);
75 background-color: var(--bg);
76 }
77
78 .search-tab {
79 flex: 1;
80 padding: 14px;
81 text-align: center;
82 font-weight: 600;
83 font-size: 15px;
84 color: var(--text-secondary);
85 cursor: pointer;
86 border-bottom: 2px solid transparent;
87 transition: all 0.2s ease;
88 background: none;
89 border-top: none;
90 border-left: none;
91 border-right: none;
92 }
93
94 .search-tab:hover {
95 background-color: var(--surface);
96 color: var(--text-primary);
97 }
98
99 .search-tab.active {
100 color: var(--text-primary);
101 border-bottom-color: var(--accent-primary);
102 }
103
104 /* Sort Toggle */
105 .search-sort {
106 display: flex;
107 align-items: center;
108 gap: 8px;
109 padding: 10px 16px;
110 border-bottom: 1px solid var(--border);
111 }
112
113 .search-sort-label {
114 font-size: 13px;
115 color: var(--text-secondary);
116 }
117
118 .sort-toggle {
119 display: flex;
120 background-color: var(--surface);
121 border-radius: 8px;
122 border: 1px solid var(--border);
123 overflow: hidden;
124 }
125
126 .sort-option {
127 padding: 6px 14px;
128 font-size: 13px;
129 font-weight: 500;
130 border: none;
131 background: none;
132 color: var(--text-secondary);
133 cursor: pointer;
134 transition: all 0.2s ease;
135 }
136
137 .sort-option.active {
138 background-color: var(--accent-primary);
139 color: white;
140 }
141
142 /* Search History */
143 .search-history-header {
144 display: flex;
145 align-items: center;
146 justify-content: space-between;
147 padding: 12px 16px;
148 }
149
150 .search-history-title {
151 font-size: 14px;
152 font-weight: 600;
153 color: var(--text-primary);
154 }
155
156 .search-history-clear {
157 background: none;
158 border: none;
159 color: var(--accent-primary);
160 font-size: 13px;
161 font-weight: 500;
162 cursor: pointer;
163 }
164
165 .history-item {
166 display: flex;
167 align-items: center;
168 justify-content: space-between;
169 padding: 12px 16px;
170 cursor: pointer;
171 transition: background-color 0.2s ease;
172 }
173
174 .history-item:hover {
175 background-color: var(--surface);
176 }
177
178 .history-item-left {
179 display: flex;
180 align-items: center;
181 gap: 12px;
182 }
183
184 .history-icon {
185 width: 20px;
186 height: 20px;
187 color: var(--text-muted);
188 }
189
190 .history-query {
191 font-size: 15px;
192 color: var(--text-primary);
193 }
194
195 .history-meta {
196 font-size: 12px;
197 color: var(--text-muted);
198 }
199
200 .history-delete {
201 width: 20px;
202 height: 20px;
203 color: var(--text-muted);
204 background: none;
205 border: none;
206 cursor: pointer;
207 transition: color 0.2s ease;
208 }
209
210 .history-delete:hover {
211 color: var(--accent-error);
212 }
213
214 /* Actor Results */
215 .actor-result {
216 display: flex;
217 align-items: center;
218 gap: 12px;
219 padding: 12px 16px;
220 border-bottom: 1px solid var(--border);
221 cursor: pointer;
222 transition: background-color 0.2s ease;
223 }
224
225 .actor-result:hover {
226 background-color: var(--surface);
227 }
228
229 .actor-result-info {
230 flex: 1;
231 min-width: 0;
232 }
233
234 .actor-result-name {
235 font-weight: 600;
236 font-size: 15px;
237 color: var(--text-primary);
238 }
239
240 .actor-result-handle {
241 font-size: 14px;
242 color: var(--text-secondary);
243 }
244
245 .actor-result-bio {
246 font-size: 13px;
247 color: var(--text-muted);
248 margin-top: 2px;
249 display: -webkit-box;
250 line-clamp: 1;
251 -webkit-line-clamp: 1;
252 -webkit-box-orient: vertical;
253 overflow: hidden;
254 }
255
256 .follow-btn {
257 padding: 6px 16px;
258 border-radius: 9999px;
259 border: 1.5px solid var(--accent-primary);
260 background: none;
261 color: var(--accent-primary);
262 font-size: 13px;
263 font-weight: 600;
264 cursor: pointer;
265 transition: all 0.2s ease;
266 flex-shrink: 0;
267 }
268
269 .follow-btn:hover {
270 background-color: var(--accent-primary);
271 color: white;
272 }
273
274 /* Typeahead */
275 .typeahead-section {
276 border-bottom: 1px solid var(--border);
277 }
278
279 .typeahead-label {
280 padding: 8px 16px;
281 font-size: 12px;
282 font-weight: 600;
283 color: var(--text-muted);
284 text-transform: uppercase;
285 letter-spacing: 0.5px;
286 }
287
288 .post-facet-mention {
289 color: var(--accent-primary);
290 text-decoration: none;
291 font-weight: 500;
292 }
293
294 .post-facet-hashtag {
295 color: var(--accent-secondary);
296 text-decoration: none;
297 font-weight: 500;
298 }
299 </style>
300 </head>
301 <body>
302 <div class="mobile-container">
303 <!-- Search Bar (replaces standard header) -->
304 <div class="search-bar">
305 <div class="search-input-wrapper">
306 <svg
307 viewBox="0 0 24 24"
308 fill="none"
309 stroke="currentColor"
310 stroke-width="2"
311 stroke-linecap="round"
312 stroke-linejoin="round">
313 <circle cx="11" cy="11" r="8" />
314 <line x1="21" y1="21" x2="16.65" y2="16.65" />
315 </svg>
316 <input class="search-input" type="text" placeholder="Search posts or people" value="atproto" />
317 </div>
318 <button class="search-cancel">Cancel</button>
319 </div>
320
321 <!-- Tabs -->
322 <div class="search-tabs">
323 <button class="search-tab active">Posts</button>
324 <button class="search-tab">People</button>
325 </div>
326
327 <!-- Sort -->
328 <div class="search-sort">
329 <span class="search-sort-label">Sort by</span>
330 <div class="sort-toggle">
331 <button class="sort-option active">Top</button>
332 <button class="sort-option">Latest</button>
333 </div>
334 </div>
335
336 <div class="search-container">
337 <!-- Post Results -->
338 <article class="post-card">
339 <div class="post-header">
340 <div class="avatar">PB</div>
341 <div class="post-author">
342 <div class="post-author-name">Paul Frazee</div>
343 <div class="post-author-handle">@pfrazee.com · <span class="post-timestamp">3h</span></div>
344 </div>
345 </div>
346 <div class="post-content">
347 We just shipped a major update to the
348 <a href="#" class="post-facet-mention">@atproto</a> federation code. Self-hosting your own PDS is now easier
349 than ever.
350 <a href="#" class="post-facet-hashtag">#atproto</a>
351 <a href="#" class="post-facet-hashtag">#decentralized</a>
352 </div>
353 <div class="post-actions">
354 <button class="post-action">
355 <svg
356 viewBox="0 0 24 24"
357 fill="none"
358 stroke="currentColor"
359 stroke-width="2"
360 stroke-linecap="round"
361 stroke-linejoin="round">
362 <path
363 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" />
364 </svg>
365 67
366 </button>
367 <button class="post-action">
368 <svg
369 viewBox="0 0 24 24"
370 fill="none"
371 stroke="currentColor"
372 stroke-width="2"
373 stroke-linecap="round"
374 stroke-linejoin="round">
375 <polyline points="17 1 21 5 17 9" />
376 <path d="M3 11V9a4 4 0 0 1 4-4h14" />
377 <polyline points="7 23 3 19 7 15" />
378 <path d="M21 13v2a4 4 0 0 1-4 4H3" />
379 </svg>
380 34
381 </button>
382 <button class="post-action">
383 <svg
384 viewBox="0 0 24 24"
385 fill="none"
386 stroke="currentColor"
387 stroke-width="2"
388 stroke-linecap="round"
389 stroke-linejoin="round">
390 <path
391 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" />
392 </svg>
393 512
394 </button>
395 <button class="post-action">
396 <svg
397 viewBox="0 0 24 24"
398 fill="none"
399 stroke="currentColor"
400 stroke-width="2"
401 stroke-linecap="round"
402 stroke-linejoin="round">
403 <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" />
404 <polyline points="16 6 12 2 8 6" />
405 <line x1="12" y1="2" x2="12" y2="15" />
406 </svg>
407 </button>
408 </div>
409 </article>
410
411 <article class="post-card">
412 <div class="post-header">
413 <div class="avatar">JK</div>
414 <div class="post-author">
415 <div class="post-author-name">Jake Gold</div>
416 <div class="post-author-handle">@jake.bsky.social · <span class="post-timestamp">6h</span></div>
417 </div>
418 </div>
419 <div class="post-content">
420 The <a href="#" class="post-facet-hashtag">#atproto</a> ecosystem is growing fast. More and more third-party
421 apps popping up every week. The open protocol approach is really paying off 🌐
422 </div>
423 <div class="post-actions">
424 <button class="post-action">
425 <svg
426 viewBox="0 0 24 24"
427 fill="none"
428 stroke="currentColor"
429 stroke-width="2"
430 stroke-linecap="round"
431 stroke-linejoin="round">
432 <path
433 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" />
434 </svg>
435 23
436 </button>
437 <button class="post-action">
438 <svg
439 viewBox="0 0 24 24"
440 fill="none"
441 stroke="currentColor"
442 stroke-width="2"
443 stroke-linecap="round"
444 stroke-linejoin="round">
445 <polyline points="17 1 21 5 17 9" />
446 <path d="M3 11V9a4 4 0 0 1 4-4h14" />
447 <polyline points="7 23 3 19 7 15" />
448 <path d="M21 13v2a4 4 0 0 1-4 4H3" />
449 </svg>
450 11
451 </button>
452 <button class="post-action">
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 <path
461 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" />
462 </svg>
463 178
464 </button>
465 <button class="post-action">
466 <svg
467 viewBox="0 0 24 24"
468 fill="none"
469 stroke="currentColor"
470 stroke-width="2"
471 stroke-linecap="round"
472 stroke-linejoin="round">
473 <path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" />
474 <polyline points="16 6 12 2 8 6" />
475 <line x1="12" y1="2" x2="12" y2="15" />
476 </svg>
477 </button>
478 </div>
479 </article>
480
481 <!-- People Results (shown when People tab is active — rendered inline for preview) -->
482 <div class="typeahead-section" style="display: none">
483 <div class="typeahead-label">People</div>
484
485 <div class="actor-result">
486 <div class="avatar">AT</div>
487 <div class="actor-result-info">
488 <div class="actor-result-name">AT Protocol</div>
489 <div class="actor-result-handle">@atproto.com</div>
490 <div class="actor-result-bio">The AT Protocol – social networking technology created by Bluesky</div>
491 </div>
492 <button class="follow-btn">Follow</button>
493 </div>
494
495 <div class="actor-result">
496 <div class="avatar">BS</div>
497 <div class="actor-result-info">
498 <div class="actor-result-name">Bluesky</div>
499 <div class="actor-result-handle">@bsky.app</div>
500 <div class="actor-result-bio">Building a social internet. Join us at bsky.app</div>
501 </div>
502 <button class="follow-btn">Follow</button>
503 </div>
504 </div>
505
506 <!-- Search History (shown when search input is empty) -->
507 <div style="display: none">
508 <div class="search-history-header">
509 <span class="search-history-title">Recent Searches</span>
510 <button class="search-history-clear">Clear All</button>
511 </div>
512
513 <div class="history-item">
514 <div class="history-item-left">
515 <svg
516 class="history-icon"
517 viewBox="0 0 24 24"
518 fill="none"
519 stroke="currentColor"
520 stroke-width="2"
521 stroke-linecap="round"
522 stroke-linejoin="round">
523 <polyline points="12 8 12 12 14 14" />
524 <circle cx="12" cy="12" r="10" />
525 </svg>
526 <div>
527 <div class="history-query">flutter bloc</div>
528 <div class="history-meta">Posts · 2 hours ago</div>
529 </div>
530 </div>
531 <button class="history-delete">
532 <svg
533 viewBox="0 0 24 24"
534 fill="none"
535 stroke="currentColor"
536 stroke-width="2"
537 stroke-linecap="round"
538 stroke-linejoin="round">
539 <line x1="18" y1="6" x2="6" y2="18" />
540 <line x1="6" y1="6" x2="18" y2="18" />
541 </svg>
542 </button>
543 </div>
544
545 <div class="history-item">
546 <div class="history-item-left">
547 <svg
548 class="history-icon"
549 viewBox="0 0 24 24"
550 fill="none"
551 stroke="currentColor"
552 stroke-width="2"
553 stroke-linecap="round"
554 stroke-linejoin="round">
555 <polyline points="12 8 12 12 14 14" />
556 <circle cx="12" cy="12" r="10" />
557 </svg>
558 <div>
559 <div class="history-query">@pfrazee.com</div>
560 <div class="history-meta">People · Yesterday</div>
561 </div>
562 </div>
563 <button class="history-delete">
564 <svg
565 viewBox="0 0 24 24"
566 fill="none"
567 stroke="currentColor"
568 stroke-width="2"
569 stroke-linecap="round"
570 stroke-linejoin="round">
571 <line x1="18" y1="6" x2="6" y2="18" />
572 <line x1="6" y1="6" x2="18" y2="18" />
573 </svg>
574 </button>
575 </div>
576
577 <div class="history-item">
578 <div class="history-item-left">
579 <svg
580 class="history-icon"
581 viewBox="0 0 24 24"
582 fill="none"
583 stroke="currentColor"
584 stroke-width="2"
585 stroke-linecap="round"
586 stroke-linejoin="round">
587 <polyline points="12 8 12 12 14 14" />
588 <circle cx="12" cy="12" r="10" />
589 </svg>
590 <div>
591 <div class="history-query">#decentralized</div>
592 <div class="history-meta">Posts · 3 days ago</div>
593 </div>
594 </div>
595 <button class="history-delete">
596 <svg
597 viewBox="0 0 24 24"
598 fill="none"
599 stroke="currentColor"
600 stroke-width="2"
601 stroke-linecap="round"
602 stroke-linejoin="round">
603 <line x1="18" y1="6" x2="6" y2="18" />
604 <line x1="6" y1="6" x2="18" y2="18" />
605 </svg>
606 </button>
607 </div>
608 </div>
609 </div>
610
611 <!-- Bottom Navigation -->
612 <nav class="nav-bar">
613 <a href="home.html" class="nav-item">
614 <svg
615 viewBox="0 0 24 24"
616 fill="none"
617 stroke="currentColor"
618 stroke-width="2"
619 stroke-linecap="round"
620 stroke-linejoin="round">
621 <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
622 <polyline points="9 22 9 12 15 12 15 22" />
623 </svg>
624 <span>Home</span>
625 </a>
626
627 <a href="search.html" class="nav-item active">
628 <svg
629 viewBox="0 0 24 24"
630 fill="none"
631 stroke="currentColor"
632 stroke-width="2"
633 stroke-linecap="round"
634 stroke-linejoin="round">
635 <circle cx="11" cy="11" r="8" />
636 <line x1="21" y1="21" x2="16.65" y2="16.65" />
637 </svg>
638 <span>Search</span>
639 </a>
640
641 <a href="profile.html" class="nav-item">
642 <svg
643 viewBox="0 0 24 24"
644 fill="none"
645 stroke="currentColor"
646 stroke-width="2"
647 stroke-linecap="round"
648 stroke-linejoin="round">
649 <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
650 <circle cx="12" cy="7" r="4" />
651 </svg>
652 <span>Profile</span>
653 </a>
654
655 <a href="settings.html" class="nav-item">
656 <svg
657 viewBox="0 0 24 24"
658 fill="none"
659 stroke="currentColor"
660 stroke-width="2"
661 stroke-linecap="round"
662 stroke-linejoin="round">
663 <circle cx="12" cy="12" r="3" />
664 <path
665 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" />
666 </svg>
667 <span>Settings</span>
668 </a>
669 </nav>
670 </div>
671
672 <script>
673 if (localStorage.getItem("theme") === "dark") {
674 document.documentElement.setAttribute("data-theme", "dark");
675 }
676
677 document.querySelectorAll(".search-tab").forEach((tab) => {
678 tab.addEventListener("click", () => {
679 document.querySelectorAll(".search-tab").forEach((t) => t.classList.remove("active"));
680 tab.classList.add("active");
681
682 const isPeople = tab.textContent === "People";
683 document.querySelectorAll(".post-card").forEach((el) => (el.style.display = isPeople ? "none" : "block"));
684 document.querySelector(".search-sort").style.display = isPeople ? "none" : "flex";
685 document.querySelector(".typeahead-section").style.display = isPeople ? "block" : "none";
686 });
687 });
688 </script>
689 </body>
690</html>