Update UI styles and enhance controller navigation functionality
- Replaced cyan color with yellow in various UI components for a cohesive visual theme. - Improved focus styles for controller navigation, adding subtle grow and glow effects to sidebar items and containers. - Enhanced controller navigation logic to support direct focus on app containers in the Marketplace and My Apps sections. - Introduced wheel scrolling support for better navigation experience within scrollable areas. - Removed unused audio tone function from useLoginSounds.ts to streamline code.
This commit is contained in:
@@ -201,6 +201,17 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
|
||||
const el = focusable[currentIndex] as HTMLElement
|
||||
|
||||
if (el.hasAttribute('data-controller-container')) {
|
||||
// Marketplace: Enter = install (click install button)
|
||||
if (el.hasAttribute('data-controller-install')) {
|
||||
const installBtn = el.querySelector<HTMLButtonElement>('[data-controller-install-btn]:not([disabled])')
|
||||
if (installBtn) {
|
||||
playNavSound('action')
|
||||
installBtn.click()
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
}
|
||||
// My Apps, etc: Enter = focus first inner control
|
||||
const inner = getInnerFocusables(el)
|
||||
const firstInner = inner[0]
|
||||
if (firstInner) {
|
||||
@@ -231,8 +242,10 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
|
||||
const mainEls = getElementsInZone('main')
|
||||
const hasZones = sidebarEls.length > 0 && mainEls.length > 0
|
||||
|
||||
// Right: from sidebar → main
|
||||
const firstMain = mainEls[0]
|
||||
// Right: from sidebar → main (on App Store/My Apps, go straight to first app container)
|
||||
const mainZone = document.querySelector('[data-controller-zone="main"]')
|
||||
const firstAppContainer = mainZone?.querySelector<HTMLElement>('[data-controller-container]')
|
||||
const firstMain = firstAppContainer ?? mainEls[0]
|
||||
if (e.key === 'ArrowRight' && hasZones && isInZone(activeEl, 'sidebar') && firstMain) {
|
||||
playNavSound('move')
|
||||
firstMain.focus()
|
||||
@@ -254,13 +267,15 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
|
||||
return
|
||||
}
|
||||
|
||||
// No element in that direction: Left from leftmost → sidebar
|
||||
// No element in that direction: Left from leftmost → sidebar (focus active tab, not logout)
|
||||
if (e.key === 'ArrowLeft' && dir === 'left') {
|
||||
const lastSidebar = sidebarEls[sidebarEls.length - 1]
|
||||
if (lastSidebar) {
|
||||
const sidebarZone = document.querySelector('[data-controller-zone="sidebar"]')
|
||||
const activeNavTab = sidebarZone?.querySelector<HTMLElement>('.nav-tab-active')
|
||||
const target = activeNavTab ?? sidebarEls[0]
|
||||
if (target) {
|
||||
playNavSound('move')
|
||||
lastSidebar.focus()
|
||||
lastSidebar.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
|
||||
target.focus()
|
||||
target.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
@@ -350,9 +365,38 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
|
||||
isControllerActive.value = gamepadCount.value > 0
|
||||
}
|
||||
|
||||
/** Find nearest scrollable ancestor (overflow-y auto/scroll) */
|
||||
function getScrollableAncestor(el: HTMLElement | null): HTMLElement | null {
|
||||
let p = el?.parentElement
|
||||
while (p) {
|
||||
const style = getComputedStyle(p)
|
||||
const oy = style.overflowY
|
||||
if ((oy === 'auto' || oy === 'scroll') && p.scrollHeight > p.clientHeight) return p
|
||||
p = p.parentElement
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** Ensure wheel scrolls the scrollable area containing the focused element */
|
||||
function handleWheel(e: WheelEvent) {
|
||||
const active = document.activeElement as HTMLElement | null
|
||||
if (!active) return
|
||||
const scrollable = getScrollableAncestor(active)
|
||||
if (!scrollable) return
|
||||
if (e.deltaY !== 0) {
|
||||
scrollable.scrollTop += e.deltaY
|
||||
e.preventDefault()
|
||||
}
|
||||
if (e.deltaX !== 0 && scrollable.scrollWidth > scrollable.clientWidth) {
|
||||
scrollable.scrollLeft += e.deltaX
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkGamepads()
|
||||
window.addEventListener('keydown', handleKeyDown, true)
|
||||
window.addEventListener('wheel', handleWheel, { passive: false })
|
||||
window.addEventListener('gamepadconnected', handleGamepadConnected)
|
||||
window.addEventListener('gamepaddisconnected', handleGamepadDisconnected)
|
||||
pollIntervalId = setInterval(handleGamepadInput, 500)
|
||||
@@ -360,6 +404,7 @@ export function useControllerNav(containerRef?: { value: HTMLElement | null }) {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', handleKeyDown, true)
|
||||
window.removeEventListener('wheel', handleWheel)
|
||||
window.removeEventListener('gamepadconnected', handleGamepadConnected)
|
||||
window.removeEventListener('gamepaddisconnected', handleGamepadDisconnected)
|
||||
if (pollIntervalId) clearInterval(pollIntervalId)
|
||||
|
||||
@@ -16,28 +16,6 @@ function getContext(): AudioContext | null {
|
||||
}
|
||||
}
|
||||
|
||||
function playTone(
|
||||
ctx: AudioContext,
|
||||
freq: number,
|
||||
duration: number,
|
||||
gain: number,
|
||||
type: OscillatorType = 'sine',
|
||||
startOffset = 0,
|
||||
dest: AudioNode = ctx.destination
|
||||
) {
|
||||
const osc = ctx.createOscillator()
|
||||
const g = ctx.createGain()
|
||||
osc.connect(g)
|
||||
g.connect(dest)
|
||||
g.gain.setValueAtTime(0, ctx.currentTime)
|
||||
g.gain.linearRampToValueAtTime(gain, ctx.currentTime + 0.01)
|
||||
g.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration)
|
||||
osc.frequency.value = freq
|
||||
osc.type = type
|
||||
osc.start(ctx.currentTime + startOffset)
|
||||
osc.stop(ctx.currentTime + startOffset + duration)
|
||||
}
|
||||
|
||||
const INTRO_AUDIO_URL = '/assets/audio/cosmic-updrift.mp3'
|
||||
const LOOP_START_URL = '/assets/audio/loop-start.mp3'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user