diff --git a/core/archipelago/src/api/rpc/package/runtime.rs b/core/archipelago/src/api/rpc/package/runtime.rs index ae136f7d..c1143fe5 100644 --- a/core/archipelago/src/api/rpc/package/runtime.rs +++ b/core/archipelago/src/api/rpc/package/runtime.rs @@ -7,7 +7,7 @@ use anyhow::{Context, Result}; /// Per-container graceful shutdown timeout in seconds. /// Bitcoin Core needs 600s to flush UTXO set, LND 330s for channel state, /// indexers 300s for index flush, databases 120s for WAL/transaction commit. -fn stop_timeout_secs(container_name: &str) -> &'static str { +pub fn stop_timeout_secs(container_name: &str) -> &'static str { let id = container_name.strip_prefix("archy-").unwrap_or(container_name); match id { "bitcoin-knots" | "bitcoin-core" | "bitcoin" => "600", diff --git a/neode-ui/dev-dist/sw.js b/neode-ui/dev-dist/sw.js index 13fdf824..8692272d 100644 --- a/neode-ui/dev-dist/sw.js +++ b/neode-ui/dev-dist/sw.js @@ -82,7 +82,7 @@ define(['./workbox-21a80088'], (function (workbox) { 'use strict'; "revision": "3ca0b8505b4bec776b69afdba2768812" }, { "url": "index.html", - "revision": "0.ld9oh2eb91o" + "revision": "0.huo00jkc7v4" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/neode-ui/src/components/SplashScreen.vue b/neode-ui/src/components/SplashScreen.vue index 32298792..b70d249a 100644 --- a/neode-ui/src/components/SplashScreen.vue +++ b/neode-ui/src/components/SplashScreen.vue @@ -277,6 +277,13 @@ if (!storedSeenIntro && isOnDashboard) { localStorage.setItem('neode_intro_seen', '1') } +function handleEnterKey(e: KeyboardEvent) { + if (e.key === 'Enter' && showTapToStart.value && !tapStartTransitioning.value) { + e.preventDefault() + handleTapToStart() + } +} + function onIntroLogoHover() { introLogoHover.value = true if (!tapStartTransitioning.value) playKeyboardTypingSound() @@ -465,10 +472,13 @@ onMounted(() => { showSplash.value = false document.body.classList.add('splash-complete') emit('complete') + } else { + window.addEventListener('keydown', handleEnterKey) } }) onBeforeUnmount(() => { + window.removeEventListener('keydown', handleEnterKey) if (introTypingTimeout) { clearTimeout(introTypingTimeout) introTypingTimeout = null diff --git a/neode-ui/src/composables/useControllerNav.ts b/neode-ui/src/composables/useControllerNav.ts index 3e011cbe..f859804b 100644 --- a/neode-ui/src/composables/useControllerNav.ts +++ b/neode-ui/src/composables/useControllerNav.ts @@ -115,7 +115,14 @@ function findNearestInDirection( scored.sort((a, b) => { if (b.overlap !== a.overlap) return b.overlap - a.overlap - return a.dist - b.dist + if (a.dist !== b.dist) return a.dist - b.dist + // Tiebreaker for up/down: prefer leftmost element in grid layouts + if (direction === 'up' || direction === 'down') { + const aLeft = a.el.getBoundingClientRect().left + const bLeft = b.el.getBoundingClientRect().left + return aLeft - bLeft + } + return 0 }) return scored[0]?.el ?? null } @@ -149,7 +156,7 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) { const target = e.target as HTMLElement if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') { - // Enter in text field: blur and move to next focusable element (e.g., submit button) + // Enter in text field: find next focusable — if it's a button, click it directly (submit) if (e.key === 'Enter' && target.tagName === 'INPUT' && (target as HTMLInputElement).type !== 'submit') { e.preventDefault() const root = containerRef?.value ?? document @@ -157,12 +164,24 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) { const idx = all.indexOf(target as HTMLElement) const next = idx >= 0 ? all[idx + 1] : undefined if (next) { - next.focus() - next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) + if (next.tagName === 'BUTTON' || next.getAttribute('role') === 'button') { + next.focus() + next.click() + } else { + next.focus() + next.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) + } } return } - if (e.key !== 'Escape') return + // Up/Down arrows: exit field and navigate to element above/below + if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { + e.preventDefault() + ;(target as HTMLElement).blur() + // Fall through to arrow key handling below + } else if (e.key !== 'Escape') { + return + } } const root = containerRef?.value ?? document diff --git a/neode-ui/src/style.css b/neode-ui/src/style.css index 26075d7b..f22c4499 100644 --- a/neode-ui/src/style.css +++ b/neode-ui/src/style.css @@ -46,11 +46,12 @@ backdrop-filter: blur(12px); } -/* Controller / keyboard navigation - soft glow only (no box outline) */ +/* Controller / keyboard navigation - orange border (Archipelago brand) */ *:focus-visible { outline: none; - box-shadow: 0 0 16px rgba(120, 180, 255, 0.2), 0 0 32px rgba(100, 160, 255, 0.1); - transition: box-shadow 0.2s ease; + border-color: rgba(251, 146, 60, 0.8) !important; + box-shadow: 0 0 0 2px rgba(251, 146, 60, 0.7), 0 0 16px rgba(251, 146, 60, 0.25); + transition: box-shadow 0.2s ease, border-color 0.2s ease; } /* Mobile touch targets — ensure tappable elements meet 44px minimum */ @@ -98,12 +99,12 @@ input[type="radio"]:active + * { transition: transform 0.2s ease, box-shadow 0.2s ease; } -/* Containers get subtle grow + inner glow when focused (gamepad selection) */ +/* Containers get subtle grow + orange glow when focused (gamepad selection) */ [data-controller-container]:focus-visible { transform: scale(1.02); box-shadow: - 0 0 24px rgba(120, 180, 255, 0.15), - 0 0 48px rgba(100, 160, 255, 0.08), + 0 0 0 2px rgba(251, 146, 60, 0.7), + 0 0 24px rgba(251, 146, 60, 0.2), inset 0 0 24px rgba(255, 255, 255, 0.03); } @@ -978,8 +979,8 @@ input[type="radio"]:active + * { .sidebar-nav-item:focus-visible { transform: scale(1.02) !important; box-shadow: - 0 0 24px rgba(120, 180, 255, 0.15), - 0 0 48px rgba(100, 160, 255, 0.08), + 0 0 0 2px rgba(251, 146, 60, 0.7), + 0 0 24px rgba(251, 146, 60, 0.2), inset 0 0 24px rgba(255, 255, 255, 0.03) !important; } } @@ -1302,7 +1303,7 @@ html:has(body.video-background-active)::before { background: rgba(255, 255, 255, 0.1); } .cloud-file-item:focus-visible { - box-shadow: 0 0 16px rgba(120, 180, 255, 0.2), 0 0 32px rgba(100, 160, 255, 0.1); + box-shadow: 0 0 0 2px rgba(251, 146, 60, 0.7), 0 0 16px rgba(251, 146, 60, 0.25); } .cloud-file-item-thumb { @@ -1480,7 +1481,7 @@ html:has(body.video-background-active)::before { transform: translateY(0); } .cloud-grid-card:focus-visible { - box-shadow: 0 0 16px rgba(120, 180, 255, 0.2), 0 0 32px rgba(100, 160, 255, 0.1); + box-shadow: 0 0 0 2px rgba(251, 146, 60, 0.7), 0 0 16px rgba(251, 146, 60, 0.25); } .cloud-grid-card-cover { diff --git a/neode-ui/src/views/OnboardingBackup.vue b/neode-ui/src/views/OnboardingBackup.vue index d8229e98..c493ed17 100644 --- a/neode-ui/src/views/OnboardingBackup.vue +++ b/neode-ui/src/views/OnboardingBackup.vue @@ -74,13 +74,7 @@ -
- +
-
- +