diff --git a/app/components/AppHeader.vue b/app/components/AppHeader.vue
index ae779a39..67626fb4 100644
--- a/app/components/AppHeader.vue
+++ b/app/components/AppHeader.vue
@@ -72,11 +72,14 @@ onKeyStroke(',', e => {
-
+
+
+
+
+const props = defineProps<{
+ packageName: string
+}>()
+
+const { useIsBookmarked, toggleBookmark } = useBookmarks()
+
+const isBookmarked = useIsBookmarked(() => props.packageName)
+
+function handleClick() {
+ toggleBookmark(props.packageName)
+}
+
+
+
+
+
diff --git a/app/components/HeaderBookmarksDropdown.vue b/app/components/HeaderBookmarksDropdown.vue
new file mode 100644
index 00000000..644152eb
--- /dev/null
+++ b/app/components/HeaderBookmarksDropdown.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('header.bookmarks_dropdown.title') }}
+
+
+
+
+
+
+
+
+ {{ $t('header.bookmarks_dropdown.empty') }}
+
+
+
+
+
+
+
diff --git a/app/composables/useBookmarks.ts b/app/composables/useBookmarks.ts
new file mode 100644
index 00000000..71b2797e
--- /dev/null
+++ b/app/composables/useBookmarks.ts
@@ -0,0 +1,92 @@
+import type { RemovableRef } from '@vueuse/core'
+import { useLocalStorage } from '@vueuse/core'
+
+/**
+ * Bookmark entry with package name and timestamp
+ */
+export interface Bookmark {
+ packageName: string
+ addedAt: number
+}
+
+const STORAGE_KEY = 'npmx-bookmarks'
+
+// Shared bookmarks instance (singleton per app)
+let bookmarksRef: RemovableRef | null = null
+
+/**
+ * Composable for managing package bookmarks with localStorage persistence.
+ * Bookmarks are shared across all components that use this composable.
+ */
+export function useBookmarks() {
+ if (!bookmarksRef) {
+ bookmarksRef = useLocalStorage(STORAGE_KEY, [], {
+ mergeDefaults: true,
+ })
+ }
+
+ const bookmarks = bookmarksRef
+
+ /**
+ * Check if a package is bookmarked
+ */
+ function isBookmarked(packageName: string): boolean {
+ return bookmarks.value.some(b => b.packageName === packageName)
+ }
+
+ /**
+ * Reactive computed to check if a specific package is bookmarked
+ */
+ function useIsBookmarked(packageName: MaybeRefOrGetter) {
+ return computed(() => isBookmarked(toValue(packageName)))
+ }
+
+ /**
+ * Add a package to bookmarks
+ */
+ function addBookmark(packageName: string): void {
+ if (!isBookmarked(packageName)) {
+ bookmarks.value = [{ packageName, addedAt: Date.now() }, ...bookmarks.value]
+ }
+ }
+
+ /**
+ * Remove a package from bookmarks
+ */
+ function removeBookmark(packageName: string): void {
+ bookmarks.value = bookmarks.value.filter(b => b.packageName !== packageName)
+ }
+
+ /**
+ * Toggle bookmark status for a package
+ */
+ function toggleBookmark(packageName: string): void {
+ if (isBookmarked(packageName)) {
+ removeBookmark(packageName)
+ } else {
+ addBookmark(packageName)
+ }
+ }
+
+ /**
+ * Clear all bookmarks
+ */
+ function clearBookmarks(): void {
+ bookmarks.value = []
+ }
+
+ const bookmarkCount = computed(() => bookmarks.value.length)
+ const hasBookmarks = computed(() => bookmarks.value.length > 0)
+
+ return {
+ bookmarks,
+ bookmarkCount,
+ hasBookmarks,
+ isBookmarked,
+ useIsBookmarked,
+ addBookmark,
+ removeBookmark,
+ toggleBookmark,
+ clearBookmarks,
+ }
+}
diff --git a/app/pages/[...package].vue b/app/pages/[...package].vue
index b32e1bba..e172f116 100644
--- a/app/pages/[...package].vue
+++ b/app/pages/[...package].vue
@@ -574,12 +574,15 @@ function handleClick(event: MouseEvent) {
-
+