Skip to content

Commit 63274c7

Browse files
Deploy site from 1b3ebcc
1 parent ed1a8eb commit 63274c7

48,244 files changed

Lines changed: 29528 additions & 48018 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

404.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@
197197
<button class="nav-link dropdown-toggle" id="navbarDropdown4" type="button" data-bs-toggle="dropdown" aria-expanded="false">Tools</button>
198198
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown4">
199199
<a class="dropdown-item" href="/gamepad-tester">Gamepad Tester</a>
200+
<a class="dropdown-item" href="/mouse-tester">Mouse Tester</a>
200201
</div>
201202
</li>
202203

assets/css/mouse-tester.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.intro-header {
2+
margin-bottom: 0;
3+
}
4+
5+
@media (min-width: 1200px) {
6+
.intro-header {
7+
margin-top: 5.6875rem;
8+
}
9+
}

assets/css/styles.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

assets/js/mouse-tester.js

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
function formatSigned(value) {
2+
const roundedValue = Math.round(value);
3+
return roundedValue > 0 ? `+${roundedValue}` : `${roundedValue}`;
4+
}
5+
6+
function wheelUnit(deltaMode) {
7+
if (deltaMode === WheelEvent.DOM_DELTA_LINE) return 'lines';
8+
if (deltaMode === WheelEvent.DOM_DELTA_PAGE) return 'pages';
9+
return 'px';
10+
}
11+
12+
function wheelModeName(deltaMode) {
13+
if (deltaMode === WheelEvent.DOM_DELTA_LINE) return 'Lines';
14+
if (deltaMode === WheelEvent.DOM_DELTA_PAGE) return 'Pages';
15+
return 'Pixels';
16+
}
17+
18+
function wheelDirection(deltaX, deltaY) {
19+
if (Math.abs(deltaY) >= Math.abs(deltaX) && deltaY !== 0) {
20+
return deltaY < 0 ? '↑ Up' : '↓ Down';
21+
}
22+
if (deltaX !== 0) {
23+
return deltaX < 0 ? '← Left' : '→ Right';
24+
}
25+
return 'Idle';
26+
}
27+
28+
function preventBrowserAction(event) {
29+
event.preventDefault();
30+
}
31+
32+
document.addEventListener('DOMContentLoaded', function() {
33+
const buttonConfigs = [
34+
{ number: 0, bit: 1, name: 'primary' },
35+
{ number: 1, bit: 4, name: 'wheel' },
36+
{ number: 2, bit: 2, name: 'secondary' },
37+
{ number: 3, bit: 8, name: 'back' },
38+
{ number: 4, bit: 16, name: 'forward' }
39+
];
40+
const browserNavigationButtons = new Set([3, 4]);
41+
const buttonPressCounts = new Map(buttonConfigs.map(config => [config.number, 0]));
42+
const pressedButtons = new Set();
43+
const suppressedNavigationButtons = new Set();
44+
const captureSurface = document.getElementById('mouse-capture-surface');
45+
const positionIndicator = document.getElementById('mouse-position-indicator');
46+
const status = document.getElementById('mouse-status');
47+
const statusMessage = document.getElementById('mouse-status-message');
48+
const activeButtonsOutput = document.getElementById('mouse-active-buttons');
49+
const lastEventOutput = document.getElementById('mouse-last-event');
50+
const wheelModeOutput = document.getElementById('mouse-wheel-mode');
51+
const browserSupportOutput = document.getElementById('mouse-browser-support');
52+
const firefoxMouseButtonsWarning = document.getElementById('firefox-mouse-buttons-warning');
53+
const movementXOutput = document.getElementById('mouse-movement-x');
54+
const movementYOutput = document.getElementById('mouse-movement-y');
55+
const positionOutput = document.getElementById('mouse-position');
56+
const distanceOutput = document.getElementById('mouse-distance');
57+
const moveEventsOutput = document.getElementById('mouse-move-events');
58+
const doubleClicksOutput = document.getElementById('mouse-double-clicks');
59+
const wheelXOutput = document.getElementById('mouse-wheel-x');
60+
const wheelYOutput = document.getElementById('mouse-wheel-y');
61+
const wheelDirectionOutput = document.getElementById('mouse-wheel-direction');
62+
const wheelEventsOutput = document.getElementById('mouse-wheel-events');
63+
let hasReceivedInput = false;
64+
let scrollArrowTimer = null;
65+
let motionFrameId = null;
66+
let movementX = 0;
67+
let movementY = 0;
68+
let positionX = 0;
69+
let positionY = 0;
70+
let totalDistance = 0;
71+
let moveEvents = 0;
72+
let doubleClicks = 0;
73+
let wheelX = 0;
74+
let wheelY = 0;
75+
let wheelEvents = 0;
76+
let wheelDeltaMode = 0;
77+
78+
firefoxMouseButtonsWarning.hidden = !navigator.userAgent.includes('Firefox/');
79+
80+
function updateStatus(message, type) {
81+
status.classList.remove('alert-warning', 'alert-success', 'alert-danger');
82+
status.classList.add(`alert-${type}`);
83+
statusMessage.textContent = message;
84+
}
85+
86+
function markInput(eventName) {
87+
lastEventOutput.textContent = eventName;
88+
if (!hasReceivedInput) {
89+
hasReceivedInput = true;
90+
updateStatus('Mouse input detected. Keep testing buttons, movement, and scrolling.', 'success');
91+
}
92+
}
93+
94+
function getButtonElements(buttonNumber) {
95+
return {
96+
card: document.querySelector(`.mouse-button-card[data-mouse-button="${buttonNumber}"]`),
97+
count: document.getElementById(`mouse-button-${buttonNumber}-count`),
98+
state: document.getElementById(`mouse-button-${buttonNumber}-state`),
99+
visual: document.querySelector(`.mouse-svg-control[data-mouse-button="${buttonNumber}"]`)
100+
};
101+
}
102+
103+
function renderButton(buttonNumber) {
104+
const elements = getButtonElements(buttonNumber);
105+
const isActive = pressedButtons.has(buttonNumber);
106+
elements.card.classList.toggle('is-active', isActive);
107+
elements.visual.classList.toggle('is-active', isActive);
108+
elements.state.textContent = isActive ? 'Pressed' : 'Released';
109+
elements.count.textContent = buttonPressCounts.get(buttonNumber);
110+
}
111+
112+
function renderAllButtons() {
113+
buttonConfigs.forEach(config => renderButton(config.number));
114+
}
115+
116+
function renderActiveButtons(buttonsBitmask) {
117+
const activeNames = buttonConfigs
118+
.filter(config => (buttonsBitmask & config.bit) !== 0)
119+
.map(config => config.name);
120+
activeButtonsOutput.textContent = activeNames.length === 0
121+
? '0 (none)'
122+
: `${buttonsBitmask} (${activeNames.join(' + ')})`;
123+
}
124+
125+
function syncPressedButtons(buttonsBitmask) {
126+
pressedButtons.clear();
127+
buttonConfigs.forEach(config => {
128+
if ((buttonsBitmask & config.bit) !== 0) {
129+
pressedButtons.add(config.number);
130+
}
131+
});
132+
renderActiveButtons(buttonsBitmask);
133+
renderAllButtons();
134+
}
135+
136+
function renderMotion() {
137+
movementXOutput.textContent = formatSigned(movementX);
138+
movementYOutput.textContent = formatSigned(movementY);
139+
positionOutput.textContent = `${Math.round(positionX)}, ${Math.round(positionY)}`;
140+
distanceOutput.textContent = Math.round(totalDistance);
141+
moveEventsOutput.textContent = moveEvents;
142+
positionIndicator.style.left = `${positionX}px`;
143+
positionIndicator.style.top = `${positionY}px`;
144+
positionIndicator.hidden = false;
145+
motionFrameId = null;
146+
}
147+
148+
function scheduleMotionRender() {
149+
if (motionFrameId === null) {
150+
motionFrameId = requestAnimationFrame(renderMotion);
151+
}
152+
}
153+
154+
function showScrollArrows(deltaX, deltaY) {
155+
if (scrollArrowTimer !== null) {
156+
clearTimeout(scrollArrowTimer);
157+
}
158+
const activeDirections = new Set();
159+
if (deltaX < 0) activeDirections.add('left');
160+
if (deltaX > 0) activeDirections.add('right');
161+
if (deltaY < 0) activeDirections.add('up');
162+
if (deltaY > 0) activeDirections.add('down');
163+
document.querySelectorAll('.mouse-svg-scroll-indicator').forEach(indicator => {
164+
indicator.classList.toggle('is-active', activeDirections.has(indicator.dataset.scrollDirection));
165+
});
166+
scrollArrowTimer = setTimeout(function() {
167+
document.querySelectorAll('.mouse-svg-scroll-indicator').forEach(indicator => {
168+
indicator.classList.remove('is-active');
169+
});
170+
scrollArrowTimer = null;
171+
}, 450);
172+
}
173+
174+
function recordButtonDown(event) {
175+
const wasPressed = pressedButtons.has(event.button);
176+
captureSurface.focus({ preventScroll: true });
177+
markInput(`Button ${event.button} down`);
178+
syncPressedButtons(event.buttons);
179+
if (!wasPressed && buttonPressCounts.has(event.button)) {
180+
buttonPressCounts.set(event.button, buttonPressCounts.get(event.button) + 1);
181+
renderButton(event.button);
182+
}
183+
}
184+
185+
function handleMouseDown(event) {
186+
event.preventDefault();
187+
recordButtonDown(event);
188+
}
189+
190+
function handleSideButtonPointerDown(event) {
191+
if (!browserNavigationButtons.has(event.button)) return;
192+
event.preventDefault();
193+
suppressedNavigationButtons.add(event.button);
194+
recordButtonDown(event);
195+
}
196+
197+
function handleSideButtonPointerUp(event) {
198+
if (!browserNavigationButtons.has(event.button) || !suppressedNavigationButtons.has(event.button)) return;
199+
event.preventDefault();
200+
syncPressedButtons(event.buttons);
201+
markInput(`Button ${event.button} up`);
202+
suppressedNavigationButtons.delete(event.button);
203+
}
204+
205+
function handleMouseMove(event) {
206+
const surfaceBounds = captureSurface.getBoundingClientRect();
207+
movementX = event.movementX;
208+
movementY = event.movementY;
209+
positionX = Math.min(Math.max(event.clientX - surfaceBounds.left, 0), surfaceBounds.width);
210+
positionY = Math.min(Math.max(event.clientY - surfaceBounds.top, 0), surfaceBounds.height);
211+
totalDistance += Math.hypot(movementX, movementY);
212+
moveEvents += 1;
213+
markInput('Mouse move');
214+
syncPressedButtons(event.buttons);
215+
scheduleMotionRender();
216+
}
217+
218+
function handleWheel(event) {
219+
event.preventDefault();
220+
wheelDeltaMode = event.deltaMode;
221+
wheelX += event.deltaX;
222+
wheelY += event.deltaY;
223+
wheelEvents += 1;
224+
const unit = wheelUnit(wheelDeltaMode);
225+
wheelXOutput.textContent = `${formatSigned(wheelX)} ${unit}`;
226+
wheelYOutput.textContent = `${formatSigned(wheelY)} ${unit}`;
227+
wheelDirectionOutput.textContent = wheelDirection(event.deltaX, event.deltaY);
228+
wheelEventsOutput.textContent = wheelEvents;
229+
wheelModeOutput.textContent = wheelModeName(wheelDeltaMode);
230+
markInput('Mouse wheel');
231+
showScrollArrows(event.deltaX, event.deltaY);
232+
}
233+
234+
function releaseButtons(event) {
235+
syncPressedButtons(event?.buttons ?? 0);
236+
if (event && captureSurface.contains(event.target)) {
237+
event.preventDefault();
238+
markInput(`Button ${event.button} up`);
239+
}
240+
}
241+
242+
function preventSideButtonBrowserAction(event) {
243+
if (browserNavigationButtons.has(event.button)) {
244+
event.preventDefault();
245+
}
246+
}
247+
248+
function releaseAllButtons() {
249+
suppressedNavigationButtons.clear();
250+
syncPressedButtons(0);
251+
}
252+
253+
function resetTester() {
254+
hasReceivedInput = false;
255+
buttonConfigs.forEach(config => buttonPressCounts.set(config.number, 0));
256+
pressedButtons.clear();
257+
suppressedNavigationButtons.clear();
258+
if (scrollArrowTimer !== null) {
259+
clearTimeout(scrollArrowTimer);
260+
scrollArrowTimer = null;
261+
}
262+
document.querySelectorAll('.mouse-svg-scroll-indicator').forEach(indicator => {
263+
indicator.classList.remove('is-active');
264+
});
265+
if (motionFrameId !== null) {
266+
cancelAnimationFrame(motionFrameId);
267+
motionFrameId = null;
268+
}
269+
movementX = 0;
270+
movementY = 0;
271+
positionX = 0;
272+
positionY = 0;
273+
totalDistance = 0;
274+
moveEvents = 0;
275+
doubleClicks = 0;
276+
wheelX = 0;
277+
wheelY = 0;
278+
wheelEvents = 0;
279+
wheelDeltaMode = 0;
280+
movementXOutput.textContent = '0';
281+
movementYOutput.textContent = '0';
282+
positionOutput.textContent = '0, 0';
283+
distanceOutput.textContent = '0';
284+
moveEventsOutput.textContent = '0';
285+
doubleClicksOutput.textContent = '0';
286+
wheelXOutput.textContent = '0 px';
287+
wheelYOutput.textContent = '0 px';
288+
wheelDirectionOutput.textContent = 'Idle';
289+
wheelEventsOutput.textContent = '0';
290+
wheelModeOutput.textContent = 'Pixels';
291+
lastEventOutput.textContent = 'None';
292+
positionIndicator.hidden = true;
293+
renderActiveButtons(0);
294+
renderAllButtons();
295+
updateStatus('Move, click, or scroll inside the test area to begin.', 'warning');
296+
}
297+
298+
if (globalThis.MouseEvent === undefined || globalThis.WheelEvent === undefined) {
299+
browserSupportOutput.textContent = 'Not supported';
300+
updateStatus('Mouse events are not supported in this browser.', 'danger');
301+
captureSurface.removeAttribute('tabindex');
302+
return;
303+
}
304+
305+
browserSupportOutput.textContent = 'Supported';
306+
captureSurface.addEventListener('pointerdown', handleSideButtonPointerDown, { capture: true });
307+
captureSurface.addEventListener('mousedown', handleMouseDown);
308+
captureSurface.addEventListener('mousemove', handleMouseMove);
309+
captureSurface.addEventListener('mouseup', preventBrowserAction);
310+
captureSurface.addEventListener('wheel', handleWheel, { passive: false });
311+
captureSurface.addEventListener('contextmenu', preventBrowserAction);
312+
captureSurface.addEventListener('click', preventSideButtonBrowserAction, { capture: true });
313+
captureSurface.addEventListener('auxclick', preventSideButtonBrowserAction, { capture: true });
314+
captureSurface.addEventListener('dblclick', function(event) {
315+
event.preventDefault();
316+
doubleClicks += 1;
317+
doubleClicksOutput.textContent = doubleClicks;
318+
markInput('Double click');
319+
});
320+
globalThis.addEventListener('pointerup', handleSideButtonPointerUp, { capture: true });
321+
globalThis.addEventListener('pointercancel', releaseAllButtons, { capture: true });
322+
globalThis.addEventListener('mouseup', releaseButtons);
323+
globalThis.addEventListener('blur', releaseAllButtons);
324+
document.addEventListener('visibilitychange', function() {
325+
if (document.hidden) {
326+
releaseAllButtons();
327+
}
328+
});
329+
document.getElementById('mouse-reset').addEventListener('click', resetTester);
330+
resetTester();
331+
});

browse/characters/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@
304304
<button class="nav-link dropdown-toggle" id="navbarDropdown4" type="button" data-bs-toggle="dropdown" aria-expanded="false">Tools</button>
305305
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown4">
306306
<a class="dropdown-item" href="/gamepad-tester">Gamepad Tester</a>
307+
<a class="dropdown-item" href="/mouse-tester">Mouse Tester</a>
307308
</div>
308309
</li>
309310

browse/collections/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@
277277
<button class="nav-link dropdown-toggle" id="navbarDropdown4" type="button" data-bs-toggle="dropdown" aria-expanded="false">Tools</button>
278278
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown4">
279279
<a class="dropdown-item" href="/gamepad-tester">Gamepad Tester</a>
280+
<a class="dropdown-item" href="/mouse-tester">Mouse Tester</a>
280281
</div>
281282
</li>
282283

browse/franchises/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@
277277
<button class="nav-link dropdown-toggle" id="navbarDropdown4" type="button" data-bs-toggle="dropdown" aria-expanded="false">Tools</button>
278278
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown4">
279279
<a class="dropdown-item" href="/gamepad-tester">Gamepad Tester</a>
280+
<a class="dropdown-item" href="/mouse-tester">Mouse Tester</a>
280281
</div>
281282
</li>
282283

browse/games/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@
420420
<button class="nav-link dropdown-toggle" id="navbarDropdown4" type="button" data-bs-toggle="dropdown" aria-expanded="false">Tools</button>
421421
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown4">
422422
<a class="dropdown-item" href="/gamepad-tester">Gamepad Tester</a>
423+
<a class="dropdown-item" href="/mouse-tester">Mouse Tester</a>
423424
</div>
424425
</li>
425426

browse/platforms/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@
364364
<button class="nav-link dropdown-toggle" id="navbarDropdown4" type="button" data-bs-toggle="dropdown" aria-expanded="false">Tools</button>
365365
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown4">
366366
<a class="dropdown-item" href="/gamepad-tester">Gamepad Tester</a>
367+
<a class="dropdown-item" href="/mouse-tester">Mouse Tester</a>
367368
</div>
368369
</li>
369370

buckets/10.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)