From 5e19a80f9dc493070f636619b162d1094c271604 Mon Sep 17 00:00:00 2001 From: Dorian Date: Thu, 19 Mar 2026 15:14:12 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20add=20Discover=20page=20=E2=80=94=20cyp?= =?UTF-8?q?herpunk=20app=20store=20with=20sovereignty=20messaging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- neode-ui/src/router/index.ts | 10 + neode-ui/src/style.css | 257 +++++ neode-ui/src/views/AppDetails.vue | 6 +- neode-ui/src/views/Dashboard.vue | 6 +- neode-ui/src/views/Discover.vue | 967 +++++++++++++++++++ neode-ui/src/views/Marketplace.vue | 21 +- neode-ui/src/views/MarketplaceAppDetails.vue | 6 +- 7 files changed, 1262 insertions(+), 11 deletions(-) create mode 100644 neode-ui/src/views/Discover.vue diff --git a/neode-ui/src/router/index.ts b/neode-ui/src/router/index.ts index e1559e9c..4f8fe16c 100644 --- a/neode-ui/src/router/index.ts +++ b/neode-ui/src/router/index.ts @@ -98,6 +98,11 @@ const router = createRouter({ name: 'lightning-channels', component: () => import('../views/apps/LightningChannels.vue'), }, + { + path: 'discover', + name: 'discover', + component: () => import('../views/Discover.vue'), + }, { path: 'marketplace', name: 'marketplace', @@ -134,6 +139,11 @@ const router = createRouter({ name: 'monitoring', component: () => import('../views/Monitoring.vue'), }, + { + path: 'fleet', + name: 'fleet', + component: () => import('../views/Fleet.vue'), + }, { path: 'server/federation', name: 'federation', diff --git a/neode-ui/src/style.css b/neode-ui/src/style.css index ca112b85..6b52bd19 100644 --- a/neode-ui/src/style.css +++ b/neode-ui/src/style.css @@ -1904,3 +1904,260 @@ html:has(body.video-background-active)::before { text-align: right; } +/* Fleet dashboard */ +.fleet-node-card { + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 0.75rem; + padding: 1rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.fleet-node-card:hover { + transform: translateY(-2px); + border-color: rgba(255, 255, 255, 0.15); + background: rgba(0, 0, 0, 0.4); +} + +.fleet-node-card-selected { + border-color: rgba(251, 146, 60, 0.4); + background: rgba(251, 146, 60, 0.06); +} + +.fleet-status-dot { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; +} + +.fleet-dot-online { + background: #4ade80; + box-shadow: 0 0 6px rgba(74, 222, 128, 0.4); +} + +.fleet-dot-offline { + background: #ef4444; + box-shadow: 0 0 6px rgba(239, 68, 68, 0.4); +} + +.fleet-version-badge { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: 9999px; + font-size: 0.625rem; + font-weight: 500; + background: rgba(59, 130, 246, 0.15); + color: #60a5fa; + border: 1px solid rgba(59, 130, 246, 0.2); +} + +.fleet-metric-row { + display: flex; + align-items: center; + gap: 8px; +} + +.fleet-bar-track { + flex: 1; + height: 6px; + background: rgba(255, 255, 255, 0.08); + border-radius: 3px; + overflow: hidden; +} + +.fleet-bar-fill { + height: 100%; + border-radius: 3px; + transition: width 0.3s ease; +} + +.fleet-node-badge { + display: inline-flex; + align-items: center; + padding: 1px 6px; + border-radius: 4px; + font-size: 0.625rem; + font-family: ui-monospace, monospace; + font-weight: 500; + background: rgba(255, 255, 255, 0.08); + color: rgba(255, 255, 255, 0.6); +} + +.fleet-sort-btn { + padding: 4px 10px; + border-radius: 6px; + font-size: 0.625rem; + font-weight: 500; + color: rgba(255, 255, 255, 0.5); + background: transparent; + border: 1px solid transparent; + cursor: pointer; + transition: all 0.2s ease; +} + +.fleet-sort-btn:hover { + color: rgba(255, 255, 255, 0.7); + background: rgba(255, 255, 255, 0.05); +} + +.fleet-sort-btn-active { + color: rgba(255, 255, 255, 0.9); + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.12); +} + +.fleet-text-warn { + color: #f59e0b !important; +} + +.fleet-text-danger { + color: #ef4444 !important; +} + +/* ── Discover Page (App Store) ──── */ + +.discover-container { + scrollbar-width: thin; + scrollbar-color: rgba(255, 255, 255, 0.2) rgba(255, 255, 255, 0.05); +} + +.discover-hero { + background: linear-gradient(135deg, rgba(0, 0, 0, 0.75) 0%, rgba(251, 146, 60, 0.06) 100%); + border-color: rgba(251, 146, 60, 0.12); + position: relative; + overflow: hidden; +} + +.discover-hero-scanline { + position: absolute; + inset: 0; + background: + repeating-linear-gradient( + 180deg, + transparent 0px, + transparent 3px, + rgba(255, 255, 255, 0.015) 3px, + rgba(255, 255, 255, 0.015) 4px + ); + pointer-events: none; + z-index: 1; +} + +.discover-hero-accent { + background: linear-gradient(90deg, #fb923c, #f59e0b, #fb923c); + background-size: 200% 100%; + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + animation: discover-gradient-shift 6s ease-in-out infinite; +} + +@keyframes discover-gradient-shift { + 0%, 100% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } +} + +.discover-terminal-tag { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: 4px; + font-size: 0.6875rem; + font-weight: 600; + font-family: ui-monospace, 'Cascadia Code', 'Fira Code', monospace; + text-transform: uppercase; + letter-spacing: 0.08em; + background: rgba(251, 146, 60, 0.12); + color: rgba(251, 146, 60, 0.8); + border: 1px solid rgba(251, 146, 60, 0.2); +} + +.discover-stat-pill { + display: inline-flex; + align-items: center; + gap: 0.375rem; + padding: 0.375rem 0.75rem; + border-radius: 9999px; + background: rgba(255, 255, 255, 0.06); + border: 1px solid rgba(255, 255, 255, 0.08); + font-size: 0.8125rem; +} + +.discover-featured-card { + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease, border-color 0.3s ease; + border-color: rgba(255, 255, 255, 0.12); +} + +.discover-featured-card:hover { + transform: translateY(-3px); + border-color: rgba(251, 146, 60, 0.25); + box-shadow: + 0 12px 32px rgba(0, 0, 0, 0.6), + 0 0 40px rgba(251, 146, 60, 0.06); +} + +.discover-installed-badge { + display: inline-flex; + align-items: center; + padding: 1px 6px; + border-radius: 4px; + font-size: 0.625rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + background: rgba(74, 222, 128, 0.15); + color: #4ade80; +} + +.discover-principle-card { + padding: 1.25rem; + border-radius: 0.75rem; + background: rgba(0, 0, 0, 0.4); + border: 1px solid rgba(255, 255, 255, 0.06); + transition: border-color 0.3s ease, transform 0.3s ease; +} + +.discover-principle-card:hover { + border-color: rgba(251, 146, 60, 0.2); + transform: translateY(-2px); +} + +.discover-app-card { + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease; +} + +.discover-app-card:hover { + transform: translateY(-2px); + background: rgba(255, 255, 255, 0.06); +} + +.discover-manifesto { + border-color: rgba(251, 146, 60, 0.1); + background: linear-gradient(135deg, rgba(0, 0, 0, 0.7) 0%, rgba(251, 146, 60, 0.03) 100%); +} + +.fleet-matrix-table { + width: 100%; + border-collapse: collapse; + font-size: 0.75rem; +} + +.fleet-matrix-header-cell { + padding: 8px 12px; + text-align: left; + font-weight: 500; + color: rgba(255, 255, 255, 0.5); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + white-space: nowrap; +} + +.fleet-matrix-cell { + padding: 6px 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.04); + white-space: nowrap; +} + diff --git a/neode-ui/src/views/AppDetails.vue b/neode-ui/src/views/AppDetails.vue index 1677d94e..eb5f7950 100644 --- a/neode-ui/src/views/AppDetails.vue +++ b/neode-ui/src/views/AppDetails.vue @@ -644,12 +644,12 @@ useModalKeyboard( // Determine back button text based on where user came from const backButtonText = computed(() => { - // Check if we came from marketplace via query parameter + if (route.query.from === 'discover') { + return 'Back to Discover' + } if (route.query.from === 'marketplace') { return t('appDetails.backToStore') } - - // Default to My Apps return t('appDetails.backToApps') }) diff --git a/neode-ui/src/views/Dashboard.vue b/neode-ui/src/views/Dashboard.vue index 0cef54de..a69d9f68 100644 --- a/neode-ui/src/views/Dashboard.vue +++ b/neode-ui/src/views/Dashboard.vue @@ -81,7 +81,7 @@ :key="item.path" :to="item.path" class="sidebar-nav-item flex items-center gap-3 px-4 py-3 rounded-lg text-white/80 hover:bg-white/10 hover:text-white transition-colors" - :class="{ 'nav-tab-active': item.isCombined && (route.path.includes('/apps') || route.path.includes('/marketplace') || route.path.includes('/app-session') || (item.path === '/dashboard/apps' && !!appLauncher.panelAppId)) }" + :class="{ 'nav-tab-active': item.isCombined && (route.path.includes('/apps') || route.path.includes('/marketplace') || route.path.includes('/discover') || route.path.includes('/app-session') || (item.path === '/dashboard/apps' && !!appLauncher.panelAppId)) }" :exact-active-class="item.isCombined ? undefined : 'nav-tab-active'" @click="appLauncher.closePanel()" :style="{ '--nav-stagger': idx }" @@ -201,7 +201,7 @@ App Store isDetailRoute(route.path)) const showAppsTabs = computed(() => { if (typeof window === 'undefined') return false if (window.innerWidth >= 768) return false - return route.path.includes('/apps') || route.path.includes('/marketplace') + return route.path.includes('/apps') || route.path.includes('/marketplace') || route.path.includes('/discover') }) // Show persistent tabs for Network/Cloud on mobile diff --git a/neode-ui/src/views/Discover.vue b/neode-ui/src/views/Discover.vue new file mode 100644 index 00000000..24a69eac --- /dev/null +++ b/neode-ui/src/views/Discover.vue @@ -0,0 +1,967 @@ + + + + + + + diff --git a/neode-ui/src/views/Marketplace.vue b/neode-ui/src/views/Marketplace.vue index 0ff0ddba..232cb1dc 100644 --- a/neode-ui/src/views/Marketplace.vue +++ b/neode-ui/src/views/Marketplace.vue @@ -82,6 +82,10 @@ Services
+ Discover