diff --git a/neode-ui/src/style.css b/neode-ui/src/style.css index 5df04f80..ace3c75b 100644 --- a/neode-ui/src/style.css +++ b/neode-ui/src/style.css @@ -1926,6 +1926,129 @@ html:has(body.video-background-active)::before { } } +/* ===== iPhone-style App Icon Grid (mobile) ===== */ + +.app-icon-grid-wrap { + width: 100%; +} + +.app-icon-pages { + display: flex; + overflow-x: auto; + scroll-snap-type: x mandatory; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; +} +.app-icon-pages::-webkit-scrollbar { + display: none; +} + +.app-icon-page { + flex: 0 0 100%; + scroll-snap-align: start; + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 20px 12px; + padding: 8px 4px 16px; + min-height: 0; +} + +.app-icon-item { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform 0.1s ease; +} +.app-icon-item:active { + transform: scale(0.9); +} + +.app-icon-frame { + position: relative; + width: 60px; + height: 60px; + border-radius: 14px; + overflow: hidden; + background: rgba(255, 255, 255, 0.08); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); +} + +.app-icon-img { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* Status dot — top-right of icon */ +.app-icon-status { + position: absolute; + top: -2px; + right: -2px; + width: 12px; + height: 12px; + border-radius: 50%; + border: 2px solid #000; +} +.app-icon-status-running { + background: #22c55e; +} +.app-icon-status-error { + background: #ef4444; +} +.app-icon-status-transition { + background: #f59e0b; + animation: pulse 1.5s infinite; +} + +.app-icon-installing { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.6); + border-radius: 14px; +} + +.app-icon-label { + font-size: 11px; + line-height: 1.2; + color: rgba(255, 255, 255, 0.85); + text-align: center; + max-width: 72px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Page indicator dots */ +.app-icon-dots { + display: flex; + justify-content: center; + gap: 6px; + padding: 4px 0 8px; +} + +.app-icon-dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.25); + border: none; + padding: 0; + cursor: pointer; + transition: background 0.2s, transform 0.2s; +} +.app-icon-dot-active { + background: rgba(247, 147, 26, 0.9); + transform: scale(1.3); +} + +/* ===== End App Icon Grid ===== */ + /* Monitoring dashboard */ .monitoring-stat-card { background: rgba(0, 0, 0, 0.3); @@ -2120,6 +2243,29 @@ html:has(body.video-background-active)::before { z-index: 1; } +.discover-hero-layout { + display: flex; + align-items: center; + gap: 2rem; +} + +.discover-hero-content { + flex: 1; + min-width: 0; +} + +.discover-hero-face { + display: none; + flex-shrink: 0; + opacity: 0.85; +} + +@media (min-width: 1280px) { + .discover-hero-face { + display: block; + } +} + .discover-hero-accent { background: linear-gradient(90deg, #fb923c, #f59e0b, #fb923c); background-size: 200% 100%; @@ -2175,9 +2321,12 @@ html:has(body.video-background-active)::before { .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); + border-radius: 1rem; + background-color: rgba(0, 0, 0, 0.65); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); + border: 1px solid rgba(255, 255, 255, 0.18); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45); transition: border-color 0.3s ease, transform 0.3s ease; } @@ -2187,8 +2336,9 @@ html:has(body.video-background-active)::before { } .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%); + background-color: rgba(0, 0, 0, 0.65); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); } .fleet-matrix-table { diff --git a/neode-ui/src/views/AppSession.vue b/neode-ui/src/views/AppSession.vue index 2df34578..5f4cf43c 100644 --- a/neode-ui/src/views/AppSession.vue +++ b/neode-ui/src/views/AppSession.vue @@ -37,6 +37,17 @@ @refresh="refresh" @open-new-tab-and-back="openNewTabAndBack" /> + + + + + + + { .content-fade-leave-to { opacity: 0; } + +/* Mobile floating glass close button */ +.app-session-mobile-close { + position: fixed; + bottom: calc(24px + env(safe-area-inset-bottom, 0px)); + left: 50%; + transform: translateX(-50%); + z-index: 2500; + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: rgba(0, 0, 0, 0.45); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid rgba(255, 255, 255, 0.15); + color: rgba(255, 255, 255, 0.85); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); + transition: background 0.15s ease, transform 0.15s ease; +} +.app-session-mobile-close:active { + background: rgba(0, 0, 0, 0.65); + transform: translateX(-50%) scale(0.9); +} diff --git a/neode-ui/src/views/Apps.vue b/neode-ui/src/views/Apps.vue index 7b32c70d..c3a07529 100644 --- a/neode-ui/src/views/Apps.vue +++ b/neode-ui/src/views/Apps.vue @@ -91,8 +91,16 @@ {{ t('apps.noResults', { query: searchQuery }) }} - - + + + + + + + - + diff --git a/neode-ui/src/views/apps/AppIconGrid.vue b/neode-ui/src/views/apps/AppIconGrid.vue new file mode 100644 index 00000000..74528163 --- /dev/null +++ b/neode-ui/src/views/apps/AppIconGrid.vue @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + {{ getTitle(id, pkg) }} + + + + + + + + + + + + diff --git a/neode-ui/src/views/dashboard/DashboardMobileNav.vue b/neode-ui/src/views/dashboard/DashboardMobileNav.vue index 13b45f57..f617c311 100644 --- a/neode-ui/src/views/dashboard/DashboardMobileNav.vue +++ b/neode-ui/src/views/dashboard/DashboardMobileNav.vue @@ -1,7 +1,7 @@ - + - {{ item.label }} - AIUI @@ -141,6 +140,11 @@ const uiMode = useUIModeStore() const mobileTabBar = ref(null) +// Hide tab bar when an app session is open (fullscreen on mobile) +const isAppSessionActive = computed(() => { + return route.name === 'app-session' || !!appLauncher.panelAppId +}) + // Show persistent tabs for Apps/Marketplace on mobile const showAppsTabs = computed(() => { if (typeof window === 'undefined') return false
{{ t('apps.noResults', { query: searchQuery }) }}