feat(v3): choose handedness in the instrument selector + onboarding (give lefties a break) (#793)
ship-ci / ci (push) Waiting to run

* feat(v3): add a handedness (left-handed) choice to the instrument selector + onboarding

Left-handed players could already mirror the highway, but only via a buried
Settings toggle they had to find AFTER setup -- so a lefty went through the tour,
the tuner and calibration all right-handed first (community callout).

Add a "Handedness: Right / Left" row to the v3 instrument badge popover, alongside
Instrument / Strings / Tuning (all player-orientation choices). It writes the same
lefty preference -- highway.setLefty when a live highway exists (flips it
immediately + persists), else the 'lefty' localStorage key the highway reads on
init -- and keeps the Settings "Left-handed" checkbox in sync. The first-run
tour's "Choose your instrument" step, which runs before the tuner/audio-
calibration steps, now calls it out so lefties flip it up front.

Frontend-only, additive. Full core JS suite green (938). Tests:
tests/js/badges_handedness.test.js.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UR2Cr7GEu3yMY7SrfxH6c1

* docs: split the spliced Handedness/Colorblind CHANGELOG entries

A rebase pasted the Handedness bullet over the Colorblind preset entry's bold
lead, merging two unrelated Added entries into one run-on bullet. Restore them
as two separate bullets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: byrongamatos <xasiklas@gmail.com>
This commit is contained in:
ChrisBeWithYou
2026-07-05 23:50:30 +02:00
committed by GitHub
co-authored by Claude Opus 4.8 byrongamatos
parent 4f6dc233f1
commit 612b1f2e0d
4 changed files with 75 additions and 1 deletions
+26
View File
@@ -451,6 +451,12 @@
pill('inst', v, v[0].toUpperCase() + v.slice(1), settings.instrument === v)).join('')) +
instRow('Strings', STRING_COUNTS[settings.instrument].map((v) =>
pill('strings', v, v + '', settings.string_count === v)).join('')) +
// Handedness — a left-hander flips the whole highway (frets mirror).
// Lives with the other player-orientation choices so it's part of the
// same "Choose your instrument" step the onboarding tour spotlights —
// i.e. set before you ever tune up or calibrate.
instRow('Handedness', pill('hand', 'right', 'Right', !_leftyPref()) +
pill('hand', 'left', 'Left', _leftyPref())) +
'<div><div class="text-[0.625rem] uppercase tracking-wider text-fb-textDim mb-1">Tuning</div>' +
'<select data-inst-tuning class="w-full bg-gray-800/50 border border-gray-700 rounded-md px-2 py-1.5 text-xs text-fb-text outline-none focus:border-fb-primary">' +
// An offset-array tuning has no named option — surface it as a
@@ -513,6 +519,10 @@
setWorkingInstrument(settings.instrument, newSc);
renderInstrument(); keepOpen();
}));
menu.querySelectorAll('[data-pill="hand"]').forEach((b) => b.addEventListener('click', () => {
_setLeftyPref(b.getAttribute('data-val') === 'left');
renderInstrument(); keepOpen(); // reflect the active pill; keep the menu open
}));
menu.querySelector('[data-inst-tuning]').addEventListener('change', (e) => saveSettings({ tuning: e.target.value }));
menu.querySelector('[data-inst-pathway]').addEventListener('change', (e) => saveSettings({ pathway: e.target.value }));
const ref = menu.querySelector('[data-inst-ref]');
@@ -528,6 +538,22 @@
function instRow(label, inner) {
return '<div><div class="text-[0.625rem] uppercase tracking-wider text-fb-textDim mb-1">' + label + '</div><div class="flex flex-wrap gap-1">' + inner + '</div></div>';
}
// Handedness (left-handed) preference. The canonical store is the highway's
// `lefty` localStorage key; when a live highway exists, setLefty() also flips
// it immediately. Feature-detected so it works on the dashboard before any
// highway has been created (the value is read on the highway's next init).
function _leftyPref() {
try { if (window.highway && typeof window.highway.getLefty === 'function') return !!window.highway.getLefty(); } catch (_) { /* */ }
try { return localStorage.getItem('lefty') === '1'; } catch (_) { return false; }
}
function _setLeftyPref(on) {
try {
if (window.highway && typeof window.highway.setLefty === 'function') window.highway.setLefty(!!on);
else localStorage.setItem('lefty', on ? '1' : '0');
} catch (_) { /* storage blocked — the pill still reflects the choice via re-render */ }
// Keep the Settings "Left-handed" checkbox in sync when it's mounted.
try { const cb = document.getElementById('setting-lefty'); if (cb) cb.checked = !!on; } catch (_) { /* */ }
}
function pill(group, val, label, active) {
return '<button type="button" data-pill="' + group + '" data-val="' + val + '" class="px-2 py-1 rounded-md text-xs ' +
(active ? 'bg-fb-primary text-white' : 'bg-gray-800/50 text-fb-textDim hover:text-fb-text') + '">' + esc(label) + '</button>';
+1 -1
View File
@@ -42,7 +42,7 @@
id: 'instrument', shape: 'spotlight', position: 'bottom',
selector: '#v3-instrument-wrap', waitFor: '#v3-instrument-wrap',
title: 'Choose your instrument',
content: 'Set your instrument, string count and tuning here. The highway, tuner and scoring all adapt to this selection.',
content: 'Set your instrument, string count and tuning here — and if you play left-handed, flip Handedness to Left so the whole highway mirrors. The highway, tuner and scoring all adapt to this selection.',
},
{
id: 'tuner', shape: 'spotlight', position: 'bottom',