Skip to content

Commit 1e1b87f

Browse files
committed
Search route, homepage featured
1 parent 089385f commit 1e1b87f

7 files changed

Lines changed: 343 additions & 22 deletions

File tree

src/lib/components/global/ToolCard.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@
455455
display: flex;
456456
flex-direction: column;
457457
align-items: center;
458-
gap: var(--spacing-xs);
458+
gap: var(--spacing-sm);
459459
width: 100%;
460460
position: relative;
461461
z-index: 1;
Lines changed: 304 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,293 @@
11
<script lang="ts">
22
import { onMount } from 'svelte';
33
import ToolsGrid from '$lib/components/global/ToolsGrid.svelte';
4-
import BookmarksGrid from '$lib/components/global/BookmarksGrid.svelte';
4+
import Icon from '$lib/components/global/Icon.svelte';
55
import { bookmarks } from '$lib/stores/bookmarks';
6+
import { frequentlyUsedTools, recentlyUsedTools } from '$lib/stores/toolUsage';
67
78
onMount(() => {
89
bookmarks.init();
910
});
1011
12+
type ViewMode = 'bookmarks' | 'most-used' | 'recent';
13+
let activeView = $state<ViewMode>('bookmarks');
14+
1115
const bookmarkCount = $derived($bookmarks.length);
16+
const mostUsedCount = $derived($frequentlyUsedTools.length);
17+
const recentCount = $derived($recentlyUsedTools.length);
18+
1219
let showAll = $state(false);
20+
21+
// Reset showAll when switching views
22+
$effect(() => {
23+
// Track activeView to reset showAll when it changes
24+
void activeView;
25+
showAll = false;
26+
});
27+
28+
// Determine if current view has items
29+
const hasItems = $derived(() => {
30+
if (activeView === 'bookmarks') return bookmarkCount > 0;
31+
if (activeView === 'most-used') return mostUsedCount > 0;
32+
if (activeView === 'recent') return recentCount > 0;
33+
return false;
34+
});
35+
36+
// Convert tool usage to NavItem format
37+
const mostUsedItems = $derived(
38+
$frequentlyUsedTools.map((tool) => ({
39+
href: tool.href,
40+
label: tool.label || 'Tool',
41+
icon: tool.icon,
42+
description: tool.description,
43+
keywords: [],
44+
})),
45+
);
46+
47+
const recentItems = $derived(
48+
$recentlyUsedTools.map((tool) => ({
49+
href: tool.href,
50+
label: tool.label || 'Tool',
51+
icon: tool.icon,
52+
description: tool.description,
53+
keywords: [],
54+
})),
55+
);
56+
57+
const bookmarkItems = $derived(
58+
$bookmarks.map((bookmark) => ({
59+
href: bookmark.href,
60+
label: bookmark.label,
61+
icon: bookmark.icon,
62+
description: bookmark.description,
63+
keywords: [],
64+
})),
65+
);
1366
</script>
1467

15-
<BookmarksGrid />
68+
<div class="bookmarks-page">
69+
<div class="bookmarks-container" aria-live="polite">
70+
<div class="bookmarks-header">
71+
<div class="tools-grid-sub-header">
72+
{#if activeView === 'bookmarks'}
73+
<Icon name="bookmarks" size="sm" />
74+
<h2>Bookmarked Tools</h2>
75+
<span class="count">{bookmarkCount}</span>
76+
{:else if activeView === 'most-used'}
77+
<Icon name="frequently-used" size="sm" />
78+
<h2>Most Used</h2>
79+
<span class="count">{mostUsedCount}</span>
80+
{:else if activeView === 'recent'}
81+
<Icon name="clock" size="sm" />
82+
<h2>Recent</h2>
83+
<span class="count">{recentCount}</span>
84+
{/if}
85+
</div>
86+
87+
<div class="view-toggle">
88+
<button
89+
class="toggle-btn"
90+
class:active={activeView === 'bookmarks'}
91+
onclick={() => (activeView = 'bookmarks')}
92+
aria-pressed={activeView === 'bookmarks'}
93+
>
94+
<Icon name="bookmarks" size="sm" />
95+
<span>Bookmarks</span>
96+
</button>
97+
<button
98+
class="toggle-btn"
99+
class:active={activeView === 'most-used'}
100+
onclick={() => (activeView = 'most-used')}
101+
aria-pressed={activeView === 'most-used'}
102+
>
103+
<Icon name="frequently-used" size="sm" />
104+
<span>Most Used</span>
105+
</button>
106+
<button
107+
class="toggle-btn"
108+
class:active={activeView === 'recent'}
109+
onclick={() => (activeView = 'recent')}
110+
aria-pressed={activeView === 'recent'}
111+
>
112+
<Icon name="clock" size="sm" />
113+
<span>Recent</span>
114+
</button>
115+
</div>
116+
</div>
16117

17-
<!-- Button for showing all tools, so user can find some to bookmark -->
18-
{#if bookmarkCount > 0}
19-
<div class="toggle-container">
20-
<button onclick={() => (showAll = !showAll)} class="toggle-button">
21-
{showAll ? 'Hide All' : 'Show All'}
22-
</button>
118+
<div class="bookmarks-grid">
119+
{#if activeView === 'bookmarks'}
120+
{#if bookmarkCount > 0}
121+
<ToolsGrid tools={bookmarkItems} idPrefix="bookmarks" />
122+
{:else}
123+
<div class="empty-state">
124+
<div class="empty-icon">
125+
<Icon name="bookmarks" size="lg" />
126+
</div>
127+
<h3>No bookmarks yet</h3>
128+
<p>Hover over any tool card and click the bookmark icon to save your favorites</p>
129+
</div>
130+
{/if}
131+
{:else if activeView === 'most-used'}
132+
{#if mostUsedCount > 0}
133+
<ToolsGrid tools={mostUsedItems} idPrefix="most-used" />
134+
{:else}
135+
<div class="empty-state">
136+
<div class="empty-icon">
137+
<Icon name="frequently-used" size="lg" />
138+
</div>
139+
<h3>No usage data yet</h3>
140+
<p>Start using tools to see your most frequently used ones here</p>
141+
</div>
142+
{/if}
143+
{:else if activeView === 'recent'}
144+
{#if recentCount > 0}
145+
<ToolsGrid tools={recentItems} idPrefix="recent" />
146+
{:else}
147+
<div class="empty-state">
148+
<div class="empty-icon">
149+
<Icon name="clock" size="lg" />
150+
</div>
151+
<h3>No recent tools</h3>
152+
<p>Recently visited tools will appear here</p>
153+
</div>
154+
{/if}
155+
{/if}
156+
</div>
23157
</div>
24-
{/if}
25158

26-
<!-- Show all tools -->
27-
{#if bookmarkCount === 0 || showAll}
28-
<ToolsGrid />
29-
{/if}
159+
<!-- Button for showing all tools when current view has items -->
160+
{#if hasItems()}
161+
<div class="toggle-container">
162+
<button onclick={() => (showAll = !showAll)} class="toggle-button">
163+
{showAll ? 'Hide All Tools' : 'Show All Tools'}
164+
</button>
165+
</div>
166+
{/if}
167+
168+
<!-- Show all tools when view is empty OR when Show All is toggled -->
169+
{#if !hasItems() || showAll}
170+
<div class="all-tools-section">
171+
<ToolsGrid />
172+
</div>
173+
{/if}
174+
</div>
30175

31176
<style lang="scss">
177+
.bookmarks-page {
178+
animation: fadeIn 0.3s ease-out;
179+
}
180+
181+
.bookmarks-container {
182+
animation: slideIn 0.3s ease-out;
183+
}
184+
185+
.bookmarks-header {
186+
display: flex;
187+
justify-content: space-between;
188+
align-items: center;
189+
gap: var(--spacing-md);
190+
margin-bottom: var(--spacing-lg);
191+
flex-wrap: wrap;
192+
193+
@media (max-width: 768px) {
194+
flex-direction: column;
195+
align-items: flex-start;
196+
}
197+
}
198+
199+
.view-toggle {
200+
display: flex;
201+
gap: var(--spacing-xs);
202+
background: var(--bg-secondary);
203+
padding: var(--spacing-xs);
204+
border-radius: var(--radius-md);
205+
border: 1px solid var(--border-primary);
206+
207+
.toggle-btn {
208+
display: flex;
209+
align-items: center;
210+
gap: var(--spacing-xs);
211+
padding: var(--spacing-xs) var(--spacing-sm);
212+
background: transparent;
213+
color: var(--text-secondary);
214+
font-size: var(--font-size-sm);
215+
border: none;
216+
border-radius: var(--radius-sm);
217+
cursor: pointer;
218+
transition: all var(--transition-fast);
219+
white-space: nowrap;
220+
221+
:global(svg) {
222+
color: var(--text-secondary);
223+
transition: color var(--transition-fast);
224+
}
225+
226+
&:hover {
227+
background: var(--bg-tertiary);
228+
color: var(--text-primary);
229+
230+
:global(svg) {
231+
color: var(--text-primary);
232+
}
233+
}
234+
235+
&.active {
236+
background: var(--color-primary);
237+
color: var(--bg-primary);
238+
239+
:global(svg) {
240+
color: var(--bg-primary);
241+
}
242+
}
243+
244+
@media (max-width: 640px) {
245+
span {
246+
display: none;
247+
}
248+
padding: var(--spacing-xs);
249+
}
250+
}
251+
}
252+
253+
.bookmarks-grid {
254+
animation: subtleFadeIn 0.2s ease-out;
255+
}
256+
257+
.empty-state {
258+
background: var(--bg-secondary);
259+
margin: var(--spacing-xl) auto;
260+
padding: var(--spacing-xl) var(--spacing-lg);
261+
border-radius: var(--radius-lg);
262+
text-align: center;
263+
opacity: 0.85;
264+
animation: fadeIn 0.2s ease;
265+
266+
.empty-icon {
267+
:global(svg) {
268+
color: var(--text-secondary);
269+
opacity: 0.5;
270+
}
271+
}
272+
273+
h3 {
274+
font-size: var(--font-size-lg);
275+
color: var(--text-primary);
276+
margin: var(--spacing-md) 0 var(--spacing-sm) 0;
277+
}
278+
279+
p {
280+
color: var(--text-secondary);
281+
max-width: 24rem;
282+
margin: 0 auto;
283+
line-height: 1.5;
284+
}
285+
}
286+
32287
.toggle-container {
33288
display: flex;
34289
justify-content: center;
35-
margin: var(--spacing-lg) 0;
290+
margin: var(--spacing-xl) 0 var(--spacing-lg) 0;
36291
37292
.toggle-button {
38293
padding: var(--spacing-sm) var(--spacing-md);
@@ -42,11 +297,45 @@
42297
border-radius: var(--radius-sm);
43298
cursor: pointer;
44299
border: none;
45-
transition: background 0.3s ease;
300+
transition: background var(--transition-fast);
46301
47302
&:hover {
48303
background: var(--color-primary-hover);
49304
}
50305
}
51306
}
307+
308+
.all-tools-section {
309+
margin-top: var(--spacing-xl);
310+
animation: fadeIn 0.3s ease-out;
311+
}
312+
313+
@keyframes fadeIn {
314+
from {
315+
opacity: 0;
316+
}
317+
to {
318+
opacity: 1;
319+
}
320+
}
321+
322+
@keyframes slideIn {
323+
from {
324+
opacity: 0;
325+
transform: translateY(-10px);
326+
}
327+
to {
328+
opacity: 1;
329+
transform: translateY(0);
330+
}
331+
}
332+
333+
@keyframes subtleFadeIn {
334+
from {
335+
opacity: 0;
336+
}
337+
to {
338+
opacity: 1;
339+
}
340+
}
52341
</style>

0 commit comments

Comments
 (0)