Implement keyboard typing sound feedback and refactor login sound handling
- Added a keyboard typing sound effect that plays on character input in App.vue for enhanced user interaction. - Refactored the playSciFiTypingTick function in useLoginSounds.ts, replacing it with playKeyboardTypingSound for a more cohesive audio experience. - Removed unnecessary keydown event handlers from Login.vue to streamline the code and improve clarity. - Updated CLI store to play navigation sounds when toggling the CLI visibility.
This commit is contained in:
parent
c9f6e6b8ae
commit
578551f617
@ -67,6 +67,7 @@ import AppLauncherOverlay from './components/AppLauncherOverlay.vue'
|
|||||||
import Screensaver from './components/Screensaver.vue'
|
import Screensaver from './components/Screensaver.vue'
|
||||||
import HelpGuideModal from './components/HelpGuideModal.vue'
|
import HelpGuideModal from './components/HelpGuideModal.vue'
|
||||||
import { useControllerNav } from '@/composables/useControllerNav'
|
import { useControllerNav } from '@/composables/useControllerNav'
|
||||||
|
import { playKeyboardTypingSound } from '@/composables/useLoginSounds'
|
||||||
import { useSpotlightStore } from '@/stores/spotlight'
|
import { useSpotlightStore } from '@/stores/spotlight'
|
||||||
import { useCLIStore } from '@/stores/cli'
|
import { useCLIStore } from '@/stores/cli'
|
||||||
import { useMessageToast } from '@/composables/useMessageToast'
|
import { useMessageToast } from '@/composables/useMessageToast'
|
||||||
@ -129,6 +130,10 @@ function onKeyDown(e: KeyboardEvent) {
|
|||||||
screensaverStore.activate()
|
screensaverStore.activate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Keyboard typing sound - plays on any character typed in inputs (global)
|
||||||
|
if (isInput && e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
||||||
|
playKeyboardTypingSound()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@ -172,45 +172,27 @@ export function playTypingTick() {
|
|||||||
a.play().catch(() => {})
|
a.play().catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sci-fi typing sound - short synth blip + click per key, for login/password fields */
|
/** Keyboard input sound - plays on any character typed in inputs. Separate from typing tick/intro typing. */
|
||||||
export function playSciFiTypingTick() {
|
let keyboardInputPool: HTMLAudioElement[] = []
|
||||||
const ctx = getContext()
|
const KEYBOARD_INPUT_POOL_SIZE = 5
|
||||||
if (!ctx) return
|
|
||||||
|
|
||||||
try {
|
function getKeyboardInputSound(): HTMLAudioElement {
|
||||||
if (ctx.state === 'suspended') ctx.resume()
|
if (keyboardInputPool.length === 0) {
|
||||||
} catch {
|
for (let i = 0; i < KEYBOARD_INPUT_POOL_SIZE; i++) {
|
||||||
return
|
const a = new Audio('/assets/audio/typing.mp3')
|
||||||
|
a.volume = 0.5
|
||||||
|
keyboardInputPool.push(a)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
const a = keyboardInputPool.shift()!
|
||||||
|
keyboardInputPool.push(a)
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
const t = ctx.currentTime
|
export function playKeyboardTypingSound() {
|
||||||
|
const a = getKeyboardInputSound()
|
||||||
// Main tone
|
a.currentTime = 0
|
||||||
const osc = ctx.createOscillator()
|
a.play().catch(() => {})
|
||||||
const gain = ctx.createGain()
|
|
||||||
osc.type = 'sine'
|
|
||||||
osc.frequency.setValueAtTime(880, t)
|
|
||||||
osc.frequency.exponentialRampToValueAtTime(660, t + 0.03)
|
|
||||||
gain.gain.setValueAtTime(0, t)
|
|
||||||
gain.gain.linearRampToValueAtTime(0.08, t + 0.005)
|
|
||||||
gain.gain.exponentialRampToValueAtTime(0.001, t + 0.06)
|
|
||||||
osc.connect(gain)
|
|
||||||
gain.connect(ctx.destination)
|
|
||||||
osc.start(t)
|
|
||||||
osc.stop(t + 0.06)
|
|
||||||
|
|
||||||
// Click - short transient at key press
|
|
||||||
const clickOsc = ctx.createOscillator()
|
|
||||||
const clickGain = ctx.createGain()
|
|
||||||
clickOsc.type = 'square'
|
|
||||||
clickOsc.frequency.setValueAtTime(1200, t)
|
|
||||||
clickGain.gain.setValueAtTime(0, t)
|
|
||||||
clickGain.gain.linearRampToValueAtTime(0.04, t + 0.001)
|
|
||||||
clickGain.gain.exponentialRampToValueAtTime(0.001, t + 0.012)
|
|
||||||
clickOsc.connect(clickGain)
|
|
||||||
clickGain.connect(ctx.destination)
|
|
||||||
clickOsc.start(t)
|
|
||||||
clickOsc.stop(t + 0.012)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gaming-style boot thud - soft impact when dashboard loads */
|
/** Gaming-style boot thud - soft impact when dashboard loads */
|
||||||
|
|||||||
@ -15,7 +15,9 @@ export const useCLIStore = defineStore('cli', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
isOpen.value = !isOpen.value
|
const wasOpen = isOpen.value
|
||||||
|
isOpen.value = !wasOpen
|
||||||
|
if (!wasOpen) playNavSound('action')
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -41,7 +41,6 @@
|
|||||||
type="password"
|
type="password"
|
||||||
class="w-full px-4 py-3 bg-transparent border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-white/40 focus:ring-1 focus:ring-white/20 transition-colors"
|
class="w-full px-4 py-3 bg-transparent border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-white/40 focus:ring-1 focus:ring-white/20 transition-colors"
|
||||||
placeholder="Enter a password (min 8 characters)"
|
placeholder="Enter a password (min 8 characters)"
|
||||||
@keydown="onPasswordKeydown"
|
|
||||||
@keyup.enter="handleSetupWithSound"
|
@keyup.enter="handleSetupWithSound"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
/>
|
/>
|
||||||
@ -57,7 +56,6 @@
|
|||||||
type="password"
|
type="password"
|
||||||
class="w-full px-4 py-3 bg-transparent border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-white/40 focus:ring-1 focus:ring-white/20 transition-colors"
|
class="w-full px-4 py-3 bg-transparent border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-white/40 focus:ring-1 focus:ring-white/20 transition-colors"
|
||||||
placeholder="Confirm your password"
|
placeholder="Confirm your password"
|
||||||
@keydown="onPasswordKeydown"
|
|
||||||
@keyup.enter="handleSetupWithSound"
|
@keyup.enter="handleSetupWithSound"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
/>
|
/>
|
||||||
@ -91,7 +89,6 @@
|
|||||||
type="password"
|
type="password"
|
||||||
class="w-full px-4 py-3 bg-transparent border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-white/40 focus:ring-1 focus:ring-white/20 transition-colors"
|
class="w-full px-4 py-3 bg-transparent border border-white/20 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-white/40 focus:ring-1 focus:ring-white/20 transition-colors"
|
||||||
placeholder="Enter your password"
|
placeholder="Enter your password"
|
||||||
@keydown="onPasswordKeydown"
|
|
||||||
@keyup.enter="handleLoginWithSound"
|
@keyup.enter="handleLoginWithSound"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
/>
|
/>
|
||||||
@ -139,7 +136,7 @@ import AnimatedLogo from '@/components/AnimatedLogo.vue'
|
|||||||
import { useAppStore } from '../stores/app'
|
import { useAppStore } from '../stores/app'
|
||||||
import { useLoginTransitionStore } from '../stores/loginTransition'
|
import { useLoginTransitionStore } from '../stores/loginTransition'
|
||||||
import { rpcClient } from '../api/rpc-client'
|
import { rpcClient } from '../api/rpc-client'
|
||||||
import { startSynthwave, stopSynthwave, playLoginSuccessWhoosh, playPop, playSciFiTypingTick } from '@/composables/useLoginSounds'
|
import { startSynthwave, stopSynthwave, playLoginSuccessWhoosh, playPop } from '@/composables/useLoginSounds'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const store = useAppStore()
|
const store = useAppStore()
|
||||||
@ -220,12 +217,6 @@ async function handleSetup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPasswordKeydown(e: KeyboardEvent) {
|
|
||||||
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
||||||
playSciFiTypingTick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLoginWithSound() {
|
function handleLoginWithSound() {
|
||||||
if (!loading.value && password.value) {
|
if (!loading.value && password.value) {
|
||||||
playPop()
|
playPop()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user