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:
parent
1c82b8285e
commit
9ea8877d20
@ -93,6 +93,7 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { rpcClient } from '@/api/rpc-client'
|
import { rpcClient } from '@/api/rpc-client'
|
||||||
|
import { playNavSound } from '@/composables/useNavSounds'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const passphraseInput = ref<HTMLInputElement | null>(null)
|
const passphraseInput = ref<HTMLInputElement | null>(null)
|
||||||
@ -154,6 +155,7 @@ async function downloadBackup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function proceed() {
|
function proceed() {
|
||||||
|
playNavSound('action')
|
||||||
router.push('/onboarding/verify').catch(() => {})
|
router.push('/onboarding/verify').catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,7 @@
|
|||||||
<!-- Set Password Button -->
|
<!-- Set Password Button -->
|
||||||
<p class="text-xs text-white/50 mb-3">You'll create your node password next</p>
|
<p class="text-xs text-white/50 mb-3">You'll create your node password next</p>
|
||||||
<button
|
<button
|
||||||
|
ref="setPasswordButton"
|
||||||
@click="goToLogin"
|
@click="goToLogin"
|
||||||
class="path-action-button path-action-button--continue mx-auto"
|
class="path-action-button path-action-button--continue mx-auto"
|
||||||
>
|
>
|
||||||
@ -56,11 +57,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { playNavSound } from '@/composables/useNavSounds'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const setPasswordButton = ref<HTMLButtonElement | null>(null)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setPasswordButton.value?.focus({ preventScroll: true })
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
|
|
||||||
function goToLogin() {
|
function goToLogin() {
|
||||||
|
playNavSound('action')
|
||||||
router.push('/login').catch(() => {})
|
router.push('/login').catch(() => {})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -33,7 +33,7 @@
|
|||||||
<button
|
<button
|
||||||
v-for="p in purposes"
|
v-for="p in purposes"
|
||||||
:key="p.value"
|
: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="px-4 py-3 rounded-lg border text-left transition-all"
|
||||||
:class="selectedPurpose === p.value
|
:class="selectedPurpose === p.value
|
||||||
? 'bg-white/15 border-white/30 text-white'
|
? 'bg-white/15 border-white/30 text-white'
|
||||||
@ -79,6 +79,7 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { rpcClient } from '@/api/rpc-client'
|
import { rpcClient } from '@/api/rpc-client'
|
||||||
|
import { playNavSound } from '@/composables/useNavSounds'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const nameInput = ref<HTMLInputElement | null>(null)
|
const nameInput = ref<HTMLInputElement | null>(null)
|
||||||
@ -117,6 +118,7 @@ async function createIdentity() {
|
|||||||
purpose: selectedPurpose.value
|
purpose: selectedPurpose.value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
playNavSound('action')
|
||||||
router.push('/onboarding/backup').catch(() => {})
|
router.push('/onboarding/backup').catch(() => {})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (isServerStartingError(err)) {
|
if (isServerStartingError(err)) {
|
||||||
|
|||||||
@ -73,6 +73,7 @@ import { ref, onMounted } from 'vue'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import AnimatedLogo from '@/components/AnimatedLogo.vue'
|
import AnimatedLogo from '@/components/AnimatedLogo.vue'
|
||||||
import { rpcClient } from '@/api/rpc-client'
|
import { rpcClient } from '@/api/rpc-client'
|
||||||
|
import { playNavSound } from '@/composables/useNavSounds'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const ctaButton = ref<HTMLButtonElement | null>(null)
|
const ctaButton = ref<HTMLButtonElement | null>(null)
|
||||||
@ -85,6 +86,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function goToOptions() {
|
function goToOptions() {
|
||||||
|
playNavSound('action')
|
||||||
router.push('/onboarding/path').catch(() => {})
|
router.push('/onboarding/path').catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -82,6 +82,7 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { completeOnboarding } from '@/composables/useOnboarding'
|
import { completeOnboarding } from '@/composables/useOnboarding'
|
||||||
|
import { playNavSound } from '@/composables/useNavSounds'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const selected = ref<string | null>(null)
|
const selected = ref<string | null>(null)
|
||||||
@ -100,6 +101,7 @@ async function proceed() {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (import.meta.env.DEV) console.warn('completeOnboarding failed, localStorage fallback ensures onboarding is marked complete', e)
|
if (import.meta.env.DEV) console.warn('completeOnboarding failed, localStorage fallback ensures onboarding is marked complete', e)
|
||||||
}
|
}
|
||||||
|
playNavSound('action')
|
||||||
router.push('/login').catch(() => {})
|
router.push('/login').catch(() => {})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -96,18 +96,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { playNavSound } from '@/composables/useNavSounds'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const continueButton = ref<HTMLButtonElement | null>(null)
|
const continueButton = ref<HTMLButtonElement | null>(null)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// Focus after slide transition completes (400ms + buffer)
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
continueButton.value?.focus({ preventScroll: true })
|
continueButton.value?.focus({ preventScroll: true })
|
||||||
}, 500)
|
}, 500)
|
||||||
})
|
})
|
||||||
|
|
||||||
function proceed() {
|
function proceed() {
|
||||||
|
playNavSound('action')
|
||||||
router.push('/onboarding/did').catch(() => {})
|
router.push('/onboarding/did').catch(() => {})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
<p v-else-if="errorMessage" class="text-red-400 text-sm">{{ errorMessage }}</p>
|
<p v-else-if="errorMessage" class="text-red-400 text-sm">{{ errorMessage }}</p>
|
||||||
<!-- Sign Button (if not verified yet) -->
|
<!-- Sign Button (if not verified yet) -->
|
||||||
<button
|
<button
|
||||||
|
ref="signButton"
|
||||||
v-if="!verified"
|
v-if="!verified"
|
||||||
@click="signChallenge"
|
@click="signChallenge"
|
||||||
:disabled="isSigning"
|
:disabled="isSigning"
|
||||||
@ -65,6 +66,7 @@
|
|||||||
<!-- Action Buttons -->
|
<!-- 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">
|
<div class="flex justify-center max-w-[600px] mx-auto flex-shrink-0 px-3 sm:px-4 pb-4 sm:pb-6">
|
||||||
<button
|
<button
|
||||||
|
ref="finishButton"
|
||||||
v-if="verified"
|
v-if="verified"
|
||||||
@click="proceed"
|
@click="proceed"
|
||||||
class="path-action-button path-action-button--continue"
|
class="path-action-button path-action-button--continue"
|
||||||
@ -77,13 +79,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted, nextTick } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { completeOnboarding } from '@/composables/useOnboarding'
|
import { completeOnboarding } from '@/composables/useOnboarding'
|
||||||
import { rpcClient } from '@/api/rpc-client'
|
import { rpcClient } from '@/api/rpc-client'
|
||||||
|
import { playNavSound } from '@/composables/useNavSounds'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const signButton = ref<HTMLButtonElement | null>(null)
|
||||||
|
const finishButton = ref<HTMLButtonElement | null>(null)
|
||||||
const verified = ref(false)
|
const verified = ref(false)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
signButton.value?.focus({ preventScroll: true })
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
const isSigning = ref(false)
|
const isSigning = ref(false)
|
||||||
const signature = ref('')
|
const signature = ref('')
|
||||||
const currentChallenge = ref('')
|
const currentChallenge = ref('')
|
||||||
@ -119,6 +130,9 @@ async function signChallenge() {
|
|||||||
} else {
|
} else {
|
||||||
verified.value = true
|
verified.value = true
|
||||||
}
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => finishButton.value?.focus({ preventScroll: true }), 100)
|
||||||
|
})
|
||||||
return
|
return
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const msg = err instanceof Error ? err.message : ''
|
const msg = err instanceof Error ? err.message : ''
|
||||||
@ -138,6 +152,7 @@ async function signChallenge() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function proceed() {
|
async function proceed() {
|
||||||
|
playNavSound('action')
|
||||||
try {
|
try {
|
||||||
await completeOnboarding()
|
await completeOnboarding()
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user