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.

at main 756 lines 22 kB view raw
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&eacute; 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 &amp; 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 &amp; 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 &amp; 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 &amp; 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 &#9889;&#65039; 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 &middot; Powered by the <a href="https://atproto.com" target="_blank">AT Protocol</a> 753 </p> 754 </footer> 755 </body> 756</html>