diff --git a/src/interface/components/store/CoverSwiper.svelte b/src/interface/components/store/CoverSwiper.svelte index d5c35c1a..0b1a2c08 100644 --- a/src/interface/components/store/CoverSwiper.svelte +++ b/src/interface/components/store/CoverSwiper.svelte @@ -43,8 +43,24 @@ onclick={() => setDisplayTheme(theme)} > Theme Preview + {#if theme.featured === true} +
+ + + + + Featured + +
+ {/if}

{theme.name}

+ {#if theme.author} +

By {theme.author}

+ {/if}

{theme.description}

diff --git a/src/interface/components/store/ThemeCard.svelte b/src/interface/components/store/ThemeCard.svelte index 97dad4a3..151ed229 100644 --- a/src/interface/components/store/ThemeCard.svelte +++ b/src/interface/components/store/ThemeCard.svelte @@ -49,6 +49,19 @@ class="bg-gray-50 w-full transition-all duration-500 ease-out relative group flex flex-col rounded-xl overflow-clip border hover:scale-105 hover:shadow-2xl dark:hover:shadow-white/[0.1] dark:hover:shadow-white/[0.8] dark:bg-zinc-800 dark:border-white/[0.1] h-auto" transition:fade > + {#if theme.featured === true} +
+ + + + + Featured + +
+ {/if}
-

- {theme.name} -

+
+

+ {theme.name} +

+ {#if theme.featured === true} + + + + + Featured + + {/if} +
+ {#if theme.author} +

+ By {theme.author} +

+ {/if}
@@ -150,24 +177,26 @@ {/if}
-
+ {#if relatedThemes.length > 0} +
-

- Similar Themes -

-
- {#each getRelatedThemes() as relatedTheme (relatedTheme.id)} -
- - {/each} - + + {/each} + + {/if} {:else}
diff --git a/src/interface/pages/store.svelte b/src/interface/pages/store.svelte index ce0da22f..d9689bd4 100644 --- a/src/interface/pages/store.svelte +++ b/src/interface/pages/store.svelte @@ -54,6 +54,17 @@ activeTab = tab; }; + /** Featured themes first; within each group, newest by `created_at` (API: Unix seconds). */ + function compareStoreThemes(a: Theme, b: Theme): number { + const fa = a.featured === true ? 1 : 0; + const fb = b.featured === true ? 1 : 0; + if (fa !== fb) return fb - fa; + const ca = a.created_at ?? 0; + const cb = b.created_at ?? 0; + if (ca !== cb) return cb - ca; + return a.name.localeCompare(b.name); + } + const toggleFavorite = async (theme: Theme) => { const token = await cloudAuth.getStoredToken(); if (!token) return; @@ -96,11 +107,8 @@ if (!data?.success || !data?.data?.themes) { throw new Error(data?.error || 'Failed to fetch themes'); } - themes = data.data.themes; - - // Shuffle for cover themes - const shuffled = [...themes].sort(() => 0.5 - Math.random()); - coverThemes = shuffled.slice(0, 3); + themes = [...data.data.themes].sort(compareStoreThemes); + coverThemes = themes.slice(0, 3); loading = false; } catch (err) { @@ -118,11 +126,14 @@ darkMode = $settingsState.DarkMode; }); - // Filter themes based on search term - let filteredThemes = $derived(themes.filter(theme => - theme.name.toLowerCase().includes(searchTerm.toLowerCase()) || - theme.description.toLowerCase().includes(searchTerm.toLowerCase()) - )); + // Filter themes (list is already featured-first, then newest; filter preserves order) + let filteredThemes = $derived( + themes.filter( + (theme) => + theme.name.toLowerCase().includes(searchTerm.toLowerCase()) || + theme.description.toLowerCase().includes(searchTerm.toLowerCase()), + ), + ); $effect(() => { loadBackground(); diff --git a/src/interface/types/Theme.ts b/src/interface/types/Theme.ts index 2179156e..407744ef 100644 --- a/src/interface/types/Theme.ts +++ b/src/interface/types/Theme.ts @@ -8,6 +8,11 @@ export type Theme = { is_favorited?: boolean; favorite_count?: number; download_count?: number; + author?: string; + featured?: boolean; + tags?: string[]; + /** Unix time in seconds (API list/detail). */ + created_at?: number; /** Unix seconds — last server update (GET /api/themes). */ updated_at?: number; };