mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: update static site

+1020 -32
+5 -5
lib/features/devtools/cubit/dev_tools_cubit.dart
··· 19 19 abstract interface class DevToolsRepository { 20 20 Future<IdentityResolveHandleOutput> resolveHandle({required String handle}); 21 21 22 - Future<RepoDescribeRepoOutput> describeRepo({required String repo}); 22 + Future<RepoDescribeRepoOutput> describeRepo({required String repo, String? serviceHost}); 23 23 24 24 Future<List<ProfileViewBasic>> searchActorsTypeahead({required String query, int limit = 8}); 25 25 ··· 54 54 } 55 55 56 56 @override 57 - Future<RepoDescribeRepoOutput> describeRepo({required String repo}) async { 58 - final response = await _atproto.repo.describeRepo(repo: repo); 57 + Future<RepoDescribeRepoOutput> describeRepo({required String repo, String? serviceHost}) async { 58 + final response = await _atproto.repo.describeRepo(repo: repo, $service: serviceHost); 59 59 return response.data; 60 60 } 61 61 ··· 149 149 return; 150 150 } 151 151 152 - final repo = await _repository.describeRepo(repo: identity.did); 152 + final repo = await _repository.describeRepo(repo: identity.did, serviceHost: identity.pdsHost); 153 153 if (!_isActiveResolveRequest(resolveRequestId)) { 154 154 return; 155 155 } ··· 415 415 return; 416 416 } 417 417 418 - final repo = await _repository.describeRepo(repo: identity.did); 418 + final repo = await _repository.describeRepo(repo: identity.did, serviceHost: identity.pdsHost); 419 419 if (!_isActiveResolveRequest(resolveRequestId)) { 420 420 return; 421 421 }
+31 -25
test/features/devtools/cubit/dev_tools_cubit_test.dart
··· 18 18 }); 19 19 20 20 Future<IdentityResolveHandleOutput> Function({required String handle})? resolveHandleHandler; 21 - Future<RepoDescribeRepoOutput> Function({required String repo})? describeRepoHandler; 21 + Future<RepoDescribeRepoOutput> Function({required String repo, String? serviceHost})? describeRepoHandler; 22 22 Future<List<ProfileViewBasic>> Function({required String query, int limit})? searchActorsTypeaheadHandler; 23 23 Future<RepoListRecordsOutput> Function({ 24 24 required String repo, ··· 38 38 getRecordHandler; 39 39 40 40 @override 41 - Future<RepoDescribeRepoOutput> describeRepo({required String repo}) { 42 - return describeRepoHandler!.call(repo: repo); 41 + Future<RepoDescribeRepoOutput> describeRepo({required String repo, String? serviceHost}) { 42 + return describeRepoHandler!.call(repo: repo, serviceHost: serviceHost); 43 43 } 44 44 45 45 @override ··· 148 148 expect(handle, 'alice.bsky.social'); 149 149 return const IdentityResolveHandleOutput(did: 'did:plc:alice'); 150 150 }, 151 - describeRepoHandler: ({required String repo}) async => const RepoDescribeRepoOutput( 152 - handle: 'alice.bsky.social', 153 - did: 'did:plc:alice', 154 - didDoc: { 155 - 'service': [ 156 - {'id': '#atproto_pds', 'type': 'AtprotoPersonalDataServer', 'serviceEndpoint': 'https://alice.host'}, 157 - ], 158 - }, 159 - collections: ['app.bsky.feed.post'], 160 - handleIsCorrect: true, 161 - ), 151 + describeRepoHandler: ({required String repo, String? serviceHost}) async { 152 + expect(serviceHost, 'alice.host'); 153 + return const RepoDescribeRepoOutput( 154 + handle: 'alice.bsky.social', 155 + did: 'did:plc:alice', 156 + didDoc: { 157 + 'service': [ 158 + {'id': '#atproto_pds', 'type': 'AtprotoPersonalDataServer', 'serviceEndpoint': 'https://alice.host'}, 159 + ], 160 + }, 161 + collections: ['app.bsky.feed.post'], 162 + handleIsCorrect: true, 163 + ); 164 + }, 162 165 listRecordsHandler: 163 166 ({ 164 167 required String repo, ··· 210 213 final repository = FakeDevToolsRepository( 211 214 resolveHandleHandler: ({required String handle}) async => 212 215 const IdentityResolveHandleOutput(did: 'did:plc:alice'), 213 - describeRepoHandler: ({required String repo}) async => const RepoDescribeRepoOutput( 214 - handle: 'alice.bsky.social', 215 - did: 'did:plc:alice', 216 - didDoc: { 217 - 'service': [ 218 - {'id': '#atproto_pds', 'type': 'AtprotoPersonalDataServer', 'serviceEndpoint': 'https://alice.host'}, 219 - ], 220 - }, 221 - collections: ['app.bsky.feed.post'], 222 - handleIsCorrect: true, 223 - ), 216 + describeRepoHandler: ({required String repo, String? serviceHost}) async { 217 + expect(serviceHost, 'alice.host'); 218 + return const RepoDescribeRepoOutput( 219 + handle: 'alice.bsky.social', 220 + did: 'did:plc:alice', 221 + didDoc: { 222 + 'service': [ 223 + {'id': '#atproto_pds', 'type': 'AtprotoPersonalDataServer', 'serviceEndpoint': 'https://alice.host'}, 224 + ], 225 + }, 226 + collections: ['app.bsky.feed.post'], 227 + handleIsCorrect: true, 228 + ); 229 + }, 224 230 listRecordsHandler: 225 231 ({ 226 232 required String repo,
+335
www/csae-policy.html
··· 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 Child Safety Standards and CSAE Policy - effective May 3, 2026." /> 9 + <meta name="author" content="Stormlight Labs" /> 10 + <title>Lazurite CSAE Policy</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 + <style> 18 + :root { 19 + --bg-dark: #000000; 20 + --surface-dark: #191919; 21 + --surface-variant: #1f1f1f; 22 + --outline: rgba(255, 255, 255, 0.1); 23 + --text-primary: #f4f6fb; 24 + --text-secondary: #ababab; 25 + --text-tertiary: #6f6f6f; 26 + --primary: #7dafff; 27 + --secondary: #0073de; 28 + --tertiary: #33b1ff; 29 + --danger: #ff8080; 30 + --font-title: "Lora", serif; 31 + --font-display: "Google Sans", sans-serif; 32 + --font-body: "Inter", sans-serif; 33 + } 34 + 35 + * { 36 + margin: 0; 37 + padding: 0; 38 + box-sizing: border-box; 39 + } 40 + 41 + body { 42 + font-family: var(--font-body); 43 + background-color: var(--bg-dark); 44 + color: var(--text-primary); 45 + line-height: 1.6; 46 + min-height: 100vh; 47 + display: flex; 48 + flex-direction: column; 49 + } 50 + 51 + .container { 52 + max-width: 920px; 53 + margin: 0 auto; 54 + padding: 2rem; 55 + flex: 1; 56 + } 57 + 58 + .top-nav { 59 + display: flex; 60 + justify-content: flex-start; 61 + margin-bottom: 2rem; 62 + } 63 + 64 + .top-nav a { 65 + color: var(--primary); 66 + text-decoration: none; 67 + font-size: 0.875rem; 68 + } 69 + 70 + .top-nav a:hover { 71 + color: var(--secondary); 72 + } 73 + 74 + .logo { 75 + font-family: var(--font-title); 76 + font-size: 2.4rem; 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 + letter-spacing: -0.02em; 83 + display: inline-flex; 84 + align-items: center; 85 + gap: 0.5rem; 86 + } 87 + 88 + .logo-icon { 89 + width: 2rem; 90 + height: 2rem; 91 + mask-image: url("./static/logo.svg"); 92 + background-color: var(--primary); 93 + mask-repeat: no-repeat; 94 + mask-size: contain; 95 + mask-position: center; 96 + -webkit-mask-image: url("./static/logo.svg"); 97 + -webkit-mask-repeat: no-repeat; 98 + -webkit-mask-size: contain; 99 + -webkit-mask-position: center; 100 + } 101 + 102 + .legal-card { 103 + margin-top: 1.25rem; 104 + background: var(--surface-dark); 105 + border: 1px solid var(--outline); 106 + border-radius: 1rem; 107 + padding: 2rem; 108 + } 109 + 110 + h1 { 111 + font-family: var(--font-display); 112 + font-size: 2rem; 113 + line-height: 1.2; 114 + margin-bottom: 0.4rem; 115 + } 116 + 117 + .meta { 118 + font-size: 0.875rem; 119 + color: var(--text-tertiary); 120 + margin-bottom: 1.5rem; 121 + } 122 + 123 + .lead { 124 + color: var(--text-secondary); 125 + margin-bottom: 1.25rem; 126 + } 127 + 128 + .alert { 129 + margin-bottom: 1.5rem; 130 + background: rgba(255, 128, 128, 0.12); 131 + border: 1px solid rgba(255, 128, 128, 0.35); 132 + border-radius: 0.75rem; 133 + padding: 0.85rem 1rem; 134 + color: #ffd4d4; 135 + font-size: 0.95rem; 136 + } 137 + 138 + section { 139 + margin-bottom: 1.25rem; 140 + } 141 + 142 + section:last-of-type { 143 + margin-bottom: 0; 144 + } 145 + 146 + h2 { 147 + font-family: var(--font-display); 148 + font-size: 1.0625rem; 149 + color: var(--primary); 150 + margin-bottom: 0.45rem; 151 + } 152 + 153 + p { 154 + color: var(--text-secondary); 155 + font-size: 0.95rem; 156 + margin-bottom: 0.5rem; 157 + } 158 + 159 + ul { 160 + margin: 0.25rem 0 0.75rem 1.2rem; 161 + color: var(--text-secondary); 162 + font-size: 0.95rem; 163 + } 164 + 165 + li { 166 + margin-bottom: 0.35rem; 167 + } 168 + 169 + a { 170 + color: var(--primary); 171 + text-decoration: none; 172 + } 173 + 174 + a:hover { 175 + color: var(--secondary); 176 + } 177 + 178 + .links { 179 + margin-top: 1.25rem; 180 + background: var(--surface-variant); 181 + border: 1px solid var(--outline); 182 + border-radius: 0.75rem; 183 + padding: 1rem 1.2rem; 184 + } 185 + 186 + .links p { 187 + margin-bottom: 0.35rem; 188 + } 189 + 190 + .links p:last-child { 191 + margin-bottom: 0; 192 + } 193 + 194 + footer { 195 + text-align: center; 196 + padding: 2rem; 197 + color: var(--text-tertiary); 198 + font-size: 0.875rem; 199 + border-top: 1px solid var(--outline); 200 + margin-top: 2rem; 201 + } 202 + 203 + footer a { 204 + color: var(--primary); 205 + text-decoration: none; 206 + } 207 + 208 + footer a:hover { 209 + color: var(--secondary); 210 + } 211 + 212 + @media (max-width: 768px) { 213 + .container { 214 + padding: 1.25rem; 215 + } 216 + 217 + .legal-card { 218 + padding: 1.25rem; 219 + } 220 + 221 + h1 { 222 + font-size: 1.6rem; 223 + } 224 + } 225 + </style> 226 + </head> 227 + <body> 228 + <div class="container"> 229 + <nav class="top-nav"><a href="./index.html">Back to home</a></nav> 230 + <div class="logo"><span class="logo-icon"></span>Lazurite</div> 231 + <main class="legal-card"> 232 + <h1>Child Safety Standards (CSAE)</h1> 233 + <p class="meta">Effective May 3, 2026</p> 234 + <p class="lead"> 235 + This page describes the child safety standards for Lazurite by Stormlight Labs, including 236 + our policies against child sexual abuse and exploitation (CSAE), child sexual abuse 237 + material (CSAM), and related harmful behavior. 238 + </p> 239 + <p class="alert"> 240 + Zero tolerance: content or behavior involving child sexual abuse, exploitation, or 241 + endangerment is prohibited. 242 + </p> 243 + 244 + <section> 245 + <h2>1) Prohibited content and conduct</h2> 246 + <p>Lazurite standards prohibit, at minimum:</p> 247 + <ul> 248 + <li>Creation, sharing, solicitation, or promotion of CSAM.</li> 249 + <li>Grooming behavior targeting minors.</li> 250 + <li>Sextortion involving minors.</li> 251 + <li>Trafficking, solicitation, or sexual exploitation of children.</li> 252 + <li>Any use of Lazurite to endanger children.</li> 253 + </ul> 254 + </section> 255 + 256 + <section> 257 + <h2>2) In-app reporting and user feedback</h2> 258 + <p> 259 + Lazurite provides in-app pathways for users to report content, accounts, and behavior 260 + that may violate safety standards, including CSAE concerns. 261 + </p> 262 + <p> 263 + Reports are reviewed as a priority category and escalated when they indicate possible 264 + child endangerment or CSAM. 265 + </p> 266 + </section> 267 + 268 + <section> 269 + <h2>3) Response to potential CSAM</h2> 270 + <p> 271 + Upon obtaining actual knowledge of potential CSAM, we take appropriate action under our 272 + policies and applicable law. Actions may include: 273 + </p> 274 + <ul> 275 + <li>Removing or restricting access to violative content.</li> 276 + <li>Restricting or suspending access to violating accounts and features.</li> 277 + <li>Preserving records necessary for legal reporting and safety investigations.</li> 278 + </ul> 279 + </section> 280 + 281 + <section> 282 + <h2>4) Legal compliance and external reporting</h2> 283 + <p> 284 + We maintain procedures to comply with applicable child safety laws and regulations, 285 + including reporting confirmed CSAM to the National Center for Missing and Exploited 286 + Children (NCMEC) or the relevant regional authority as required by law. 287 + </p> 288 + <p> 289 + If you believe a child is in immediate danger, contact local law enforcement 290 + immediately. 291 + </p> 292 + </section> 293 + 294 + <section> 295 + <h2>5) Child safety point of contact</h2> 296 + <p> 297 + Child safety contact for Lazurite / Stormlight Labs: 298 + <a href="mailto:info@stormlightlabs.org">info@stormlightlabs.org</a> 299 + </p> 300 + <p> 301 + This contact is designated to receive and coordinate potential notifications related to 302 + CSAE and CSAM concerns. 303 + </p> 304 + </section> 305 + 306 + <section> 307 + <h2>6) Policy updates</h2> 308 + <p> 309 + We may update these standards as laws, platform requirements, and enforcement practices 310 + evolve. Material updates will be reflected by an updated effective date. 311 + </p> 312 + </section> 313 + 314 + <div class="links"> 315 + <p> 316 + Related pages: <a href="./terms.html">Terms of Service</a> and 317 + <a href="./privacy.html">Privacy Policy</a>. 318 + </p> 319 + <p> 320 + Contact: 321 + <a href="https://stormlightlabs.org" target="_blank" rel="noopener noreferrer">stormlightlabs.org</a> 322 + or <a href="mailto:info@stormlightlabs.org">info@stormlightlabs.org</a> 323 + </p> 324 + </div> 325 + </main> 326 + </div> 327 + <footer> 328 + <p> 329 + Built by <a href="https://stormlightlabs.org" target="_blank" rel="noopener noreferrer">Stormlight Labs</a> 330 + &middot; <a href="./privacy.html">Privacy Policy</a> 331 + &middot; <a href="./terms.html">Terms of Service</a> 332 + </p> 333 + </footer> 334 + </body> 335 + </html>
www/image.png www/static/desktop-deck.png
+5 -2
www/index.html
··· 570 570 <div class="hero-screenshot desktop-shot"> 571 571 <!-- TODO: replace with actual desktop app screenshot (landscape, ~1280x800) --> 572 572 <img 573 - src="./image.png" 573 + src="./static/desktop-deck.png" 574 574 alt="Lazurite desktop workspace screenshot" /> 575 575 </div> 576 576 <div class="hero-screenshot phone"> ··· 644 644 <div class="platform-screenshot"> 645 645 <!-- TODO: replace with actual desktop multi-column screenshot (~640x400 crop) --> 646 646 <img 647 - src="./image.png" 647 + src="./static/desktop-deck.png" 648 648 alt="Desktop multi-column layout" /> 649 649 <div class="caption">Multi-column workspace with search</div> 650 650 </div> ··· 750 750 at 751 751 <a href="https://stormlightlabs.org" target="_blank">Stormlight Labs</a> 752 752 &middot; Powered by the <a href="https://atproto.com" target="_blank">AT Protocol</a> 753 + &middot; <a href="./csae-policy.html">Child Safety Standards</a> 754 + &middot; <a href="./privacy.html">Privacy Policy</a> 755 + &middot; <a href="./terms.html">Terms of Service</a> 753 756 </p> 754 757 </footer> 755 758 </body>
+310
www/privacy.html
··· 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 Privacy Policy - effective April 15, 2026." /> 9 + <meta name="author" content="Stormlight Labs" /> 10 + <title>Lazurite Privacy Policy</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 + <style> 18 + :root { 19 + --bg-dark: #000000; 20 + --surface-dark: #191919; 21 + --surface-variant: #1f1f1f; 22 + --outline: rgba(255, 255, 255, 0.1); 23 + --outline-bright: rgba(255, 255, 255, 0.2); 24 + --text-primary: #f4f6fb; 25 + --text-secondary: #ababab; 26 + --text-tertiary: #6f6f6f; 27 + --primary: #7dafff; 28 + --secondary: #0073de; 29 + --tertiary: #33b1ff; 30 + --font-title: "Lora", serif; 31 + --font-display: "Google Sans", sans-serif; 32 + --font-body: "Inter", sans-serif; 33 + --font-mono: "Google Sans Code", monospace; 34 + } 35 + 36 + * { 37 + margin: 0; 38 + padding: 0; 39 + box-sizing: border-box; 40 + } 41 + 42 + body { 43 + font-family: var(--font-body); 44 + background-color: var(--bg-dark); 45 + color: var(--text-primary); 46 + line-height: 1.6; 47 + min-height: 100vh; 48 + display: flex; 49 + flex-direction: column; 50 + } 51 + 52 + .container { 53 + max-width: 920px; 54 + margin: 0 auto; 55 + padding: 2rem; 56 + flex: 1; 57 + } 58 + 59 + .top-nav { 60 + display: flex; 61 + justify-content: flex-start; 62 + margin-bottom: 2rem; 63 + } 64 + 65 + .top-nav a { 66 + color: var(--primary); 67 + text-decoration: none; 68 + font-size: 0.875rem; 69 + } 70 + 71 + .top-nav a:hover { 72 + color: var(--secondary); 73 + } 74 + 75 + .logo { 76 + font-family: var(--font-title); 77 + font-size: 2.4rem; 78 + font-weight: 700; 79 + background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 50%, var(--tertiary) 100%); 80 + -webkit-background-clip: text; 81 + -webkit-text-fill-color: transparent; 82 + background-clip: text; 83 + letter-spacing: -0.02em; 84 + display: inline-flex; 85 + align-items: center; 86 + gap: 0.5rem; 87 + } 88 + 89 + .logo-icon { 90 + width: 2rem; 91 + height: 2rem; 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 + -webkit-mask-image: url("./static/logo.svg"); 98 + -webkit-mask-repeat: no-repeat; 99 + -webkit-mask-size: contain; 100 + -webkit-mask-position: center; 101 + } 102 + 103 + .legal-card { 104 + margin-top: 1.25rem; 105 + background: var(--surface-dark); 106 + border: 1px solid var(--outline); 107 + border-radius: 1rem; 108 + padding: 2rem; 109 + } 110 + 111 + h1 { 112 + font-family: var(--font-display); 113 + font-size: 2rem; 114 + line-height: 1.2; 115 + margin-bottom: 0.4rem; 116 + } 117 + 118 + .meta { 119 + font-size: 0.875rem; 120 + color: var(--text-tertiary); 121 + margin-bottom: 1.5rem; 122 + } 123 + 124 + .lead { 125 + color: var(--text-secondary); 126 + margin-bottom: 1.5rem; 127 + } 128 + 129 + section { 130 + margin-bottom: 1.25rem; 131 + } 132 + 133 + section:last-of-type { 134 + margin-bottom: 0; 135 + } 136 + 137 + h2 { 138 + font-family: var(--font-display); 139 + font-size: 1.0625rem; 140 + color: var(--primary); 141 + margin-bottom: 0.45rem; 142 + } 143 + 144 + p { 145 + color: var(--text-secondary); 146 + font-size: 0.95rem; 147 + margin-bottom: 0.5rem; 148 + } 149 + 150 + a { 151 + color: var(--primary); 152 + text-decoration: none; 153 + } 154 + 155 + a:hover { 156 + color: var(--secondary); 157 + } 158 + 159 + .links { 160 + margin-top: 1.25rem; 161 + background: var(--surface-variant); 162 + border: 1px solid var(--outline); 163 + border-radius: 0.75rem; 164 + padding: 1rem 1.2rem; 165 + } 166 + 167 + .links p { 168 + margin-bottom: 0; 169 + } 170 + 171 + footer { 172 + text-align: center; 173 + padding: 2rem; 174 + color: var(--text-tertiary); 175 + font-size: 0.875rem; 176 + border-top: 1px solid var(--outline); 177 + margin-top: 2rem; 178 + } 179 + 180 + footer a { 181 + color: var(--primary); 182 + text-decoration: none; 183 + } 184 + 185 + footer a:hover { 186 + color: var(--secondary); 187 + } 188 + 189 + @media (max-width: 768px) { 190 + .container { 191 + padding: 1.25rem; 192 + } 193 + 194 + .legal-card { 195 + padding: 1.25rem; 196 + } 197 + 198 + h1 { 199 + font-size: 1.6rem; 200 + } 201 + } 202 + </style> 203 + </head> 204 + <body> 205 + <div class="container"> 206 + <nav class="top-nav"><a href="./index.html">Back to home</a></nav> 207 + <div class="logo"><span class="logo-icon"></span>Lazurite</div> 208 + <main class="legal-card"> 209 + <h1>Privacy Policy</h1> 210 + <p class="meta">Effective April 15, 2026</p> 211 + <p class="lead"> 212 + Lazurite is a client for Bluesky. Core app behavior runs from your device, and we do not 213 + operate a developer backend for normal use. 214 + </p> 215 + 216 + <section> 217 + <h2>What the app stores on your device</h2> 218 + <p> 219 + Lazurite stores account session data, settings, cached content, and other app data 220 + locally so the app can work quickly and reliably. 221 + </p> 222 + <p> 223 + This local data can include profile metadata, viewed posts, follows, lists, likes, 224 + drafts, and media caches. 225 + </p> 226 + </section> 227 + 228 + <section> 229 + <h2>How your data is used</h2> 230 + <p> 231 + Local data is used to keep you signed in, remember your preferences, improve loading 232 + performance, and support offline-friendly behavior. 233 + </p> 234 + <p>Lazurite does not sell your personal information.</p> 235 + </section> 236 + 237 + <section> 238 + <h2>Network requests and third parties</h2> 239 + <p> 240 + When you use Lazurite, requests are sent directly from your device to Bluesky and 241 + related infrastructure. 242 + </p> 243 + <p> 244 + Your use of Bluesky remains subject to Bluesky policies, terms, and moderation systems. 245 + </p> 246 + </section> 247 + 248 + <section> 249 + <h2>Permissions</h2> 250 + <p> 251 + If you choose to save media, Lazurite requests photo or storage permissions required by 252 + your platform. 253 + </p> 254 + <p>Permissions are used only for the feature you invoke.</p> 255 + </section> 256 + 257 + <section> 258 + <h2>Diagnostics and logs</h2> 259 + <p> 260 + Lazurite keeps local app logs to help troubleshoot issues. These logs stay on your 261 + device unless you choose to share them. 262 + </p> 263 + <p>Lazurite does not include ad tracking SDKs.</p> 264 + </section> 265 + 266 + <section> 267 + <h2>Data retention and control</h2> 268 + <p> 269 + Data remains on your device until you remove it by signing out, clearing app storage, 270 + or uninstalling the app. 271 + </p> 272 + <p> 273 + Because Lazurite does not run a central app backend for normal use, most data-control 274 + actions happen on your device or through Bluesky account settings. 275 + </p> 276 + </section> 277 + 278 + <section> 279 + <h2>Children</h2> 280 + <p> 281 + Lazurite is not directed to children under 13, or under the minimum age required in 282 + your jurisdiction. 283 + </p> 284 + </section> 285 + 286 + <section> 287 + <h2>Policy updates</h2> 288 + <p> 289 + We may revise this policy from time to time. Material updates will be reflected by a 290 + new effective date and app release notes when appropriate. 291 + </p> 292 + </section> 293 + 294 + <div class="links"> 295 + <p> 296 + Contact: <a href="https://stormlightlabs.org" target="_blank" rel="noopener noreferrer">stormlightlabs.org</a> 297 + or <a href="mailto:info@stormlightlabs.org">info@stormlightlabs.org</a> 298 + </p> 299 + </div> 300 + </main> 301 + </div> 302 + <footer> 303 + <p> 304 + Built by <a href="https://stormlightlabs.org" target="_blank" rel="noopener noreferrer">Stormlight Labs</a> 305 + &middot; <a href="./csae-policy.html">Child Safety Standards</a> 306 + &middot; <a href="./terms.html">Terms of Service</a> 307 + </p> 308 + </footer> 309 + </body> 310 + </html>
+334
www/terms.html
··· 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 Terms of Service - effective April 15, 2026." /> 9 + <meta name="author" content="Stormlight Labs" /> 10 + <title>Lazurite Terms of Service</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 + <style> 18 + :root { 19 + --bg-dark: #000000; 20 + --surface-dark: #191919; 21 + --surface-variant: #1f1f1f; 22 + --outline: rgba(255, 255, 255, 0.1); 23 + --text-primary: #f4f6fb; 24 + --text-secondary: #ababab; 25 + --text-tertiary: #6f6f6f; 26 + --primary: #7dafff; 27 + --secondary: #0073de; 28 + --tertiary: #33b1ff; 29 + --font-title: "Lora", serif; 30 + --font-display: "Google Sans", sans-serif; 31 + --font-body: "Inter", sans-serif; 32 + } 33 + 34 + * { 35 + margin: 0; 36 + padding: 0; 37 + box-sizing: border-box; 38 + } 39 + 40 + body { 41 + font-family: var(--font-body); 42 + background-color: var(--bg-dark); 43 + color: var(--text-primary); 44 + line-height: 1.6; 45 + min-height: 100vh; 46 + display: flex; 47 + flex-direction: column; 48 + } 49 + 50 + .container { 51 + max-width: 920px; 52 + margin: 0 auto; 53 + padding: 2rem; 54 + flex: 1; 55 + } 56 + 57 + .top-nav { 58 + display: flex; 59 + justify-content: flex-start; 60 + margin-bottom: 2rem; 61 + } 62 + 63 + .top-nav a { 64 + color: var(--primary); 65 + text-decoration: none; 66 + font-size: 0.875rem; 67 + } 68 + 69 + .top-nav a:hover { 70 + color: var(--secondary); 71 + } 72 + 73 + .logo { 74 + font-family: var(--font-title); 75 + font-size: 2.4rem; 76 + font-weight: 700; 77 + background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 50%, var(--tertiary) 100%); 78 + -webkit-background-clip: text; 79 + -webkit-text-fill-color: transparent; 80 + background-clip: text; 81 + letter-spacing: -0.02em; 82 + display: inline-flex; 83 + align-items: center; 84 + gap: 0.5rem; 85 + } 86 + 87 + .logo-icon { 88 + width: 2rem; 89 + height: 2rem; 90 + mask-image: url("./static/logo.svg"); 91 + background-color: var(--primary); 92 + mask-repeat: no-repeat; 93 + mask-size: contain; 94 + mask-position: center; 95 + -webkit-mask-image: url("./static/logo.svg"); 96 + -webkit-mask-repeat: no-repeat; 97 + -webkit-mask-size: contain; 98 + -webkit-mask-position: center; 99 + } 100 + 101 + .legal-card { 102 + margin-top: 1.25rem; 103 + background: var(--surface-dark); 104 + border: 1px solid var(--outline); 105 + border-radius: 1rem; 106 + padding: 2rem; 107 + } 108 + 109 + h1 { 110 + font-family: var(--font-display); 111 + font-size: 2rem; 112 + line-height: 1.2; 113 + margin-bottom: 0.4rem; 114 + } 115 + 116 + .meta { 117 + font-size: 0.875rem; 118 + color: var(--text-tertiary); 119 + margin-bottom: 1.5rem; 120 + } 121 + 122 + .lead { 123 + color: var(--text-secondary); 124 + margin-bottom: 1.5rem; 125 + } 126 + 127 + section { 128 + margin-bottom: 1.25rem; 129 + } 130 + 131 + section:last-of-type { 132 + margin-bottom: 0; 133 + } 134 + 135 + h2 { 136 + font-family: var(--font-display); 137 + font-size: 1.0625rem; 138 + color: var(--primary); 139 + margin-bottom: 0.45rem; 140 + } 141 + 142 + p { 143 + color: var(--text-secondary); 144 + font-size: 0.95rem; 145 + margin-bottom: 0.5rem; 146 + } 147 + 148 + a { 149 + color: var(--primary); 150 + text-decoration: none; 151 + } 152 + 153 + a:hover { 154 + color: var(--secondary); 155 + } 156 + 157 + .links { 158 + margin-top: 1.25rem; 159 + background: var(--surface-variant); 160 + border: 1px solid var(--outline); 161 + border-radius: 0.75rem; 162 + padding: 1rem 1.2rem; 163 + } 164 + 165 + .links p { 166 + margin-bottom: 0.35rem; 167 + } 168 + 169 + .links p:last-child { 170 + margin-bottom: 0; 171 + } 172 + 173 + footer { 174 + text-align: center; 175 + padding: 2rem; 176 + color: var(--text-tertiary); 177 + font-size: 0.875rem; 178 + border-top: 1px solid var(--outline); 179 + margin-top: 2rem; 180 + } 181 + 182 + footer a { 183 + color: var(--primary); 184 + text-decoration: none; 185 + } 186 + 187 + footer a:hover { 188 + color: var(--secondary); 189 + } 190 + 191 + @media (max-width: 768px) { 192 + .container { 193 + padding: 1.25rem; 194 + } 195 + 196 + .legal-card { 197 + padding: 1.25rem; 198 + } 199 + 200 + h1 { 201 + font-size: 1.6rem; 202 + } 203 + } 204 + </style> 205 + </head> 206 + <body> 207 + <div class="container"> 208 + <nav class="top-nav"><a href="./index.html">Back to home</a></nav> 209 + <div class="logo"><span class="logo-icon"></span>Lazurite</div> 210 + <main class="legal-card"> 211 + <h1>Terms of Service</h1> 212 + <p class="meta">Effective April 15, 2026</p> 213 + <p class="lead"> 214 + These Terms govern your use of Lazurite, a Bluesky client built by Stormlight Labs. By 215 + installing or using Lazurite, you agree to these Terms. 216 + </p> 217 + 218 + <section> 219 + <h2>What Lazurite is</h2> 220 + <p>Lazurite is client software that helps you access Bluesky from your device.</p> 221 + <p>Lazurite does not provide the Bluesky network itself.</p> 222 + </section> 223 + 224 + <section> 225 + <h2>Eligibility</h2> 226 + <p> 227 + You must be legally able to agree to these Terms and meet the minimum age requirement 228 + in your jurisdiction. 229 + </p> 230 + </section> 231 + 232 + <section> 233 + <h2>License</h2> 234 + <p> 235 + We grant you a limited, non-exclusive, non-transferable, revocable license to use 236 + Lazurite on devices you control. 237 + </p> 238 + <p> 239 + You may not misuse the app, break applicable law, or attempt unauthorized access to 240 + systems or accounts. 241 + </p> 242 + </section> 243 + 244 + <section> 245 + <h2>Bluesky terms and policies</h2> 246 + <p> 247 + Lazurite depends on Bluesky services, and your use of Bluesky is governed by Bluesky's 248 + own legal terms. 249 + </p> 250 + </section> 251 + 252 + <section> 253 + <h2>Your account and activity</h2> 254 + <p> 255 + You are responsible for your Bluesky account, credentials, and activity taken through 256 + Lazurite. 257 + </p> 258 + <p> 259 + You retain rights to your content, subject to Bluesky policies and any third-party 260 + rights. 261 + </p> 262 + </section> 263 + 264 + <section> 265 + <h2>Acceptable use</h2> 266 + <p> 267 + Do not use Lazurite to harass others, violate rights, distribute unlawful content, or 268 + abuse platform infrastructure. 269 + </p> 270 + <p>You are responsible for complying with Bluesky rules and applicable law.</p> 271 + </section> 272 + 273 + <section> 274 + <h2>Third-party dependencies</h2> 275 + <p>Lazurite depends on Bluesky and related third-party services.</p> 276 + <p> 277 + If those services change, restrict, or discontinue access, features may degrade or stop 278 + working. 279 + </p> 280 + </section> 281 + 282 + <section> 283 + <h2>No warranty</h2> 284 + <p> 285 + Lazurite is provided "as is" and "as available." We do not guarantee uninterrupted, 286 + secure, or error-free operation. 287 + </p> 288 + </section> 289 + 290 + <section> 291 + <h2>Liability</h2> 292 + <p> 293 + To the maximum extent permitted by law, Stormlight Labs is not liable for indirect, 294 + incidental, or consequential damages arising from your use of Lazurite. 295 + </p> 296 + </section> 297 + 298 + <section> 299 + <h2>Changes and termination</h2> 300 + <p>We may update, suspend, or discontinue parts of Lazurite.</p> 301 + <p> 302 + We may update these Terms. Continued use after updates means you accept the revised 303 + Terms. 304 + </p> 305 + </section> 306 + 307 + <div class="links"> 308 + <p> 309 + Bluesky legal pages: 310 + <a href="https://bsky.social/about/support/privacy-policy" target="_blank" rel="noopener noreferrer" 311 + >Privacy Policy</a 312 + > 313 + and 314 + <a href="https://bsky.social/about/support/tos" target="_blank" rel="noopener noreferrer" 315 + >Terms of Service</a 316 + >. 317 + </p> 318 + <p> 319 + Contact: 320 + <a href="https://stormlightlabs.org" target="_blank" rel="noopener noreferrer">stormlightlabs.org</a> 321 + or <a href="mailto:info@stormlightlabs.org">info@stormlightlabs.org</a> 322 + </p> 323 + </div> 324 + </main> 325 + </div> 326 + <footer> 327 + <p> 328 + Built by <a href="https://stormlightlabs.org" target="_blank" rel="noopener noreferrer">Stormlight Labs</a> 329 + &middot; <a href="./csae-policy.html">Child Safety Standards</a> 330 + &middot; <a href="./privacy.html">Privacy Policy</a> 331 + </p> 332 + </footer> 333 + </body> 334 + </html>