1<script lang="ts">
2 import { homepageState } from '../state.svelte';
3 import { History, Star, BarChart3, Terminal, Search, Folder, Tags, Command } from '@lucide/svelte';
4
5 // Derived status indicators
6 const panelLabel = $derived({
7 history: 'HISTORY',
8 favorites: 'FAVORITES',
9 analytics: 'ANALYTICS',
10 }[homepageState.activePanel]);
11
12 const panelIcon = $derived({
13 history: History,
14 favorites: Star,
15 analytics: BarChart3,
16 }[homepageState.activePanel]);
17
18 // Loading states
19 const isLoading = $derived(
20 homepageState.historyLoading ||
21 homepageState.favoritesLoading ||
22 homepageState.analyticsLoading
23 );
24
25 // Current list stats
26 const listStats = $derived.by(() => {
27 switch (homepageState.activePanel) {
28 case 'history':
29 if (homepageState.historySearchQuery) {
30 return {
31 count: homepageState.historySearchResults.length,
32 label: 'results',
33 filtered: true,
34 };
35 }
36 return {
37 count: homepageState.history.length,
38 label: 'entries',
39 filtered: false,
40 };
41 case 'favorites': {
42 const filtered = homepageState.filteredFavorites;
43 const total = homepageState.favorites.length;
44 return {
45 count: filtered.length,
46 label: filtered.length === total ? 'items' : `of ${total}`,
47 filtered: filtered.length !== total,
48 };
49 }
50 case 'analytics':
51 return {
52 count: homepageState.domainStats.length,
53 label: 'domains',
54 filtered: false,
55 };
56 default:
57 return { count: 0, label: 'items', filtered: false };
58 }
59 });
60
61 // Position in list
62 const positionInfo = $derived.by(() => {
63 const length = homepageState.currentListLength;
64 if (length === 0) return null;
65
66 const pos = homepageState.focusedIndex + 1;
67 return { current: pos, total: length };
68 });
69
70 // Mode indicator
71 const modeIndicator = $derived.by(() => {
72 if (homepageState.commandPaletteOpen) {
73 return { icon: Terminal, label: 'COMMAND', color: 'accent' };
74 }
75 if (homepageState.historySearchQuery) {
76 return { icon: Search, label: 'SEARCH', color: 'warning' };
77 }
78 if (homepageState.selectedFolderId !== null) {
79 const folder = homepageState.folders.find(f => f.id === homepageState.selectedFolderId);
80 return { icon: Folder, label: folder?.name || 'FOLDER', color: 'info' };
81 }
82 if (homepageState.selectedTagIds.length > 0) {
83 return { icon: Tags, label: `${homepageState.selectedTagIds.length} TAGS`, color: 'info' };
84 }
85 return null;
86 });
87</script>
88
89<footer class="status-bar">
90 <!-- Left Section: Mode & Panel -->
91 <div class="status-left">
92 <!-- Pending Key Prefix Indicator -->
93 {#if homepageState.pendingKeyPrefix}
94 <div class="status-chip prefix-chip" data-color="warning">
95 <span class="chip-icon">
96 <Command size={12} strokeWidth={2} />
97 </span>
98 <kbd class="prefix-key">{homepageState.pendingKeyPrefix}</kbd>
99 <span class="prefix-waiting">...</span>
100 </div>
101 {/if}
102
103 <!-- Mode Indicator -->
104 {#if modeIndicator}
105 {@const ModeIcon = modeIndicator.icon}
106 <div class="status-chip mode-chip" data-color={modeIndicator.color}>
107 <span class="chip-icon">
108 <ModeIcon size={12} strokeWidth={2} />
109 </span>
110 <span class="chip-text">{modeIndicator.label}</span>
111 </div>
112 {/if}
113
114 <!-- Panel Indicator -->
115 <div class="status-chip panel-chip">
116 <span class="chip-icon">
117 {#if panelIcon}
118 {@const PanelIcon = panelIcon}
119 <PanelIcon size={12} strokeWidth={2} />
120 {/if}
121 </span>
122 <span class="chip-text">{panelLabel}</span>
123 </div>
124
125 <!-- Loading Indicator -->
126 {#if isLoading}
127 <div class="status-chip loading-chip">
128 <span class="loading-spinner"></span>
129 <span class="chip-text">SYNC</span>
130 </div>
131 {/if}
132 </div>
133
134 <!-- Center Section: Stats -->
135 <div class="status-center">
136 <div class="stats-group">
137 <span class="stats-count" class:filtered={listStats.filtered}>
138 {listStats.count}
139 </span>
140 <span class="stats-label">{listStats.label}</span>
141 </div>
142
143 {#if positionInfo}
144 <div class="position-group">
145 <span class="position-current">{positionInfo.current}</span>
146 <span class="position-sep">/</span>
147 <span class="position-total">{positionInfo.total}</span>
148 </div>
149 {/if}
150 </div>
151
152 <!-- Right Section: Quick Hints -->
153 <div class="status-right">
154 <div class="hint-item">
155 <kbd>/</kbd>
156 <span>cmd</span>
157 </div>
158 <div class="hint-item">
159 <kbd>j</kbd><kbd>k</kbd>
160 <span>nav</span>
161 </div>
162 <div class="hint-item">
163 <kbd>?</kbd>
164 <span>help</span>
165 </div>
166 </div>
167</footer>
168
169<style>
170 .status-bar {
171 display: flex;
172 align-items: center;
173 justify-content: space-between;
174 gap: 1rem;
175 padding: 0.5rem 0.85rem;
176 font-size: 0.68rem;
177 font-family: ui-monospace, 'Fira Code', 'Cascadia Code', Menlo, Monaco, Consolas, monospace;
178 letter-spacing: 0.05em;
179 border-top: 1px solid var(--border);
180 background: color-mix(in srgb, var(--background) 92%, var(--card) 8%);
181 color: var(--muted-foreground);
182 }
183
184 /* Sections */
185 .status-left,
186 .status-center,
187 .status-right {
188 display: flex;
189 align-items: center;
190 gap: 0.6rem;
191 }
192
193 .status-left {
194 flex: 1;
195 justify-content: flex-start;
196 }
197
198 .status-center {
199 flex: 0 0 auto;
200 gap: 1rem;
201 }
202
203 .status-right {
204 flex: 1;
205 justify-content: flex-end;
206 gap: 0.85rem;
207 }
208
209 /* Status Chips */
210 .status-chip {
211 display: inline-flex;
212 align-items: center;
213 gap: 0.35rem;
214 padding: 0.25rem 0.5rem;
215 background: color-mix(in srgb, var(--card) 60%, transparent);
216 border-width: 1px;
217 border-style: solid;
218 border-color: var(--border);
219 }
220
221 .chip-icon {
222 display: flex;
223 align-items: center;
224 }
225
226 .chip-text {
227 font-weight: 500;
228 text-transform: uppercase;
229 }
230
231 /* Mode chip colors */
232 .mode-chip[data-color="accent"] {
233 background: color-mix(in srgb, var(--primary, #4ade80) 15%, transparent);
234 border-color: color-mix(in srgb, var(--primary, #4ade80) 40%, var(--border) 60%);
235 color: var(--primary, #4ade80);
236 }
237
238 .mode-chip[data-color="warning"] {
239 background: color-mix(in srgb, var(--warning) 15%, transparent);
240 border-color: color-mix(in srgb, var(--warning) 40%, var(--border) 60%);
241 color: var(--warning);
242 }
243
244 .mode-chip[data-color="info"] {
245 background: color-mix(in srgb, var(--info) 15%, transparent);
246 border-color: color-mix(in srgb, var(--info) 40%, var(--border) 60%);
247 color: var(--info);
248 }
249
250 /* Pending prefix indicator */
251 .prefix-chip {
252 animation: pulse-border 1s ease-in-out infinite;
253 }
254
255 .prefix-chip[data-color="warning"] {
256 background: color-mix(in srgb, var(--warning) 15%, transparent);
257 border-color: color-mix(in srgb, var(--warning) 50%, var(--border) 50%);
258 color: var(--warning);
259 }
260
261 .prefix-key {
262 font-size: 0.7rem;
263 font-weight: 700;
264 padding: 0.1rem 0.25rem;
265 background: color-mix(in srgb, var(--warning) 25%, transparent);
266 border: 1px solid color-mix(in srgb, var(--warning) 40%, var(--border) 60%);
267 font-family: ui-monospace, 'Fira Code', 'Cascadia Code', Menlo, Monaco, Consolas, monospace;
268 }
269
270 .prefix-waiting {
271 font-size: 0.65rem;
272 opacity: 0.8;
273 animation: blink 0.6s ease-in-out infinite;
274 }
275
276 @keyframes pulse-border {
277 0%, 100% { border-color: color-mix(in srgb, var(--warning) 50%, var(--border) 50%); }
278 50% { border-color: var(--warning); }
279 }
280
281 @keyframes blink {
282 0%, 100% { opacity: 0.4; }
283 50% { opacity: 1; }
284 }
285
286 .panel-chip {
287 color: var(--foreground);
288 }
289
290 .panel-chip .chip-icon {
291 color: var(--primary, #4ade80);
292 }
293
294 /* Loading */
295 .loading-chip {
296 color: var(--muted-foreground);
297 }
298
299 .loading-spinner {
300 width: 0.7rem;
301 height: 0.7rem;
302 border: 1.5px solid var(--border);
303 border-top-color: var(--primary, #4ade80);
304 border-radius: 50%;
305 animation: spin 0.8s linear infinite;
306 }
307
308 @keyframes spin {
309 to { transform: rotate(360deg); }
310 }
311
312 /* Stats */
313 .stats-group {
314 display: flex;
315 align-items: baseline;
316 gap: 0.35rem;
317 }
318
319 .stats-count {
320 font-weight: 600;
321 color: var(--foreground);
322 font-variant-numeric: tabular-nums;
323 }
324
325 .stats-count.filtered {
326 color: var(--primary, #4ade80);
327 }
328
329 .stats-label {
330 color: var(--muted-foreground);
331 }
332
333 .position-group {
334 display: flex;
335 align-items: center;
336 gap: 0.15rem;
337 padding: 0.2rem 0.45rem;
338 background: var(--background);
339 border-width: 1px;
340 border-style: solid;
341 border-color: var(--border);
342 font-variant-numeric: tabular-nums;
343 }
344
345 .position-current {
346 font-weight: 600;
347 color: var(--foreground);
348 }
349
350 .position-sep {
351 color: var(--border);
352 }
353
354 .position-total {
355 color: var(--muted-foreground);
356 }
357
358 /* Hints */
359 .hint-item {
360 display: flex;
361 align-items: center;
362 gap: 0.25rem;
363 color: var(--muted-foreground);
364 }
365
366 .hint-item kbd {
367 padding: 0.1rem 0.3rem;
368 font-size: 0.6rem;
369 background: var(--background);
370 border-width: 1px;
371 border-style: solid;
372 border-color: var(--border);
373 color: var(--foreground);
374 }
375
376 .hint-item span {
377 font-size: 0.62rem;
378 text-transform: uppercase;
379 letter-spacing: 0.08em;
380 }
381
382 /* Responsive */
383 @media (max-width: 640px) {
384 .status-bar {
385 flex-wrap: wrap;
386 gap: 0.5rem;
387 }
388
389 .status-left,
390 .status-right {
391 flex: 0 0 auto;
392 }
393
394 .status-center {
395 order: -1;
396 width: 100%;
397 justify-content: center;
398 padding-bottom: 0.35rem;
399 border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
400 }
401
402 .hint-item span {
403 display: none;
404 }
405 }
406
407 @media (max-width: 480px) {
408 .status-right {
409 display: none;
410 }
411
412 .status-left {
413 flex: 1;
414 }
415 }
416</style>