@@ -322,7 +441,7 @@ export const GamesPage = () => {
setActiveZone('details')}
+ onFocus={() => { if (mode === 'gamepad') setActiveZone('details'); }}
onEnterPress={() => console.log("Play Game", detailsQuery.data?.id)}
className="shrink-0"
focusKey="DETAILS_PLAY"
@@ -336,7 +455,7 @@ export const GamesPage = () => {
setActiveZone('details')}
+ onFocus={() => { if (mode === 'gamepad') setActiveZone('details'); }}
onEnterPress={() => console.log("Download", detailsQuery.data?.id)}
className="shrink-0"
focusKey="DETAILS_DOWNLOAD"
diff --git a/src/hooks/useGamepad.ts b/src/hooks/useGamepad.ts
index ef719ef..c8acced 100644
--- a/src/hooks/useGamepad.ts
+++ b/src/hooks/useGamepad.ts
@@ -8,6 +8,8 @@ const BUTTON_DPAD_UP = 12;
const BUTTON_DPAD_DOWN = 13;
const BUTTON_DPAD_LEFT = 14;
const BUTTON_DPAD_RIGHT = 15;
+const BUTTON_LB = 4;
+const BUTTON_RB = 5;
const AXIS_THRESHOLD = 0.5;
const INITIAL_DELAY_MS = 400;
const REPEAT_RATE_MS = 100;
@@ -86,6 +88,19 @@ export const useGamepad = () => {
handleInput(`gp${i}_right`, right, () => navigateByDirection('right', {}));
handleInput(`gp${i}_enter`, enter, () => dispatchEnter(), false);
handleInput(`gp${i}_back`, back, () => dispatchEscape(), false);
+
+ const lb = gp.buttons[BUTTON_LB]?.pressed;
+ const rb = gp.buttons[BUTTON_RB]?.pressed;
+ handleInput(`gp${i}_lb`, lb, () => window.dispatchEvent(new CustomEvent('previousplatform')));
+ handleInput(`gp${i}_rb`, rb, () => window.dispatchEvent(new CustomEvent('nextplatform')));
+
+ // Right analog stick — scroll the currently focused list
+ const rsX = gp.axes[2] ?? 0;
+ const rsY = gp.axes[3] ?? 0;
+ if (Math.abs(rsX) > AXIS_THRESHOLD || Math.abs(rsY) > AXIS_THRESHOLD) {
+ setMode('gamepad');
+ window.dispatchEvent(new CustomEvent('analogscroll', { detail: { dx: rsX, dy: rsY } }));
+ }
}
} catch (e) {
console.error("Gamepad Polling Error", e);
diff --git a/src/index.css b/src/index.css
index 79bfd82..dd018fd 100644
--- a/src/index.css
+++ b/src/index.css
@@ -23,7 +23,7 @@
background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover {
- background: var(--scrollbar-thumb-hover-color, rgba(255, 255, 255, 0.2));
+ background: var(--scrollbar-thumb-color, rgba(255, 255, 255, 0.2));
background-clip: content-box;
}
@@ -46,10 +46,12 @@ body {
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
/* Setting the CSS variable on the scrollable container causes the pseudo-element
- to inherit it, which is the correct way to drive scrollbar color via state. */
- .scrollbar-active {
+ to inherit it, which is the correct way to drive scrollbar color via state.
+ .scrollbar-active = gamepad / zone focus
+ .scrollbar-hoverable = mouse hover (same mechanism, same color) */
+ .scrollbar-active,
+ .scrollbar-hoverable:hover {
--scrollbar-thumb-color: var(--color-accent);
- --scrollbar-thumb-hover-color: var(--color-accent);
}
/* Auto-scroll animation for long game titles when focused */