fork
Configure Feed
Select the types of activity you want to include in your feed.
mobile bluesky app made with flutter
lazurite.stormlightlabs.org/
mobile
bluesky
flutter
fork
Configure Feed
Select the types of activity you want to include in your feed.
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" />
6 <meta
7 name="description"
8 content="Lazurite - A beautiful Bluesky client for mobile and desktop. Material You on iOS & Android, native desktop with semantic search." />
9 <meta name="author" content="Stormlight Labs" />
10 <title>Lazurite for BlueSky</title>
11 <link rel="icon" type="image/svg+xml" href="static/favicon.svg" />
12 <link rel="preconnect" href="https://fonts.googleapis.com" />
13 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
14 <link
15 href="https://fonts.googleapis.com/css2?family=Google+Sans+Code:ital,wght@0,300..800;1,300..800&family=Google+Sans:ital,opsz,wght@0,17..18,400..700;1,17..18,400..700&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Lora:ital,wght@0,400..700;1,400..700&display=swap"
16 rel="stylesheet" />
17
18 <style>
19 :root {
20 /* Matches LazuriteTheme.dark() from lazurite_theme.dart */
21 --bg-dark: #000000; /* darkSurfaceContainerLowest (scaffold) */
22 --surface-dark: #191919; /* darkSurfaceContainer */
23 --surface-variant: #1f1f1f; /* darkSurfaceContainerHigh */
24 --outline: rgba(255, 255, 255, 0.1); /* darkOutlineVariant */
25 --outline-bright: rgba(255, 255, 255, 0.2); /* darkOutline */
26 --text-primary: #f4f6fb; /* darkOnSurface / darkOnPrimaryContainer */
27 --text-secondary: #ababab; /* darkOnSurfaceVariant */
28 --text-tertiary: #6f6f6f;
29
30 --primary: #7dafff; /* darkPrimary */
31 --secondary: #0073de; /* darkPrimaryContainer */
32 --tertiary: #33b1ff;
33 --cyan: #08bdba;
34 --purple: #be95ff;
35 --error: #ff8080; /* darkError */
36
37 --font-title: "Lora", serif;
38 --font-display: "Google Sans", sans-serif;
39 --font-body: "Inter", sans-serif;
40 --font-mono: "Google Sans Code", monospace;
41 }
42
43 * {
44 margin: 0;
45 padding: 0;
46 box-sizing: border-box;
47 }
48
49 body {
50 font-family: var(--font-body);
51 background-color: var(--bg-dark);
52 color: var(--text-primary);
53 line-height: 1.6;
54 min-height: 100vh;
55 display: flex;
56 flex-direction: column;
57 }
58
59 .container {
60 max-width: 1200px;
61 margin: 0 auto;
62 padding: 2rem;
63 flex: 1;
64 display: flex;
65 flex-direction: column;
66 justify-content: center;
67 }
68
69 header {
70 text-align: center;
71 margin-bottom: 4rem;
72 }
73
74 .logo {
75 font-family: var(--font-title);
76 font-size: 3.5rem;
77 font-weight: 700;
78 background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 50%, var(--tertiary) 100%);
79 -webkit-background-clip: text;
80 -webkit-text-fill-color: transparent;
81 background-clip: text;
82 margin-bottom: 1rem;
83 letter-spacing: -0.02em;
84 display: inline-flex;
85 align-items: center;
86 gap: 0.5rem;
87 }
88
89 .logo-icon {
90 width: 3rem;
91 height: 3rem;
92 mask-image: url('./static/logo.svg');
93 background-color: var(--primary);
94 mask-repeat: no-repeat;
95 mask-size: contain;
96 mask-position: center;
97
98 -webkit-mask-image: url('./static/logo.svg');
99 -webkit-mask-repeat: no-repeat;
100 -webkit-mask-size: contain;
101 -webkit-mask-position: center;
102 }
103
104 .tagline {
105 font-size: 1.25rem;
106 color: var(--text-secondary);
107 font-weight: 400;
108 letter-spacing: 0.01em;
109 }
110
111 main {
112 max-width: 900px;
113 margin: 0 auto;
114 }
115
116 .hero {
117 text-align: center;
118 margin-bottom: 4rem;
119 }
120
121 .hero h1 {
122 font-family: var(--font-display);
123 font-size: 2.5rem;
124 font-weight: 600;
125 margin-bottom: 1.5rem;
126 color: var(--text-primary);
127 line-height: 1.2;
128 }
129
130 .hero p {
131 font-size: 1.125rem;
132 color: var(--text-secondary);
133 margin-bottom: 2rem;
134 line-height: 1.8;
135 }
136
137 .status-badge {
138 display: inline-flex;
139 align-items: center;
140 gap: 0.5rem;
141 background: var(--surface-dark);
142 padding: 0.75rem 1.5rem;
143 border-radius: 2rem;
144 border: 1px solid var(--outline);
145 font-size: 0.875rem;
146 font-weight: 500;
147 letter-spacing: 0.02em;
148 margin-bottom: 3rem;
149 }
150
151 .status-dot {
152 width: 8px;
153 height: 8px;
154 border-radius: 50%;
155 background: var(--primary);
156 animation: pulse 2s ease-in-out infinite;
157 }
158
159 @keyframes pulse {
160 0%,
161 100% {
162 opacity: 1;
163 }
164
165 50% {
166 opacity: 0.5;
167 }
168 }
169
170 /* Platform sections */
171 .platforms {
172 display: grid;
173 grid-template-columns: 1fr 1fr;
174 gap: 2rem;
175 margin-bottom: 4rem;
176 }
177
178 .platform {
179 background: var(--surface-dark);
180 padding: 2rem;
181 border-radius: 1rem;
182 border: 1px solid var(--outline);
183 }
184
185 .platform-header {
186 display: flex;
187 align-items: center;
188 gap: 0.75rem;
189 margin-bottom: 1.25rem;
190 }
191
192 .platform-icon {
193 width: 40px;
194 height: 40px;
195 border-radius: 10px;
196 display: flex;
197 align-items: center;
198 justify-content: center;
199 flex-shrink: 0;
200 }
201
202 .platform-icon.mobile {
203 background: linear-gradient(135deg, var(--primary), var(--secondary));
204 }
205
206 .platform-icon.desktop {
207 background: linear-gradient(135deg, var(--purple), var(--tertiary));
208 }
209
210 .platform-icon img {
211 width: 22px;
212 height: 22px;
213 }
214
215 .platform h2 {
216 font-family: var(--font-display);
217 font-size: 1.375rem;
218 font-weight: 600;
219 }
220
221 .platform-badge {
222 font-size: 0.6875rem;
223 font-weight: 600;
224 text-transform: uppercase;
225 letter-spacing: 0.06em;
226 padding: 0.2rem 0.5rem;
227 border-radius: 0.25rem;
228 margin-left: auto;
229 }
230
231 .platform-badge.alpha {
232 background: rgba(190, 149, 255, 0.15);
233 color: var(--purple);
234 }
235
236 .platform-badge.beta {
237 background: rgba(125, 175, 255, 0.15);
238 color: var(--primary);
239 }
240
241 .platform-desc {
242 color: var(--text-secondary);
243 font-size: 0.9375rem;
244 line-height: 1.6;
245 margin-bottom: 1.25rem;
246 }
247
248 .platform-targets {
249 display: flex;
250 gap: 0.5rem;
251 flex-wrap: wrap;
252 margin-bottom: 1.25rem;
253 }
254
255 .target-tag {
256 background: var(--surface-variant);
257 padding: 0.3rem 0.625rem;
258 border-radius: 0.375rem;
259 font-size: 0.8125rem;
260 font-family: var(--font-mono);
261 color: var(--text-secondary);
262 }
263
264 .platform-features {
265 list-style: none;
266 }
267
268 .platform-features li {
269 color: var(--text-secondary);
270 font-size: 0.875rem;
271 padding: 0.3rem 0;
272 padding-left: 1.25rem;
273 position: relative;
274 }
275
276 .platform-features li::before {
277 content: "";
278 position: absolute;
279 left: 0;
280 top: 0.7rem;
281 width: 6px;
282 height: 6px;
283 border-radius: 50%;
284 background: var(--primary);
285 opacity: 0.6;
286 }
287
288 .platform-screenshot {
289 margin-top: 1.5rem;
290 border-radius: 0.75rem;
291 overflow: hidden;
292 border: 1px solid var(--outline);
293 }
294
295 .platform-screenshot img {
296 width: 100%;
297 height: auto;
298 display: block;
299 }
300
301 .platform-screenshot .caption {
302 font-size: 0.75rem;
303 color: var(--text-tertiary);
304 text-align: center;
305 padding: 0.5rem;
306 background: var(--surface-variant);
307 }
308
309 /* Hero screenshots */
310 .hero-screenshots {
311 display: flex;
312 gap: 1.5rem;
313 justify-content: center;
314 align-items: flex-end;
315 margin-bottom: 3rem;
316 padding: 0 1rem;
317 }
318
319 .hero-screenshot {
320 border-radius: 1rem;
321 overflow: hidden;
322 border: 1px solid var(--outline);
323 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
324 }
325
326 .hero-screenshot img {
327 display: block;
328 width: 100%;
329 height: auto;
330 }
331
332 .hero-screenshot.phone {
333 width: 200px;
334 flex-shrink: 0;
335 }
336
337 .hero-screenshot.desktop-shot {
338 width: 640px;
339 flex-shrink: 0;
340 }
341
342 @media (max-width: 768px) {
343 .hero-screenshots {
344 flex-direction: column;
345 align-items: center;
346 }
347
348 .hero-screenshot.phone {
349 width: 160px;
350 }
351
352 .hero-screenshot.desktop-shot {
353 width: 100%;
354 max-width: 360px;
355 }
356 }
357
358 /* Features grid */
359 .section-title {
360 font-family: var(--font-display);
361 font-size: 1.5rem;
362 font-weight: 600;
363 margin-bottom: 1.5rem;
364 color: var(--text-primary);
365 }
366
367 .features {
368 display: grid;
369 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
370 gap: 1.5rem;
371 margin-bottom: 4rem;
372 }
373
374 .feature {
375 background: var(--surface-dark);
376 padding: 1.75rem;
377 border-radius: 1rem;
378 border: 1px solid var(--outline);
379 transition:
380 transform 0.2s ease,
381 border-color 0.2s ease;
382 }
383
384 .feature:hover {
385 transform: translateY(-2px);
386 border-color: var(--outline-bright);
387 }
388
389 .feature h3 {
390 font-family: var(--font-display);
391 font-size: 1.125rem;
392 margin-bottom: 0.5rem;
393 color: var(--text-primary);
394 }
395
396 .feature p {
397 color: var(--text-secondary);
398 font-size: 0.875rem;
399 line-height: 1.6;
400 }
401
402 .feature-icon {
403 width: 44px;
404 height: 44px;
405 background: linear-gradient(135deg, var(--primary), var(--secondary));
406 border-radius: 10px;
407 display: flex;
408 align-items: center;
409 justify-content: center;
410 margin-bottom: 0.875rem;
411 padding: 10px;
412 }
413
414 .feature-icon.alt {
415 background: linear-gradient(135deg, var(--purple), var(--tertiary));
416 }
417
418 .feature-icon img {
419 width: 100%;
420 height: 100%;
421 }
422
423 /* Tech stack */
424 .tech-stack {
425 background: var(--surface-dark);
426 padding: 2rem;
427 border-radius: 1rem;
428 border: 1px solid var(--outline);
429 margin-bottom: 4rem;
430 }
431
432 .tech-stack h2 {
433 font-family: var(--font-display);
434 font-size: 1.5rem;
435 margin-bottom: 1.5rem;
436 color: var(--text-primary);
437 }
438
439 .tech-group {
440 margin-bottom: 1rem;
441 }
442
443 .tech-group:last-child {
444 margin-bottom: 0;
445 }
446
447 .tech-group-label {
448 font-size: 0.75rem;
449 font-weight: 600;
450 text-transform: uppercase;
451 letter-spacing: 0.08em;
452 color: var(--text-tertiary);
453 margin-bottom: 0.5rem;
454 }
455
456 .tech-list {
457 display: flex;
458 flex-wrap: wrap;
459 gap: 0.5rem;
460 }
461
462 .tech-tag {
463 background: var(--surface-variant);
464 padding: 0.4rem 0.875rem;
465 border-radius: 0.5rem;
466 font-size: 0.8125rem;
467 font-family: var(--font-mono);
468 color: var(--text-secondary);
469 border: 1px solid transparent;
470 transition: border-color 0.2s ease;
471 }
472
473 .tech-tag:hover {
474 border-color: var(--outline-bright);
475 }
476
477 .oauth-info {
478 background: var(--surface-variant);
479 padding: 1rem 1.5rem;
480 border-radius: 0.75rem;
481 border-left: 3px solid var(--primary);
482 margin-top: 2rem;
483 font-size: 0.875rem;
484 color: var(--text-secondary);
485 }
486
487 .oauth-info a {
488 color: var(--primary);
489 text-decoration: none;
490 font-weight: 500;
491 transition: color 0.2s ease;
492 }
493
494 .oauth-info a:hover {
495 color: var(--secondary);
496 text-decoration: underline;
497 }
498
499 footer {
500 text-align: center;
501 padding: 2rem;
502 color: var(--text-tertiary);
503 font-size: 0.875rem;
504 border-top: 1px solid var(--outline);
505 margin-top: 4rem;
506 }
507
508 footer a {
509 color: var(--primary);
510 text-decoration: none;
511 transition: color 0.2s ease;
512 }
513
514 footer a:hover {
515 color: var(--secondary);
516 }
517
518 @media (max-width: 768px) {
519 .logo {
520 font-size: 2.5rem;
521 }
522
523 .hero h1 {
524 font-size: 2rem;
525 }
526
527 .hero p {
528 font-size: 1rem;
529 }
530
531 .platforms {
532 grid-template-columns: 1fr;
533 }
534
535 .features {
536 grid-template-columns: 1fr;
537 }
538
539 .container {
540 padding: 1.5rem;
541 }
542 }
543 </style>
544 </head>
545
546 <body>
547 <div class="container">
548 <header>
549 <div class="logo"><span class="logo-icon"></span>Lazurite</div>
550 <p class="tagline">A better Bluesky client</p>
551 </header>
552
553 <main>
554 <div class="hero">
555 <div class="status-badge">
556 <span class="status-dot"></span>
557 <span>In Active Development</span>
558 </div>
559
560 <h1>Bluesky, everywhere you are</h1>
561 <p>
562 Lazurite is a native Bluesky client built for people who want more from their social experience.
563 A full-featured mobile app for iOS and Android, paired with a powerful desktop companion
564 for macOS, Windows, and Linux.
565 </p>
566 </div>
567
568 <div class="hero-screenshots">
569
570 <div class="hero-screenshot desktop-shot">
571 <!-- TODO: replace with actual desktop app screenshot (landscape, ~1280x800) -->
572 <img
573 src="./image.png"
574 alt="Lazurite desktop workspace screenshot" />
575 </div>
576 <div class="hero-screenshot phone">
577 <!-- TODO: replace with actual mobile app screenshot (portrait, ~390x844) -->
578 <img
579 src="https://placehold.co/720x1280/191919/33b1ff?text=Mobile%0AProfile&font=inter"
580 alt="Lazurite mobile profile screenshot" />
581 </div>
582 </div>
583
584 <div class="platforms">
585 <div class="platform">
586 <div class="platform-header">
587 <div class="platform-icon mobile">
588 <!-- TODO: add mobile device SVG icon (e.g. smartphone outline, white, 22x22) -->
589 <img src="static/smartphone.svg" alt="Mobile icon" />
590 </div>
591 <h2>Mobile</h2>
592 <span class="platform-badge beta">Beta</span>
593 </div>
594 <p class="platform-desc">
595 Material You design with offline support, smart folders, and full AT Protocol coverage.
596 Built with Flutter for a native feel on both platforms.
597 </p>
598 <div class="platform-targets">
599 <span class="target-tag">iOS</span>
600 <span class="target-tag">Android</span>
601 </div>
602 <ul class="platform-features">
603 <li>Offline-first with local caching and offline compose</li>
604 <li>Full DM support with conversation requests</li>
605 <li>Drafts, scheduled posts, and bookmarks</li>
606 <li>Content moderation with custom labelers</li>
607 <li>Multi-account switching with data isolation</li>
608 <li>Themeable: Oxocarbon, Catppuccin, Nord, Rosé Pine</li>
609 </ul>
610 <div class="platform-screenshot">
611 <!-- TODO: replace with actual mobile feed screenshot (~390x480 crop) -->
612 <img
613 src="https://placehold.co/720x1280/000000/7dafff?text=Feed+%2B+Compose&font=inter"
614 alt="Mobile feed and compose" />
615 <div class="caption">Your home feed and a composer button</div>
616 </div>
617 </div>
618
619 <div class="platform">
620 <div class="platform-header">
621 <div class="platform-icon desktop">
622 <img src="static/desktop.svg" alt="Desktop icon" />
623 </div>
624 <h2>Desktop</h2>
625 <span class="platform-badge alpha">Alpha</span>
626 </div>
627 <p class="platform-desc">
628 A native desktop experience built with Tauri and Solid.js. Semantic search
629 across your saved posts, multi-column layouts, and deep AT Protocol tooling.
630 </p>
631 <div class="platform-targets">
632 <span class="target-tag">macOS</span>
633 <span class="target-tag">Windows</span>
634 <span class="target-tag">Linux</span>
635 </div>
636 <ul class="platform-features">
637 <li>Full-text and vector search over saved and liked posts</li>
638 <li>Multi-column workspace layouts</li>
639 <li>PDS browser for exploring repository data</li>
640 <li>Composer window for distraction-free posting</li>
641 <li>Deep-link handling for <code>at://</code> URIs</li>
642 <li>Global shortcuts (post from anywhere)</li>
643 </ul>
644 <div class="platform-screenshot">
645 <!-- TODO: replace with actual desktop multi-column screenshot (~640x400 crop) -->
646 <img
647 src="./image.png"
648 alt="Desktop multi-column layout" />
649 <div class="caption">Multi-column workspace with search</div>
650 </div>
651 </div>
652 </div>
653
654 <h2 class="section-title" title="Shared Features">Across both platforms</h2>
655 <div class="features">
656 <div class="feature">
657 <div class="feature-icon">
658 <img src="static/feed.svg" alt="Feed icon" />
659 </div>
660 <h3>Feeds & Timelines</h3>
661 <p>Home timeline, custom algorithmic feeds, pinned feeds with drag-to-reorder. Full thread and reply chain rendering.</p>
662 </div>
663
664 <div class="feature">
665 <div class="feature-icon alt">
666 <img src="static/search.svg" alt="Search icon" />
667 </div>
668 <h3>Search & Discovery</h3>
669 <p>Search your saved & liked posts, discover starterpacks with the power of local semantic search with embeddings.</p>
670 </div>
671
672 <div class="feature">
673 <div class="feature-icon">
674 <img src="static/lock.svg" alt="Moderation icon" />
675 </div>
676 <h3>Moderation</h3>
677 <p>Subscribe to labelers, configure per-label preferences, mute and block lists. Adult content controls with granular filtering.</p>
678 </div>
679
680 <div class="feature">
681 <div class="feature-icon alt">
682 <img src="static/folder.svg" alt="Lists icon" />
683 </div>
684 <h3>Lists & Starter Packs</h3>
685 <p>Create and manage curation and moderation lists. Browse, search, and build starter packs to share with others.</p>
686 </div>
687
688 <div class="feature">
689 <div class="feature-icon">
690 <img src="static/offline.svg" alt="Offline icon" />
691 </div>
692 <h3>Offline & Fast</h3>
693 <p>Smart caching with local databases on both platforms. Browse cached content and queue actions while offline.</p>
694 </div>
695
696 <div class="feature">
697 <div class="feature-icon alt">
698 <img src="static/graph.svg" alt="Graph icon" />
699 </div>
700 <h3>Social Graph</h3>
701 <p>Explore connections with a force-directed graph visualization. See mutual follows, followers, and blocking context at a glance.</p>
702 </div>
703 </div>
704
705 <div class="tech-stack">
706 <h2>Built with</h2>
707 <div class="tech-group">
708 <div class="tech-group-label">Mobile</div>
709 <div class="tech-list">
710 <span class="tech-tag">Flutter</span>
711 <span class="tech-tag">Dart</span>
712 <span class="tech-tag">Material 3</span>
713 <span class="tech-tag">Drift</span>
714 <span class="tech-tag">BLoC</span>
715 </div>
716 </div>
717 <div class="tech-group">
718 <div class="tech-group-label">Desktop</div>
719 <div class="tech-list">
720 <span class="tech-tag">Tauri 2</span>
721 <span class="tech-tag">Rust</span>
722 <span class="tech-tag">Solid.js</span>
723 <span class="tech-tag">SQLite + FTS</span>
724 <span class="tech-tag">fastembed</span>
725 </div>
726 </div>
727 <div class="tech-group">
728 <div class="tech-group-label">Protocol</div>
729 <div class="tech-list">
730 <span class="tech-tag">AT Protocol</span>
731 <span class="tech-tag">OAuth 2.0 + DPoP</span>
732 <span class="tech-tag">Constellation</span>
733 </div>
734 </div>
735 </div>
736
737 <div class="oauth-info">
738 Follow development updates on Bluesky:
739 <a href="https://bsky.app/profile/desertthunder.dev" target="_blank" rel="noopener noreferrer"
740 >@desertthunder.dev</a
741 >
742 </div>
743 </main>
744 </div>
745
746 <footer>
747 <p>
748 Built with ⚡️ by
749 <a href="https://desertthunder.dev" target="_blank">Owais</a>
750 at
751 <a href="https://stormlightlabs.org" target="_blank">Stormlight Labs</a>
752 · Powered by the <a href="https://atproto.com" target="_blank">AT Protocol</a>
753 </p>
754 </footer>
755 </body>
756</html>