const panel = document.querySelector('aside.layout'); const svg = document.getElementById('diagram'); const arrows = Array.from(svg.querySelectorAll('.arrow')); const preset = panel.querySelector('.control--preset select'); const controls = Array.from(panel.querySelectorAll('.control--axis')); const steps = Array.from(panel.querySelectorAll('.control--axis > input')); const dirs = Array.from(panel.querySelectorAll('.control--axis .dir input')); const turnCCW = document.getElementById('turn-ccw'); const turnCW = document.getElementById('turn-cw'); let state = [ 7, 2, null ]; let last = 0; let selected = null; let rot = 0; let targetRot = 30; const completeState = ([a, b, c]) => { if (a == null) { a = b - c; } else if (b == null) { b = c + a; } else { c = b - a; } return [a, b, c, -a, -b, -c]; }; const PRESETS = { 'wicki-hayden': [ 7, 2, null ], 'janko': [ 1, 2, null ], 'harmonic': [ 7, 4, null ], 'gerhard': [ 4, 3, null ], }; const updateValues = () => { const full = completeState(state); full.forEach((val, i) => { arrows[i].querySelector('text').textContent = val; if (i < steps.length) steps[i].value = Math.abs(val); if (i < dirs.length) { dirs[i].checked = val < 0; dirs[i].disabled = val == 0; } }); const presetName = Object.keys(PRESETS).find(k => completeState(PRESETS[k]).join(',') === full.join(',')); preset.value = presetName ?? 'custom'; updateFocus(); }; const updateFocus = () => { const full = completeState(state); controls.forEach((control, i) => { let className = 'control control--axis'; if (state[i] == null) className += ' driven'; else if (i == selected) className += ' selected'; if (state[i] == 0) className += ' dir-disabled'; control.className = className; }); full.forEach((val, i) => { const positive = val > 0 || (val == 0 && i < 3); let className = 'arrow'; if (state[i%3] == null) className += ' driven'; if (!positive) className += ' negative'; if (i%3 == selected) className += ' selected'; arrows[i].className.baseVal = className; }); }; const select = (i) => { if (state[i] != null) return false; const newState = [null, null, null]; newState[i] = completeState(state)[i]; newState[last] = state[last]; state = newState; last = i; return true; }; export const turn = (dir) => { targetRot += dir * 30; } arrows.forEach((arrow, i) => { arrow.onclick = () => steps[i%3].focus(); }); controls.forEach((control, i) => { control.addEventListener('focusin', () => { selected = i; select(i); updateFocus(); }); control.addEventListener('focusout', () => { selected = null; updateFocus(); }); }); steps.forEach((input, i) => { input.onchange = () => { select(i); state[i] = +input.value; if (dirs[i].checked) state[i] = -state[i]; updateValues(); }; }); dirs.forEach((input, i) => { input.onchange = () => { select(i); state[i] = state[i] * -1; updateValues(); }; }); preset.onchange = () => { const nextState = PRESETS[preset.value]; if (nextState) { state = nextState.slice(); last = state.findIndex(s => s != null); updateValues(); } }; preset.value = 'wicki-hayden'; preset.onchange(); turnCCW.onclick = () => turn(-1); turnCW.onclick = () => turn(1); export const update = () => { const delta = targetRot - rot; if (Math.abs(delta) < 1) rot = targetRot; else rot += delta * 0.1; if (rot < 60 && rot > -60) return; const full = completeState(state); let driven = state.indexOf(null); // cw rotation while (rot >= 60) { rot -= 60; targetRot -= 60; full.unshift(full.pop()); driven++; } // cw rotation while (rot <= -60) { rot += 60; targetRot += 60; full.push(full.shift()); driven = (driven-1+3) % 3; } state = full.slice(0, 3); state[driven%3] = null; updateValues(); }; export const getSteps = () => completeState(state); export const getRot = () => rot / 180 * Math.PI;