--- name: gamepad-nav description: Expert-level gamepad/controller navigation for Archipelago's console-style UI. Use when working on D-pad navigation, focus management, spatial navigation, controller support, or 10-foot UI design. --- # Gamepad Navigation Expert When working on gamepad/controller navigation in Archipelago, apply these console-derived patterns. ## Architecture **File**: `neode-ui/src/composables/useControllerNav.ts` **Styles**: `neode-ui/src/style.css` (focus-visible rules) The system uses `data-` attributes for navigation zones: - `data-controller-zone="sidebar"` / `"main"` — navigation zones - `data-controller-container` — focusable card/group (Enter drills in, Escape exits) - `data-controller-focusable` — marks element as focusable - `data-controller-ignore` — excludes from navigation - `data-controller-install` / `data-controller-launch` — app-specific actions ## Core Navigation Rules (Xbox/PS5/Switch consensus) ### D-pad Movement - **4 directions only** — Up/Down/Left/Right, one element per press - **Spatial navigation** — find nearest focusable in direction using bounding rect geometry - **Distance formula**: `euclidean + displacement - alignment` with overlap scoring - **Tiebreaker for up/down**: prefer leftmost element (visual consistency in grids) ### Wrapping - **Linear lists (1D)**: WRAP (last to first, first to last) — sidebar menu, tab bars - **Grids (2D)**: NO WRAP — stops at edges, prevents disorientation ### Zone Transitions - **Right from sidebar** -> first focusable in main content (topmost) - **Left from main's leftmost** -> sidebar's active tab (`.nav-tab-active`) - **Focus memory**: remember last-focused element per zone, restore on re-entry ### Container Navigation - **Enter/A**: drill into container (focus first inner element) - **Escape/B**: exit container (focus the container itself) - **D-pad inside container**: navigate among inner elements spatially - **D-pad at container edge**: exit and navigate to adjacent container ### Text Input Handling - **Up/Down arrows**: EXIT input, navigate to nearest element above/below - **Left/Right arrows**: stay in input (cursor movement) - **Enter**: if next focusable is a button, click it directly (submit) - **Escape**: blur input, navigate out ### Button Mapping | Action | Xbox | PlayStation | Switch | Keyboard | |--------|------|------------|--------|----------| | Confirm | A | Cross | A | Enter | | Back | B | Circle | B | Escape | | Navigate | D-pad | D-pad | D-pad | Arrow keys | ## Focus Visual Design ### Console standard (10-foot viewing distance) - **Minimum 2px** border/outline (1px flickers on interlaced TVs) - **3:1 contrast ratio** against adjacent colors (WCAG 2.4.7) - **Smooth transitions**: 150-200ms ease-out - **GPU compositing**: use `translateZ(0)` on animated elements - **Never pure white** (#f1f1f1 prevents TV halo effects) ### Archipelago Focus Patterns ```css /* Global — subtle outline that follows border-radius */ *:focus-visible { outline: 2px solid rgba(251, 146, 60, 0.6); outline-offset: 2px; } /* Containers — soft glow + slight scale */ [data-controller-container]:focus-visible { outline: none; transform: scale(1.01); box-shadow: 0 0 0 1.5px rgba(251, 146, 60, 0.5), 0 0 20px rgba(251, 146, 60, 0.15); } /* Sidebar items — background tint + thin ring */ .sidebar-nav-item:focus-visible { outline: none; background: rgba(251, 146, 60, 0.12); box-shadow: 0 0 0 1.5px rgba(251, 146, 60, 0.45); } ``` ## Gamepad API Integration ### Polling - Poll `navigator.getGamepads()` in `requestAnimationFrame` loop (cheap, returns snapshot) - Apply deadzone: `Math.abs(axis) > 0.2` before registering input - D-pad repeat: 400ms initial delay, 150ms interval (gamepads don't auto-repeat) ### Button indices (W3C Standard Mapping) - 0=A, 1=B, 2=X, 3=Y, 4=LB, 5=RB, 12=DUp, 13=DDown, 14=DLeft, 15=DRight ## When Investigating Issues 1. Check `useControllerNav.ts` for the `handleKeyDown` function 2. Check `data-controller-*` attributes in the view's template 3. Verify focusable elements are in the right `data-controller-zone` 4. Test with: arrow keys on keyboard (simulates D-pad) 5. Check `style.css` for `focus-visible` rules ## Key Sources - [Xbox Accessibility Guideline 112](https://learn.microsoft.com/en-us/gaming/accessibility/xbox-accessibility-guidelines/112) - [Microsoft: Gamepad and remote interactions](https://learn.microsoft.com/en-us/windows/apps/design/input/gamepad-and-remote-interactions) - [W3C CSS Spatial Navigation](https://www.w3.org/TR/css-nav-1/) - [W3C Gamepad Spec](https://w3c.github.io/gamepad/) - [Norigin Spatial Navigation (React reference)](https://github.com/NoriginMedia/Norigin-Spatial-Navigation)