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