Stick Calibration and Gamepad Test

Stick Calibration and Gamepad Test

<div id="xp-gamepad-tester" style="max-width:900px;margin:20px auto;font-family:system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;color:#f5f5f5;background:#222;border-radius:8px;padding:20px;box-shadow:0 0 20px rgba(0,0,0,0.4);"> <h2 style="margin-top:0;text-align:center;">XP Controllers – Gamepad Tester</h2> <p style="font-size:14px;line-height:1.4;"> 1) Plug in your controller  2) Press any button once  3) Click <button id="xp-gp-start" style="padding:4px 10px;font-size:13px;border-radius:4px;border:none;background:#ff5722;color:#fff;cursor:pointer;">Start Tester</button> </p> <div id="xp-gp-status" style="margin:10px 0;padding:8px 10px;border-radius:4px;background:#333;font-size:13px;"> Status: Waiting for user interaction… </div> <div id="xp-gp-device" style="margin:10px 0;font-size:14px;"> No gamepad detected yet. </div> <div style="display:flex;flex-wrap:wrap;gap:16px;margin-top:10px;"> <!-- Buttons panel --> <div style="flex:1 1 280px;min-width:250px;"> <h3 style="margin:0 0 6px;font-size:15px;">Buttons</h3> <div id="xp-gp-buttons" style="display:flex;flex-wrap:wrap;gap:6px;"></div> </div> <!-- Axes panel --> <div style="flex:1 1 220px;min-width:220px;"> <h3 style="margin:0 0 6px;font-size:15px;">Axes</h3> <div id="xp-gp-axes"></div> </div> </div> <p style="margin-top:16px;font-size:11px;color:#aaa;"> Tip: Chrome/Edge & other modern browsers only expose gamepads on secure (HTTPS) pages and after you press a button on the controller. </p> </div> <script> (function () { const statusEl = document.getElementById('xp-gp-status'); const deviceEl = document.getElementById('xp-gp-device'); const buttonsEl = document.getElementById('xp-gp-buttons'); const axesEl = document.getElementById('xp-gp-axes'); const startBtn = document.getElementById('xp-gp-start'); let running = false; let lastIndex = null; function setStatus(text) { statusEl.textContent = 'Status: ' + text; } function getFirstGamepad() { const gps = navigator.getGamepads ? navigator.getGamepads() : []; for (let i = 0; i < gps.length; i++) { if (gps[i]) return gps[i]; } return null; } function renderButtons(gp) { if (!gp) return; // Build button elements once if needed if (!buttonsEl.hasChildNodes()) { for (let i = 0; i < gp.buttons.length; i++) { const b = document.createElement('div'); b.className = 'xp-gp-btn'; b.dataset.index = i; b.style.cssText = 'width:40px;height:28px;border-radius:4px;' + 'border:1px solid #555;font-size:11px;' + 'display:flex;align-items:center;justify-content:center;' + 'background:#2e2e2e;color:#ccc;'; b.textContent = i; buttonsEl.appendChild(b); } } const btnElems = buttonsEl.children; for (let i = 0; i < gp.buttons.length && i < btnElems.length; i++) { const pressed = gp.buttons[i].pressed || gp.buttons[i].value > 0.5; btnElems[i].style.background = pressed ? '#ff5722' : '#2e2e2e'; btnElems[i].style.color = pressed ? '#fff' : '#ccc'; } } function renderAxes(gp) { if (!gp) return; if (!axesEl.hasChildNodes()) { for (let i = 0; i < gp.axes.length; i++) { const row = document.createElement('div'); row.style.marginBottom = '6px'; const label = document.createElement('div'); label.textContent = 'Axis ' + i; label.style.fontSize = '12px'; const barWrap = document.createElement('div'); barWrap.style.cssText = 'height:8px;background:#333;border-radius:4px;overflow:hidden;'; const bar = document.createElement('div'); bar.className = 'xp-gp-axis'; bar.dataset.index = i; bar.style.cssText = 'height:100%;width:50%;background:#4caf50;transform-origin:left center;'; barWrap.appendChild(bar); row.appendChild(label); row.appendChild(barWrap); axesEl.appendChild(row); } } const axisBars = axesEl.querySelectorAll('.xp-gp-axis'); for (let i = 0; i < gp.axes.length && i < axisBars.length; i++) { const v = gp.axes[i]; // -1..1 // Map [-1,1] to [0,100] and center at 50% const pct = (v + 1) / 2; axisBars[i].style.transform = 'scaleX(' + Math.abs(v) + ')'; axisBars[i].style.marginLeft = (pct * 100 - 50) + '%'; } } function loop() { if (!running) return; const gp = getFirstGamepad(); if (!gp) { setStatus('No gamepad detected – press a button on your controller.'); requestAnimationFrame(loop); return; } if (gp.index !== lastIndex) { lastIndex = gp.index; deviceEl.textContent = 'Using: ' + (gp.id || 'Unknown device') + ' (index ' + gp.index + ')'; } setStatus('Reading input… (press buttons / move sticks)'); renderButtons(gp); renderAxes(gp); requestAnimationFrame(loop); } function start() { if (running) return; if (!('getGamepads' in navigator)) { setStatus('Gamepad API not supported in this browser.'); return; } running = true; setStatus('Looking for a connected gamepad…'); loop(); } startBtn.addEventListener('click', start); window.addEventListener('gamepadconnected', function (e) { deviceEl.textContent = 'Gamepad connected: ' + (e.gamepad.id || 'Unknown device') + ' (index ' + e.gamepad.index + ')'; if (!running) setStatus('Gamepad connected – click "Start Tester".'); }); window.addEventListener('gamepaddisconnected', function () { deviceEl.textContent = 'Gamepad disconnected.'; setStatus('Controller disconnected. Reconnect and click "Start Tester".'); }); })(); </script>