fix: onboarding gamepad — autofocus, click sounds, focus styles
All screens:
- playNavSound('action') on every button click
- path-action-button orange focus glow (removed from suppression list)
Per-screen autofocus:
- Intro: CTA button (after animation)
- Path: Continue button
- Identity: name input
- Backup: passphrase input, Continue after download
- Verify: Sign Challenge, then Finish after verification
- Done: Set Password button
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -93,6 +93,7 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
import { playNavSound } from '@/composables/useNavSounds'
|
||||
|
||||
const router = useRouter()
|
||||
const passphraseInput = ref<HTMLInputElement | null>(null)
|
||||
@@ -154,6 +155,7 @@ async function downloadBackup() {
|
||||
}
|
||||
|
||||
function proceed() {
|
||||
playNavSound('action')
|
||||
router.push('/onboarding/verify').catch(() => {})
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
<!-- Set Password Button -->
|
||||
<p class="text-xs text-white/50 mb-3">You'll create your node password next</p>
|
||||
<button
|
||||
ref="setPasswordButton"
|
||||
@click="goToLogin"
|
||||
class="path-action-button path-action-button--continue mx-auto"
|
||||
>
|
||||
@@ -56,11 +57,21 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { playNavSound } from '@/composables/useNavSounds'
|
||||
|
||||
const router = useRouter()
|
||||
const setPasswordButton = ref<HTMLButtonElement | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
setPasswordButton.value?.focus({ preventScroll: true })
|
||||
}, 500)
|
||||
})
|
||||
|
||||
function goToLogin() {
|
||||
playNavSound('action')
|
||||
router.push('/login').catch(() => {})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<button
|
||||
v-for="p in purposes"
|
||||
:key="p.value"
|
||||
@click="selectedPurpose = p.value"
|
||||
@click="playNavSound('action'); selectedPurpose = p.value"
|
||||
class="px-4 py-3 rounded-lg border text-left transition-all"
|
||||
:class="selectedPurpose === p.value
|
||||
? 'bg-white/15 border-white/30 text-white'
|
||||
@@ -79,6 +79,7 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
import { playNavSound } from '@/composables/useNavSounds'
|
||||
|
||||
const router = useRouter()
|
||||
const nameInput = ref<HTMLInputElement | null>(null)
|
||||
@@ -117,6 +118,7 @@ async function createIdentity() {
|
||||
purpose: selectedPurpose.value
|
||||
}
|
||||
})
|
||||
playNavSound('action')
|
||||
router.push('/onboarding/backup').catch(() => {})
|
||||
} catch (err) {
|
||||
if (isServerStartingError(err)) {
|
||||
|
||||
@@ -73,6 +73,7 @@ import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import AnimatedLogo from '@/components/AnimatedLogo.vue'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
import { playNavSound } from '@/composables/useNavSounds'
|
||||
|
||||
const router = useRouter()
|
||||
const ctaButton = ref<HTMLButtonElement | null>(null)
|
||||
@@ -85,6 +86,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
function goToOptions() {
|
||||
playNavSound('action')
|
||||
router.push('/onboarding/path').catch(() => {})
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { completeOnboarding } from '@/composables/useOnboarding'
|
||||
import { playNavSound } from '@/composables/useNavSounds'
|
||||
|
||||
const router = useRouter()
|
||||
const selected = ref<string | null>(null)
|
||||
@@ -100,6 +101,7 @@ async function proceed() {
|
||||
} catch (e) {
|
||||
if (import.meta.env.DEV) console.warn('completeOnboarding failed, localStorage fallback ensures onboarding is marked complete', e)
|
||||
}
|
||||
playNavSound('action')
|
||||
router.push('/login').catch(() => {})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -96,18 +96,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { playNavSound } from '@/composables/useNavSounds'
|
||||
|
||||
const router = useRouter()
|
||||
const continueButton = ref<HTMLButtonElement | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
// Focus after slide transition completes (400ms + buffer)
|
||||
setTimeout(() => {
|
||||
continueButton.value?.focus({ preventScroll: true })
|
||||
}, 500)
|
||||
})
|
||||
|
||||
function proceed() {
|
||||
playNavSound('action')
|
||||
router.push('/onboarding/did').catch(() => {})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<p v-else-if="errorMessage" class="text-red-400 text-sm">{{ errorMessage }}</p>
|
||||
<!-- Sign Button (if not verified yet) -->
|
||||
<button
|
||||
ref="signButton"
|
||||
v-if="!verified"
|
||||
@click="signChallenge"
|
||||
:disabled="isSigning"
|
||||
@@ -65,6 +66,7 @@
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-center max-w-[600px] mx-auto flex-shrink-0 px-3 sm:px-4 pb-4 sm:pb-6">
|
||||
<button
|
||||
ref="finishButton"
|
||||
v-if="verified"
|
||||
@click="proceed"
|
||||
class="path-action-button path-action-button--continue"
|
||||
@@ -77,13 +79,22 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { completeOnboarding } from '@/composables/useOnboarding'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
import { playNavSound } from '@/composables/useNavSounds'
|
||||
|
||||
const router = useRouter()
|
||||
const signButton = ref<HTMLButtonElement | null>(null)
|
||||
const finishButton = ref<HTMLButtonElement | null>(null)
|
||||
const verified = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
signButton.value?.focus({ preventScroll: true })
|
||||
}, 500)
|
||||
})
|
||||
const isSigning = ref(false)
|
||||
const signature = ref('')
|
||||
const currentChallenge = ref('')
|
||||
@@ -119,6 +130,9 @@ async function signChallenge() {
|
||||
} else {
|
||||
verified.value = true
|
||||
}
|
||||
nextTick(() => {
|
||||
setTimeout(() => finishButton.value?.focus({ preventScroll: true }), 100)
|
||||
})
|
||||
return
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : ''
|
||||
@@ -138,6 +152,7 @@ async function signChallenge() {
|
||||
}
|
||||
|
||||
async function proceed() {
|
||||
playNavSound('action')
|
||||
try {
|
||||
await completeOnboarding()
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user