feat: add Discover page — cypherpunk app store with sovereignty messaging

- New Discover.vue with hero banner, featured sovereignty stack apps,
  principle cards, manifesto footer, and full app grid
- Featured apps (Bitcoin Knots, LND, BTCPay, Vaultwarden) with
  expanded privacy/sovereignty descriptions
- Discover is first tab in categories bar on App Store pages
- Smart back navigation: detail pages return to Discover when navigated from there
- Category clicks from Discover navigate to Marketplace with category pre-selected
- Cypherpunk aesthetic: terminal tags, scanline overlays, gradient accents,
  animated Bitcoin orange headings
- Global CSS classes: discover-hero, discover-terminal-tag, discover-featured-card,
  discover-principle-card, discover-manifesto
- Route added: /dashboard/discover

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-19 15:14:12 +00:00
co-authored by Claude Opus 4.6
parent aabeb2e679
commit 5e19a80f9d
7 changed files with 1262 additions and 11 deletions
+17 -4
View File
@@ -82,6 +82,10 @@
<RouterLink to="/dashboard/apps?tab=services" class="mode-switcher-btn">Services</RouterLink>
</div>
<div class="mode-switcher flex-shrink-0">
<RouterLink
to="/dashboard/discover"
class="mode-switcher-btn"
>Discover</RouterLink>
<button
v-for="category in categoriesWithApps"
:key="category.id"
@@ -131,7 +135,8 @@
:data-controller-install="!(isInstalled(app.id) || installingApps.has(app.id)) && (app.source === 'local' || !!app.dockerImage) ? '1' : undefined"
tabindex="0"
role="link"
class="glass-card card-stagger p-6 hover:bg-white/10 transition-all cursor-pointer flex flex-col"
class="glass-card p-6 hover:bg-white/10 transition-all cursor-pointer flex flex-col"
:class="{ 'card-stagger': showStagger }"
:style="{ '--stagger-index': index }"
@click="viewAppDetails(app)"
@keydown.enter="viewAppDetails(app)"
@@ -365,9 +370,13 @@
</div>
</template>
<script lang="ts">
let marketplaceAnimationDone = false
</script>
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
import { useRouter, RouterLink } from 'vue-router'
import { useRouter, useRoute, RouterLink } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { rpcClient } from '@/api/rpc-client'
@@ -378,13 +387,16 @@ import { useModalKeyboard } from '@/composables/useModalKeyboard'
type MarketplaceApp = Partial<MarketplaceAppInfo> & { id: string; trustScore?: number; trustTier?: string; relayCount?: number }
const router = useRouter()
const route = useRoute()
const store = useAppStore()
const { t } = useI18n()
const showStagger = !marketplaceAnimationDone
const { setCurrentApp } = useMarketplaceApp()
const appLauncher = useAppLauncherStore()
// Category state
const selectedCategory = ref('all')
// Category state — read initial value from query param (set by Discover page navigation)
const selectedCategory = ref((route.query.category as string) || 'all')
const categories = computed(() => [
{ id: 'all', name: t('marketplace.all') },
@@ -721,6 +733,7 @@ function launchInstalledApp(app: MarketplaceApp) {
// Load community marketplace on mount
onMounted(() => {
marketplaceAnimationDone = true
if (communityApps.value.length === 0 && !loadingCommunity.value) {
loadCommunityMarketplace()
}