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>Notifications - Lazurite</title>
7 <link rel="preconnect" href="https://fonts.googleapis.com" />
8 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9 <link href="https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" rel="stylesheet" />
10 <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" />
11 <link rel="stylesheet" href="styles.css" />
12 <style>
13 .notif-container {
14 padding-bottom: 88px;
15 }
16
17 /* Day Group */
18 .notif-day-header {
19 padding: 12px 16px 8px;
20 font-size: 13px;
21 font-weight: 600;
22 color: var(--text-muted);
23 text-transform: uppercase;
24 letter-spacing: 0.5px;
25 background-color: var(--bg);
26 border-bottom: 1px solid var(--border);
27 }
28
29 /* Notification Item */
30 .notif-item {
31 display: flex;
32 gap: 12px;
33 padding: 14px 16px;
34 border-bottom: 1px solid var(--border);
35 cursor: pointer;
36 transition: background-color 0.2s ease;
37 }
38
39 .notif-item:hover {
40 background-color: var(--surface);
41 }
42
43 .notif-item.unread {
44 background-color: var(--surface);
45 }
46
47 .notif-item.unread::before {
48 content: "";
49 position: absolute;
50 left: 0;
51 top: 0;
52 bottom: 0;
53 width: 3px;
54 background-color: var(--accent-primary);
55 }
56
57 .notif-item {
58 position: relative;
59 }
60
61 /* Reason Icon */
62 .notif-icon {
63 width: 32px;
64 height: 32px;
65 border-radius: 50%;
66 display: flex;
67 align-items: center;
68 justify-content: center;
69 flex-shrink: 0;
70 }
71
72 .notif-icon svg {
73 width: 16px;
74 height: 16px;
75 }
76
77 .notif-icon-like {
78 background-color: rgba(239, 68, 68, 0.1);
79 color: var(--accent-error);
80 }
81
82 .notif-icon-repost {
83 background-color: rgba(34, 197, 94, 0.1);
84 color: var(--accent-success);
85 }
86
87 .notif-icon-follow {
88 background-color: rgba(0, 102, 255, 0.1);
89 color: var(--accent-primary);
90 }
91
92 .notif-icon-reply {
93 background-color: rgba(14, 165, 233, 0.1);
94 color: var(--accent-secondary);
95 }
96
97 .notif-icon-mention {
98 background-color: rgba(0, 102, 255, 0.1);
99 color: var(--accent-primary);
100 }
101
102 .notif-icon-quote {
103 background-color: rgba(139, 92, 246, 0.1);
104 color: #8b5cf6;
105 }
106
107 /* Content */
108 .notif-content {
109 flex: 1;
110 min-width: 0;
111 }
112
113 .notif-actor {
114 display: flex;
115 align-items: center;
116 gap: 8px;
117 margin-bottom: 4px;
118 }
119
120 .notif-actor-avatar {
121 width: 28px;
122 height: 28px;
123 border-radius: 50%;
124 background-color: var(--surface-variant);
125 display: flex;
126 align-items: center;
127 justify-content: center;
128 font-size: 11px;
129 font-weight: 600;
130 color: var(--text-secondary);
131 flex-shrink: 0;
132 }
133
134 .notif-summary {
135 font-size: 14px;
136 color: var(--text-primary);
137 line-height: 1.4;
138 }
139
140 .notif-summary strong {
141 font-weight: 600;
142 }
143
144 .notif-summary .reason {
145 color: var(--text-secondary);
146 }
147
148 .notif-time {
149 font-size: 12px;
150 color: var(--text-muted);
151 margin-top: 2px;
152 }
153
154 .notif-preview {
155 margin-top: 8px;
156 padding: 10px 12px;
157 background-color: var(--surface);
158 border-radius: 8px;
159 border: 1px solid var(--border);
160 font-size: 13px;
161 color: var(--text-secondary);
162 line-height: 1.4;
163 display: -webkit-box;
164 line-clamp: 2;
165 -webkit-line-clamp: 2;
166 -webkit-box-orient: vertical;
167 overflow: hidden;
168 }
169
170 /* Unread badge for nav */
171 .nav-item-badge {
172 position: absolute;
173 top: 2px;
174 right: 8px;
175 min-width: 18px;
176 height: 18px;
177 padding: 0 5px;
178 border-radius: 9px;
179 background-color: var(--accent-error);
180 color: white;
181 font-size: 10px;
182 font-weight: 700;
183 display: flex;
184 align-items: center;
185 justify-content: center;
186 }
187
188 .nav-item {
189 position: relative;
190 }
191 </style>
192 </head>
193 <body>
194 <div class="mobile-container">
195 <!-- Header -->
196 <header class="header">
197 <h1 class="header-title">Notifications</h1>
198 <button class="header-action">Mark All Read</button>
199 </header>
200
201 <div class="notif-container">
202 <!-- Today -->
203 <div class="notif-day-header">Today</div>
204
205 <!-- Like notification (unread) -->
206 <div class="notif-item unread">
207 <div class="notif-icon notif-icon-like">
208 <svg viewBox="0 0 24 24" fill="currentColor" stroke="none">
209 <path
210 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" />
211 </svg>
212 </div>
213 <div class="notif-content">
214 <div class="notif-actor">
215 <div class="notif-actor-avatar">AS</div>
216 </div>
217 <div class="notif-summary"><strong>Alice Smith</strong> <span class="reason">liked your post</span></div>
218 <div class="notif-time">12 minutes ago</div>
219 <div class="notif-preview">
220 Excited to share my latest project built with the AT Protocol! Check it out and let me know what you
221 think.
222 </div>
223 </div>
224 </div>
225
226 <!-- Follow notification (unread) -->
227 <div class="notif-item unread">
228 <div class="notif-icon notif-icon-follow">
229 <svg
230 viewBox="0 0 24 24"
231 fill="none"
232 stroke="currentColor"
233 stroke-width="2"
234 stroke-linecap="round"
235 stroke-linejoin="round">
236 <path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
237 <circle cx="8.5" cy="7" r="4" />
238 <line x1="20" y1="8" x2="20" y2="14" />
239 <line x1="23" y1="11" x2="17" y2="11" />
240 </svg>
241 </div>
242 <div class="notif-content">
243 <div class="notif-actor">
244 <div class="notif-actor-avatar">BJ</div>
245 </div>
246 <div class="notif-summary"><strong>Bob Johnson</strong> <span class="reason">followed you</span></div>
247 <div class="notif-time">45 minutes ago</div>
248 </div>
249 </div>
250
251 <!-- Reply notification (unread) -->
252 <div class="notif-item unread">
253 <div class="notif-icon notif-icon-reply">
254 <svg
255 viewBox="0 0 24 24"
256 fill="none"
257 stroke="currentColor"
258 stroke-width="2"
259 stroke-linecap="round"
260 stroke-linejoin="round">
261 <path
262 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" />
263 </svg>
264 </div>
265 <div class="notif-content">
266 <div class="notif-actor">
267 <div class="notif-actor-avatar">CW</div>
268 </div>
269 <div class="notif-summary">
270 <strong>Carol White</strong> <span class="reason">replied to your post</span>
271 </div>
272 <div class="notif-time">1 hour ago</div>
273 <div class="notif-preview">
274 This looks amazing! I've been working on something similar with the federation layer. Would love to
275 compare notes.
276 </div>
277 </div>
278 </div>
279
280 <!-- Repost notification -->
281 <div class="notif-item">
282 <div class="notif-icon notif-icon-repost">
283 <svg
284 viewBox="0 0 24 24"
285 fill="none"
286 stroke="currentColor"
287 stroke-width="2"
288 stroke-linecap="round"
289 stroke-linejoin="round">
290 <polyline points="17 1 21 5 17 9" />
291 <path d="M3 11V9a4 4 0 0 1 4-4h14" />
292 <polyline points="7 23 3 19 7 15" />
293 <path d="M21 13v2a4 4 0 0 1-4 4H3" />
294 </svg>
295 </div>
296 <div class="notif-content">
297 <div class="notif-actor">
298 <div class="notif-actor-avatar">DM</div>
299 </div>
300 <div class="notif-summary">
301 <strong>David Miller</strong> <span class="reason">reposted your post</span>
302 </div>
303 <div class="notif-time">3 hours ago</div>
304 <div class="notif-preview">Great read on the future of decentralised social networks</div>
305 </div>
306 </div>
307
308 <!-- Yesterday -->
309 <div class="notif-day-header">Yesterday</div>
310
311 <!-- Quote notification -->
312 <div class="notif-item">
313 <div class="notif-icon notif-icon-quote">
314 <svg
315 viewBox="0 0 24 24"
316 fill="none"
317 stroke="currentColor"
318 stroke-width="2"
319 stroke-linecap="round"
320 stroke-linejoin="round">
321 <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
322 </svg>
323 </div>
324 <div class="notif-content">
325 <div class="notif-actor">
326 <div class="notif-actor-avatar">EL</div>
327 </div>
328 <div class="notif-summary"><strong>Eva Lee</strong> <span class="reason">quoted your post</span></div>
329 <div class="notif-time">18 hours ago</div>
330 <div class="notif-preview">
331 Completely agree with this take. The open protocol approach is really paying off for the whole ecosystem.
332 </div>
333 </div>
334 </div>
335
336 <!-- Mention notification -->
337 <div class="notif-item">
338 <div class="notif-icon notif-icon-mention">
339 <svg
340 viewBox="0 0 24 24"
341 fill="none"
342 stroke="currentColor"
343 stroke-width="2"
344 stroke-linecap="round"
345 stroke-linejoin="round">
346 <circle cx="12" cy="12" r="4" />
347 <path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94" />
348 </svg>
349 </div>
350 <div class="notif-content">
351 <div class="notif-actor">
352 <div class="notif-actor-avatar">FG</div>
353 </div>
354 <div class="notif-summary"><strong>Frank Garcia</strong> <span class="reason">mentioned you</span></div>
355 <div class="notif-time">Yesterday</div>
356 <div class="notif-preview">
357 Hey @johndoe.bsky.social have you tried the new SDK features? They shipped some great improvements.
358 </div>
359 </div>
360 </div>
361
362 <!-- Like notification -->
363 <div class="notif-item">
364 <div class="notif-icon notif-icon-like">
365 <svg viewBox="0 0 24 24" fill="currentColor" stroke="none">
366 <path
367 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" />
368 </svg>
369 </div>
370 <div class="notif-content">
371 <div class="notif-actor">
372 <div class="notif-actor-avatar">GH</div>
373 </div>
374 <div class="notif-summary"><strong>Grace Huang</strong> <span class="reason">liked your post</span></div>
375 <div class="notif-time">Yesterday</div>
376 </div>
377 </div>
378 </div>
379
380 <!-- Bottom Navigation (5-tab layout) -->
381 <nav class="nav-bar">
382 <a href="home.html" class="nav-item">
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 d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
391 <polyline points="9 22 9 12 15 12 15 22" />
392 </svg>
393 <span>Home</span>
394 </a>
395
396 <a href="search.html" class="nav-item">
397 <svg
398 viewBox="0 0 24 24"
399 fill="none"
400 stroke="currentColor"
401 stroke-width="2"
402 stroke-linecap="round"
403 stroke-linejoin="round">
404 <circle cx="11" cy="11" r="8" />
405 <line x1="21" y1="21" x2="16.65" y2="16.65" />
406 </svg>
407 <span>Search</span>
408 </a>
409
410 <a href="notifications.html" class="nav-item active">
411 <svg
412 viewBox="0 0 24 24"
413 fill="none"
414 stroke="currentColor"
415 stroke-width="2"
416 stroke-linecap="round"
417 stroke-linejoin="round">
418 <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
419 <path d="M13.73 21a2 2 0 0 1-3.46 0" />
420 </svg>
421 <span class="nav-item-badge">3</span>
422 <span>Alerts</span>
423 </a>
424
425 <a href="messages.html" class="nav-item">
426 <svg
427 viewBox="0 0 24 24"
428 fill="none"
429 stroke="currentColor"
430 stroke-width="2"
431 stroke-linecap="round"
432 stroke-linejoin="round">
433 <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
434 </svg>
435 <span>Chat</span>
436 </a>
437
438 <a href="profile.html" class="nav-item">
439 <svg
440 viewBox="0 0 24 24"
441 fill="none"
442 stroke="currentColor"
443 stroke-width="2"
444 stroke-linecap="round"
445 stroke-linejoin="round">
446 <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
447 <circle cx="12" cy="7" r="4" />
448 </svg>
449 <span>Profile</span>
450 </a>
451 </nav>
452 </div>
453
454 <script>
455 if (localStorage.getItem("theme")) {
456 const t = localStorage.getItem("theme");
457 if (t !== "light") document.documentElement.setAttribute("data-theme", t);
458 }
459 </script>
460 </body>
461</html>