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>Semantic Search - 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
11 href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap"
12 rel="stylesheet" />
13 <link rel="stylesheet" href="styles.css" />
14 <style>
15 .saved-container {
16 padding-bottom: 88px;
17 }
18
19 /* Tab bar under header */
20 .saved-tabs {
21 display: flex;
22 border-bottom: 1px solid var(--border);
23 background-color: var(--bg);
24 }
25
26 .saved-tab {
27 flex: 1;
28 padding: 14px;
29 text-align: center;
30 font-weight: 600;
31 font-size: 15px;
32 color: var(--text-secondary);
33 cursor: pointer;
34 border-bottom: 2px solid transparent;
35 transition: all 0.2s ease;
36 background: none;
37 border-top: none;
38 border-left: none;
39 border-right: none;
40 }
41
42 .saved-tab:hover {
43 background-color: var(--surface);
44 color: var(--text-primary);
45 }
46
47 .saved-tab.active {
48 color: var(--text-primary);
49 border-bottom-color: var(--accent-primary);
50 }
51
52 /* Search input area */
53 .semantic-search-bar {
54 padding: 12px 16px;
55 border-bottom: 1px solid var(--border);
56 }
57
58 .semantic-input-wrapper {
59 position: relative;
60 }
61
62 .semantic-input-wrapper svg {
63 position: absolute;
64 left: 12px;
65 top: 50%;
66 transform: translateY(-50%);
67 width: 18px;
68 height: 18px;
69 color: var(--text-muted);
70 }
71
72 .semantic-input {
73 width: 100%;
74 padding: 10px 12px 10px 38px;
75 border: 1px solid var(--border);
76 border-radius: 9999px;
77 background-color: var(--surface);
78 color: var(--text-primary);
79 font-size: 15px;
80 font-family: inherit;
81 transition: border-color 0.2s ease;
82 }
83
84 .semantic-input:focus {
85 outline: none;
86 border-color: var(--accent-primary);
87 }
88
89 .semantic-input::placeholder {
90 color: var(--text-muted);
91 }
92
93 /* Scope chips */
94 .scope-chips {
95 display: flex;
96 gap: 8px;
97 padding: 10px 16px;
98 border-bottom: 1px solid var(--border);
99 }
100
101 .scope-chip {
102 padding: 6px 14px;
103 border-radius: 9999px;
104 font-size: 13px;
105 font-weight: 500;
106 border: 1.5px solid var(--border);
107 background: none;
108 color: var(--text-secondary);
109 cursor: pointer;
110 transition: all 0.2s ease;
111 font-family: inherit;
112 }
113
114 .scope-chip:hover {
115 border-color: var(--accent-primary);
116 color: var(--accent-primary);
117 }
118
119 .scope-chip.active {
120 background-color: var(--accent-primary);
121 border-color: var(--accent-primary);
122 color: white;
123 }
124
125 /* Relevance badge */
126 .relevance-badge {
127 display: inline-flex;
128 align-items: center;
129 gap: 4px;
130 padding: 2px 8px;
131 border-radius: 9999px;
132 font-size: 11px;
133 font-weight: 600;
134 font-family: var(--font-mono);
135 white-space: nowrap;
136 }
137
138 .relevance-high {
139 background-color: rgba(34, 197, 94, 0.15);
140 color: var(--accent-success);
141 }
142
143 .relevance-medium {
144 background-color: rgba(245, 158, 11, 0.15);
145 color: var(--accent-warning);
146 }
147
148 .relevance-low {
149 background-color: rgba(168, 168, 168, 0.15);
150 color: var(--text-muted);
151 }
152
153 /* Source tag */
154 .source-tag {
155 display: inline-flex;
156 align-items: center;
157 gap: 4px;
158 padding: 2px 8px;
159 border-radius: 4px;
160 font-size: 11px;
161 font-weight: 500;
162 background-color: var(--surface-variant);
163 color: var(--text-secondary);
164 }
165
166 .source-tag svg {
167 width: 12px;
168 height: 12px;
169 }
170
171 /* Result meta row */
172 .result-meta {
173 display: flex;
174 align-items: center;
175 gap: 8px;
176 margin-bottom: 8px;
177 }
178
179 /* Indexing progress bar */
180 .indexing-bar {
181 padding: 12px 16px;
182 background-color: var(--surface);
183 border-bottom: 1px solid var(--border);
184 display: flex;
185 align-items: center;
186 gap: 12px;
187 }
188
189 .indexing-bar-text {
190 font-size: 13px;
191 color: var(--text-secondary);
192 white-space: nowrap;
193 }
194
195 .indexing-progress {
196 flex: 1;
197 height: 4px;
198 background-color: var(--surface-variant);
199 border-radius: 2px;
200 overflow: hidden;
201 }
202
203 .indexing-progress-fill {
204 height: 100%;
205 background-color: var(--accent-primary);
206 border-radius: 2px;
207 transition: width 0.3s ease;
208 }
209
210 .indexing-count {
211 font-size: 12px;
212 font-family: var(--font-mono);
213 color: var(--text-muted);
214 white-space: nowrap;
215 }
216
217 /* Settings-specific styles */
218 .settings-section {
219 margin-bottom: 24px;
220 }
221
222 .settings-section-title {
223 padding: 16px;
224 font-size: 13px;
225 font-weight: 600;
226 color: var(--text-muted);
227 text-transform: uppercase;
228 letter-spacing: 0.5px;
229 }
230
231 .settings-group {
232 background-color: var(--surface);
233 border-top: 1px solid var(--border);
234 border-bottom: 1px solid var(--border);
235 }
236
237 .slider-row {
238 display: flex;
239 align-items: center;
240 gap: 12px;
241 padding: 0 16px 16px;
242 }
243
244 .slider-track {
245 flex: 1;
246 height: 4px;
247 background-color: var(--surface-variant);
248 border-radius: 2px;
249 position: relative;
250 }
251
252 .slider-fill {
253 position: absolute;
254 height: 100%;
255 background-color: var(--accent-primary);
256 border-radius: 2px;
257 width: 25%;
258 }
259
260 .slider-thumb {
261 position: absolute;
262 top: -6px;
263 left: 25%;
264 transform: translateX(-50%);
265 width: 16px;
266 height: 16px;
267 border-radius: 50%;
268 background-color: var(--accent-primary);
269 border: 2px solid white;
270 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
271 }
272
273 .slider-value {
274 font-size: 14px;
275 font-weight: 600;
276 font-family: var(--font-mono);
277 color: var(--text-primary);
278 min-width: 24px;
279 text-align: right;
280 }
281
282 .slider-labels {
283 display: flex;
284 justify-content: space-between;
285 padding: 0 16px 12px;
286 }
287
288 .slider-label {
289 font-size: 11px;
290 color: var(--text-muted);
291 }
292
293 /* Unavailable banner */
294 .unavailable-banner {
295 margin: 16px;
296 padding: 16px;
297 background-color: var(--surface);
298 border: 1px solid var(--border);
299 border-radius: 12px;
300 display: flex;
301 align-items: flex-start;
302 gap: 12px;
303 }
304
305 .unavailable-banner svg {
306 width: 24px;
307 height: 24px;
308 color: var(--accent-warning);
309 flex-shrink: 0;
310 margin-top: 2px;
311 }
312
313 .unavailable-banner-text {
314 font-size: 14px;
315 color: var(--text-secondary);
316 line-height: 1.5;
317 }
318
319 .unavailable-banner-title {
320 font-weight: 600;
321 color: var(--text-primary);
322 margin-bottom: 4px;
323 }
324
325 /* View switcher */
326 .view-switcher {
327 display: flex;
328 gap: 12px;
329 padding: 16px;
330 justify-content: center;
331 }
332
333 .view-switcher-btn {
334 padding: 8px 20px;
335 border-radius: 9999px;
336 font-size: 13px;
337 font-weight: 600;
338 border: 1.5px solid var(--border);
339 background: none;
340 color: var(--text-secondary);
341 cursor: pointer;
342 transition: all 0.2s ease;
343 font-family: inherit;
344 }
345
346 .view-switcher-btn:hover {
347 border-color: var(--accent-primary);
348 color: var(--accent-primary);
349 }
350
351 .view-switcher-btn.active-view {
352 background-color: var(--accent-primary);
353 border-color: var(--accent-primary);
354 color: white;
355 }
356 </style>
357 </head>
358 <body>
359 <div class="mobile-container">
360 <!-- Header -->
361 <header class="header">
362 <div style="display: flex; align-items: center; gap: 12px">
363 <button
364 style="background: none; border: none; color: var(--text-primary); cursor: pointer; padding: 4px"
365 title="Back">
366 <svg
367 width="24"
368 height="24"
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="15 18 9 12 15 6" />
376 </svg>
377 </button>
378 <h1 class="header-title">Saved Posts</h1>
379 </div>
380 </header>
381
382 <!-- Tabs: All Saved / Search -->
383 <div class="saved-tabs">
384 <button class="saved-tab" data-view="all">All Saved</button>
385 <button class="saved-tab active" data-view="search">Search</button>
386 </div>
387
388 <!-- ==================== -->
389 <!-- VIEW: SEARCH (active) -->
390 <!-- ==================== -->
391 <div class="saved-container" id="view-search">
392 <!-- Search input -->
393 <div class="semantic-search-bar">
394 <div class="semantic-input-wrapper">
395 <svg
396 viewBox="0 0 24 24"
397 fill="none"
398 stroke="currentColor"
399 stroke-width="2"
400 stroke-linecap="round"
401 stroke-linejoin="round">
402 <circle cx="11" cy="11" r="8" />
403 <line x1="21" y1="21" x2="16.65" y2="16.65" />
404 </svg>
405 <input class="semantic-input" type="text" placeholder="Search your saved posts..." value="federation decentralized protocol" />
406 </div>
407 </div>
408
409 <!-- Scope chips -->
410 <div class="scope-chips">
411 <button class="scope-chip active">Both</button>
412 <button class="scope-chip">Saved</button>
413 <button class="scope-chip">Liked</button>
414 </div>
415
416 <!-- Search results -->
417 <article class="post-card">
418 <div class="result-meta">
419 <span class="relevance-badge relevance-high">94%</span>
420 <span class="source-tag">
421 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
422 <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" />
423 </svg>
424 Saved
425 </span>
426 </div>
427 <div class="post-header">
428 <div class="avatar">PF</div>
429 <div class="post-author">
430 <div class="post-author-name">Paul Frazee</div>
431 <div class="post-author-handle">@pfrazee.com · <span class="post-timestamp">Mar 12</span></div>
432 </div>
433 </div>
434 <div class="post-content">
435 We just shipped a major update to the <a href="#" class="post-facet">@atproto</a> federation code. Self-hosting your own PDS is now easier than ever. The decentralized web is happening.
436 </div>
437 <div class="post-actions">
438 <button class="post-action">
439 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
440 <path 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" />
441 </svg>
442 67
443 </button>
444 <button class="post-action">
445 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
446 <polyline points="17 1 21 5 17 9" /><path d="M3 11V9a4 4 0 0 1 4-4h14" /><polyline points="7 23 3 19 7 15" /><path d="M21 13v2a4 4 0 0 1-4 4H3" />
447 </svg>
448 34
449 </button>
450 <button class="post-action">
451 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
452 <path 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" />
453 </svg>
454 512
455 </button>
456 </div>
457 </article>
458
459 <article class="post-card">
460 <div class="result-meta">
461 <span class="relevance-badge relevance-high">87%</span>
462 <span class="source-tag">
463 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
464 <path 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" />
465 </svg>
466 Liked
467 </span>
468 </div>
469 <div class="post-header">
470 <div class="avatar">JG</div>
471 <div class="post-author">
472 <div class="post-author-name">Jake Gold</div>
473 <div class="post-author-handle">@jake.bsky.social · <span class="post-timestamp">Mar 8</span></div>
474 </div>
475 </div>
476 <div class="post-content">
477 The <a href="#" class="post-facet">#atproto</a> ecosystem is growing fast. More and more third-party apps are being built on the open protocol. Federation changes everything about how we think about social networks.
478 </div>
479 <div class="post-actions">
480 <button class="post-action">
481 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
482 <path 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" />
483 </svg>
484 23
485 </button>
486 <button class="post-action">
487 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
488 <polyline points="17 1 21 5 17 9" /><path d="M3 11V9a4 4 0 0 1 4-4h14" /><polyline points="7 23 3 19 7 15" /><path d="M21 13v2a4 4 0 0 1-4 4H3" />
489 </svg>
490 11
491 </button>
492 <button class="post-action">
493 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
494 <path 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" />
495 </svg>
496 178
497 </button>
498 </div>
499 </article>
500
501 <article class="post-card">
502 <div class="result-meta">
503 <span class="relevance-badge relevance-medium">72%</span>
504 <span class="source-tag">
505 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
506 <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z" />
507 </svg>
508 Saved
509 </span>
510 </div>
511 <div class="post-header">
512 <div class="avatar">SW</div>
513 <div class="post-author">
514 <div class="post-author-name">Sarah Wu</div>
515 <div class="post-author-handle">@sarahwu.dev · <span class="post-timestamp">Feb 21</span></div>
516 </div>
517 </div>
518 <div class="post-content">
519 Just set up my own PDS on a $5/mo VPS. The documentation has gotten so much better. If you're interested in running your own node on the AT Protocol network, now is the time.
520 </div>
521 <div class="post-actions">
522 <button class="post-action">
523 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
524 <path 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" />
525 </svg>
526 41
527 </button>
528 <button class="post-action">
529 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
530 <polyline points="17 1 21 5 17 9" /><path d="M3 11V9a4 4 0 0 1 4-4h14" /><polyline points="7 23 3 19 7 15" /><path d="M21 13v2a4 4 0 0 1-4 4H3" />
531 </svg>
532 8
533 </button>
534 <button class="post-action">
535 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
536 <path 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" />
537 </svg>
538 95
539 </button>
540 </div>
541 </article>
542
543 <article class="post-card">
544 <div class="result-meta">
545 <span class="relevance-badge relevance-low">58%</span>
546 <span class="source-tag">
547 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
548 <path 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" />
549 </svg>
550 Liked
551 </span>
552 </div>
553 <div class="post-header">
554 <div class="avatar">MR</div>
555 <div class="post-author">
556 <div class="post-author-name">Mark Rivera</div>
557 <div class="post-author-handle">@mrivera.bsky.social · <span class="post-timestamp">Jan 30</span></div>
558 </div>
559 </div>
560 <div class="post-content">
561 Interesting thread comparing ActivityPub and AT Protocol approaches to decentralization. Both have trade-offs but I think the data portability story is stronger with atproto.
562 </div>
563 <div class="post-actions">
564 <button class="post-action">
565 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
566 <path 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" />
567 </svg>
568 156
569 </button>
570 <button class="post-action">
571 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
572 <polyline points="17 1 21 5 17 9" /><path d="M3 11V9a4 4 0 0 1 4-4h14" /><polyline points="7 23 3 19 7 15" /><path d="M21 13v2a4 4 0 0 1-4 4H3" />
573 </svg>
574 52
575 </button>
576 <button class="post-action">
577 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
578 <path 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" />
579 </svg>
580 389
581 </button>
582 </div>
583 </article>
584 </div>
585
586 <!-- ====================== -->
587 <!-- VIEW: EMPTY STATE -->
588 <!-- ====================== -->
589 <div class="saved-container" id="view-empty" style="display: none">
590 <div class="semantic-search-bar">
591 <div class="semantic-input-wrapper">
592 <svg
593 viewBox="0 0 24 24"
594 fill="none"
595 stroke="currentColor"
596 stroke-width="2"
597 stroke-linecap="round"
598 stroke-linejoin="round">
599 <circle cx="11" cy="11" r="8" />
600 <line x1="21" y1="21" x2="16.65" y2="16.65" />
601 </svg>
602 <input class="semantic-input" type="text" placeholder="Search your saved posts..." />
603 </div>
604 </div>
605
606 <div class="scope-chips">
607 <button class="scope-chip active">Both</button>
608 <button class="scope-chip">Saved</button>
609 <button class="scope-chip">Liked</button>
610 </div>
611
612 <div class="empty-state" style="padding-top: 80px">
613 <svg
614 class="empty-state-icon"
615 viewBox="0 0 24 24"
616 fill="none"
617 stroke="currentColor"
618 stroke-width="1.5"
619 stroke-linecap="round"
620 stroke-linejoin="round">
621 <circle cx="11" cy="11" r="8" />
622 <line x1="21" y1="21" x2="16.65" y2="16.65" />
623 <path d="M8 11h6" />
624 <path d="M11 8v6" />
625 </svg>
626 <div class="empty-state-title">Semantic Search</div>
627 <div class="empty-state-text">Search your saved and liked posts by meaning, not just keywords</div>
628 </div>
629 </div>
630
631 <!-- ======================== -->
632 <!-- VIEW: NO RESULTS -->
633 <!-- ======================== -->
634 <div class="saved-container" id="view-noresults" style="display: none">
635 <div class="semantic-search-bar">
636 <div class="semantic-input-wrapper">
637 <svg
638 viewBox="0 0 24 24"
639 fill="none"
640 stroke="currentColor"
641 stroke-width="2"
642 stroke-linecap="round"
643 stroke-linejoin="round">
644 <circle cx="11" cy="11" r="8" />
645 <line x1="21" y1="21" x2="16.65" y2="16.65" />
646 </svg>
647 <input class="semantic-input" type="text" placeholder="Search your saved posts..." value="quantum computing hardware" />
648 </div>
649 </div>
650
651 <div class="scope-chips">
652 <button class="scope-chip active">Both</button>
653 <button class="scope-chip">Saved</button>
654 <button class="scope-chip">Liked</button>
655 </div>
656
657 <div class="empty-state" style="padding-top: 80px">
658 <svg
659 class="empty-state-icon"
660 viewBox="0 0 24 24"
661 fill="none"
662 stroke="currentColor"
663 stroke-width="1.5"
664 stroke-linecap="round"
665 stroke-linejoin="round">
666 <circle cx="11" cy="11" r="8" />
667 <line x1="21" y1="21" x2="16.65" y2="16.65" />
668 </svg>
669 <div class="empty-state-title">No similar posts found</div>
670 <div class="empty-state-text">Try a different search or broaden your scope</div>
671 </div>
672 </div>
673
674 <!-- ============================ -->
675 <!-- VIEW: INDEXING IN PROGRESS -->
676 <!-- ============================ -->
677 <div class="saved-container" id="view-indexing" style="display: none">
678 <div class="semantic-search-bar">
679 <div class="semantic-input-wrapper">
680 <svg
681 viewBox="0 0 24 24"
682 fill="none"
683 stroke="currentColor"
684 stroke-width="2"
685 stroke-linecap="round"
686 stroke-linejoin="round">
687 <circle cx="11" cy="11" r="8" />
688 <line x1="21" y1="21" x2="16.65" y2="16.65" />
689 </svg>
690 <input class="semantic-input" type="text" placeholder="Search your saved posts..." disabled />
691 </div>
692 </div>
693
694 <div class="indexing-bar">
695 <div class="indexing-bar-text">Indexing posts...</div>
696 <div class="indexing-progress">
697 <div class="indexing-progress-fill" style="width: 47%"></div>
698 </div>
699 <div class="indexing-count">142/300</div>
700 </div>
701
702 <div class="empty-state" style="padding-top: 60px">
703 <svg
704 class="empty-state-icon"
705 viewBox="0 0 24 24"
706 fill="none"
707 stroke="currentColor"
708 stroke-width="1.5"
709 stroke-linecap="round"
710 stroke-linejoin="round">
711 <path d="M12 2v4" /><path d="M12 18v4" /><path d="M4.93 4.93l2.83 2.83" /><path d="M16.24 16.24l2.83 2.83" /><path d="M2 12h4" /><path d="M18 12h4" /><path d="M4.93 19.07l2.83-2.83" /><path d="M16.24 7.76l2.83-2.83" />
712 </svg>
713 <div class="empty-state-title">Building search index</div>
714 <div class="empty-state-text">Your saved and liked posts are being indexed for semantic search. This happens once.</div>
715 </div>
716 </div>
717
718 <!-- ========================== -->
719 <!-- VIEW: UNAVAILABLE -->
720 <!-- ========================== -->
721 <div class="saved-container" id="view-unavailable" style="display: none">
722 <div class="unavailable-banner">
723 <svg
724 viewBox="0 0 24 24"
725 fill="none"
726 stroke="currentColor"
727 stroke-width="2"
728 stroke-linecap="round"
729 stroke-linejoin="round">
730 <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
731 <line x1="12" y1="9" x2="12" y2="13" />
732 <line x1="12" y1="17" x2="12.01" y2="17" />
733 </svg>
734 <div>
735 <div class="unavailable-banner-title">Semantic search unavailable</div>
736 <div class="unavailable-banner-text">
737 The embedding model could not be loaded on this device. Semantic search requires a device that supports on-device ML inference.
738 </div>
739 </div>
740 </div>
741 </div>
742
743 <!-- ========================== -->
744 <!-- VIEW: SETTINGS SECTION -->
745 <!-- ========================== -->
746 <div class="saved-container" id="view-settings" style="display: none">
747 <div class="settings-section">
748 <div class="settings-section-title">Search</div>
749
750 <div class="settings-group">
751 <!-- Toggle -->
752 <div class="settings-item">
753 <div class="settings-item-left">
754 <svg
755 class="settings-item-icon"
756 viewBox="0 0 24 24"
757 fill="none"
758 stroke="currentColor"
759 stroke-width="2"
760 stroke-linecap="round"
761 stroke-linejoin="round">
762 <circle cx="11" cy="11" r="8" />
763 <line x1="21" y1="21" x2="16.65" y2="16.65" />
764 </svg>
765 <div class="settings-item-content">
766 <div class="settings-item-title">Semantic Search</div>
767 <div class="settings-item-subtitle">Search saved posts by meaning</div>
768 </div>
769 </div>
770 <div class="settings-item-right">
771 <div class="toggle active">
772 <div class="toggle-thumb"></div>
773 </div>
774 </div>
775 </div>
776
777 <!-- Search scope -->
778 <div class="settings-item">
779 <div class="settings-item-left">
780 <svg
781 class="settings-item-icon"
782 viewBox="0 0 24 24"
783 fill="none"
784 stroke="currentColor"
785 stroke-width="2"
786 stroke-linecap="round"
787 stroke-linejoin="round">
788 <polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
789 </svg>
790 <div class="settings-item-content">
791 <div class="settings-item-title">Default Scope</div>
792 <div class="settings-item-subtitle">Both</div>
793 </div>
794 </div>
795 <div class="settings-item-right">
796 <svg
797 width="20"
798 height="20"
799 viewBox="0 0 24 24"
800 fill="none"
801 stroke="currentColor"
802 stroke-width="2"
803 stroke-linecap="round"
804 stroke-linejoin="round">
805 <polyline points="9 18 15 12 9 6" />
806 </svg>
807 </div>
808 </div>
809
810 <!-- Index status -->
811 <div class="settings-item">
812 <div class="settings-item-left">
813 <svg
814 class="settings-item-icon"
815 viewBox="0 0 24 24"
816 fill="none"
817 stroke="currentColor"
818 stroke-width="2"
819 stroke-linecap="round"
820 stroke-linejoin="round">
821 <ellipse cx="12" cy="5" rx="9" ry="3" />
822 <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
823 <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
824 </svg>
825 <div class="settings-item-content">
826 <div class="settings-item-title">Index Status</div>
827 <div class="settings-item-subtitle">847 posts indexed</div>
828 </div>
829 </div>
830 <div class="settings-item-right">
831 <button
832 style="
833 padding: 6px 14px;
834 border-radius: 9999px;
835 border: 1.5px solid var(--border);
836 background: none;
837 color: var(--text-secondary);
838 font-size: 13px;
839 font-weight: 500;
840 cursor: pointer;
841 font-family: inherit;
842 ">
843 Re-index
844 </button>
845 </div>
846 </div>
847
848 <!-- Max results slider -->
849 <div class="settings-item" style="flex-direction: column; align-items: stretch; gap: 8px">
850 <div style="display: flex; align-items: center; justify-content: space-between">
851 <div class="settings-item-left">
852 <svg
853 class="settings-item-icon"
854 viewBox="0 0 24 24"
855 fill="none"
856 stroke="currentColor"
857 stroke-width="2"
858 stroke-linecap="round"
859 stroke-linejoin="round">
860 <line x1="4" y1="21" x2="4" y2="14" /><line x1="4" y1="10" x2="4" y2="3" />
861 <line x1="12" y1="21" x2="12" y2="12" /><line x1="12" y1="8" x2="12" y2="3" />
862 <line x1="20" y1="21" x2="20" y2="16" /><line x1="20" y1="12" x2="20" y2="3" />
863 <line x1="1" y1="14" x2="7" y2="14" /><line x1="9" y1="8" x2="15" y2="8" /><line x1="17" y1="16" x2="23" y2="16" />
864 </svg>
865 <div class="settings-item-content">
866 <div class="settings-item-title">Max Results</div>
867 </div>
868 </div>
869 <div class="slider-value">20</div>
870 </div>
871 <div class="slider-row">
872 <div class="slider-track">
873 <div class="slider-fill"></div>
874 <div class="slider-thumb"></div>
875 </div>
876 </div>
877 <div class="slider-labels">
878 <span class="slider-label">10</span>
879 <span class="slider-label">50</span>
880 </div>
881 </div>
882 </div>
883 </div>
884 </div>
885
886 <!-- View switcher (wireframe navigation) -->
887 <div style="
888 position: fixed;
889 bottom: 100px;
890 left: 50%;
891 transform: translateX(-50%);
892 z-index: 200;
893 background-color: var(--surface);
894 border: 1px solid var(--border);
895 border-radius: 16px;
896 padding: 8px;
897 display: flex;
898 gap: 6px;
899 flex-wrap: wrap;
900 max-width: 390px;
901 justify-content: center;
902 box-shadow: 0 4px 12px rgba(0,0,0,0.15);
903 ">
904 <button class="view-switcher-btn active-view" data-target="view-search">Results</button>
905 <button class="view-switcher-btn" data-target="view-empty">Empty</button>
906 <button class="view-switcher-btn" data-target="view-noresults">No Results</button>
907 <button class="view-switcher-btn" data-target="view-indexing">Indexing</button>
908 <button class="view-switcher-btn" data-target="view-unavailable">Unavailable</button>
909 <button class="view-switcher-btn" data-target="view-settings">Settings</button>
910 </div>
911
912 <!-- Bottom Navigation -->
913 <nav class="nav-bar">
914 <a href="home.html" class="nav-item">
915 <svg
916 viewBox="0 0 24 24"
917 fill="none"
918 stroke="currentColor"
919 stroke-width="2"
920 stroke-linecap="round"
921 stroke-linejoin="round">
922 <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
923 <polyline points="9 22 9 12 15 12 15 22" />
924 </svg>
925 <span>Home</span>
926 </a>
927
928 <a href="search.html" class="nav-item">
929 <svg
930 viewBox="0 0 24 24"
931 fill="none"
932 stroke="currentColor"
933 stroke-width="2"
934 stroke-linecap="round"
935 stroke-linejoin="round">
936 <circle cx="11" cy="11" r="8" />
937 <line x1="21" y1="21" x2="16.65" y2="16.65" />
938 </svg>
939 <span>Search</span>
940 </a>
941
942 <a href="profile.html" class="nav-item">
943 <svg
944 viewBox="0 0 24 24"
945 fill="none"
946 stroke="currentColor"
947 stroke-width="2"
948 stroke-linecap="round"
949 stroke-linejoin="round">
950 <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
951 <circle cx="12" cy="7" r="4" />
952 </svg>
953 <span>Profile</span>
954 </a>
955
956 <a href="settings.html" class="nav-item">
957 <svg
958 viewBox="0 0 24 24"
959 fill="none"
960 stroke="currentColor"
961 stroke-width="2"
962 stroke-linecap="round"
963 stroke-linejoin="round">
964 <circle cx="12" cy="12" r="3" />
965 <path
966 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" />
967 </svg>
968 <span>Settings</span>
969 </a>
970 </nav>
971 </div>
972
973 <script>
974 /* Theme init */
975 (function () {
976 const mode = localStorage.getItem("appearance-mode") || "dark";
977 if (mode === "dark") {
978 document.documentElement.setAttribute("data-theme", "dark");
979 } else if (mode === "system") {
980 const prefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
981 if (prefersDark) document.documentElement.setAttribute("data-theme", "dark");
982 }
983 })();
984
985 /* Tab switching */
986 document.querySelectorAll(".saved-tab").forEach((tab) => {
987 tab.addEventListener("click", () => {
988 document.querySelectorAll(".saved-tab").forEach((t) => t.classList.remove("active"));
989 tab.classList.add("active");
990 });
991 });
992
993 /* Scope chip switching */
994 document.querySelectorAll(".scope-chips").forEach((row) => {
995 row.querySelectorAll(".scope-chip").forEach((chip) => {
996 chip.addEventListener("click", () => {
997 row.querySelectorAll(".scope-chip").forEach((c) => c.classList.remove("active"));
998 chip.classList.add("active");
999 });
1000 });
1001 });
1002
1003 /* View switcher for wireframe preview */
1004 document.querySelectorAll(".view-switcher-btn").forEach((btn) => {
1005 btn.addEventListener("click", () => {
1006 document.querySelectorAll(".view-switcher-btn").forEach((b) => b.classList.remove("active-view"));
1007 btn.classList.add("active-view");
1008
1009 const target = btn.dataset.target;
1010 document.querySelectorAll(".saved-container").forEach((v) => (v.style.display = "none"));
1011 document.getElementById(target).style.display = "block";
1012 });
1013 });
1014 </script>
1015 </body>
1016</html>