(() => { const container = document.getElementById('chord-container'); const rootFilters = document.getElementById('root-filters'); const qualityFilters = document.getElementById('quality-filters'); const roots = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']; let activeRoots = new Set(); let activeQualities = new Set(); let allChords = []; let maxFret = 4; let numStrings = 6; function initFilters() { roots.forEach(r => { const pill = document.createElement('button'); pill.className = 'filter-pill'; pill.textContent = r; pill.addEventListener('click', () => { if (activeRoots.has(r)) { activeRoots.delete(r); pill.classList.remove('active'); } else { activeRoots.add(r); pill.classList.add('active'); } applyFilters(); }); rootFilters.appendChild(pill); }); if (window.go && window.go.main && window.go.main.App) { window.go.main.App.GetChordDefinitions().then(defs => { const qualities = new Set(); for (const cat of Object.keys(defs)) { for (const q of Object.keys(defs[cat])) { qualities.add(q); } } Array.from(qualities).sort().forEach(q => { const pill = document.createElement('button'); pill.className = 'filter-pill'; pill.textContent = q; pill.addEventListener('click', () => { if (activeQualities.has(q)) { activeQualities.delete(q); pill.classList.remove('active'); } else { activeQualities.add(q); pill.classList.add('active'); } applyFilters(); }); qualityFilters.appendChild(pill); }); }); } } function applyFilters() { const cards = container.querySelectorAll('.chord-card'); cards.forEach((card, i) => { const chord = allChords[i]; if (!chord) return; let show = true; if (activeRoots.size > 0 && !activeRoots.has(chord.root)) { show = false; } if (activeQualities.size > 0 && !activeQualities.has(chord.quality)) { show = false; } card.style.display = show ? '' : 'none'; }); } function buildChordCards(chords, mf, ns) { allChords = chords; maxFret = mf || maxFret; numStrings = ns || numStrings; container.innerHTML = ''; chords.forEach(match => { const card = document.createElement('div'); card.className = 'chord-card'; const h2 = document.createElement('h2'); h2.textContent = match.chord; card.appendChild(h2); const fb = document.createElement('div'); fb.className = 'fretboard'; fb.dataset.fingering = JSON.stringify(match.fingering); card.appendChild(fb); if (match.alternatives && match.alternatives.length > 0) { const altSection = document.createElement('div'); altSection.className = 'alternatives'; const h3 = document.createElement('h3'); h3.textContent = 'Alternatives:'; altSection.appendChild(h3); const altContainer = document.createElement('div'); altContainer.className = 'alternatives-container'; match.alternatives.forEach(alt => { const altFb = document.createElement('div'); altFb.className = 'fretboard alternative-fretboard'; altFb.dataset.fingering = JSON.stringify(alt); altContainer.appendChild(altFb); }); altSection.appendChild(altContainer); card.appendChild(altSection); } container.appendChild(card); }); renderFretboards(); applyFilters(); } function renderFretboards() { const fretboards = document.querySelectorAll('.fretboard'); fretboards.forEach(fb => { const fingering = JSON.parse(fb.dataset.fingering); const wrapper = document.createElement('div'); wrapper.className = 'fretboard'; fb.innerHTML = ''; fb.style.display = 'inline-block'; fb.style.marginBottom = '1rem'; const fretCounts = {}; fingering.forEach(f => { if (!isNaN(f)) fretCounts[f] = (fretCounts[f] || 0) + 1; }); const entries = Object.entries(fretCounts) .filter(([, count]) => count >= 2) .map(([f]) => parseInt(f)); let barreFretNum = null; for (const f of entries.sort((a, b) => a - b)) { if (fingering.every(x => x === 'x' || isNaN(x) || parseInt(x) >= f)) { barreFretNum = f; break; } } const fretMatrix = []; for (let s = 0; s < numStrings; s++) { const stringRow = []; for (let f = 1; f <= maxFret; f++) { const fret = document.createElement('div'); fret.className = 'fret'; fret.dataset.row = s; fret.dataset.col = f; const fretValue = fingering[s]; const numericFret = parseInt(fretValue, 10); if (fretValue === 'x' && f === 1) { fret.setAttribute('muted', ''); fret.textContent = 'x'; } else if (fretValue !== 'x' && numericFret === f) { fret.dataset.dot = 'true'; if (barreFretNum !== null && numericFret === barreFretNum) { fret.classList.add('barre'); } } stringRow.push(fret); } fretMatrix.push(stringRow); } const barreCols = []; if (barreFretNum !== null) { for (let s = 0; s < numStrings; s++) { if (parseInt(fingering[s]) === barreFretNum) barreCols.push(s); } } for (let s = numStrings - 1; s >= 0; s--) { const stringRow = document.createElement('div'); stringRow.className = 'fret-row'; for (let f = 1; f <= maxFret; f++) { stringRow.appendChild(fretMatrix[s][f - 1]); } wrapper.appendChild(stringRow); } fb.appendChild(wrapper); if (barreFretNum !== null && barreCols.length >= 2) { const start = Math.min(...barreCols); const end = Math.max(...barreCols); const line = document.createElement('div'); line.className = 'barre-line'; requestAnimationFrame(() => { let totalDotCenter = 0; let dotCount = 0; for (let s = start; s <= end; s++) { const dotFret = fretMatrix[s][barreFretNum - 1]; if (dotFret) { const rect = dotFret.getBoundingClientRect(); totalDotCenter += (rect.left + rect.right) / 2; dotCount++; } } const avgDotCenter = totalDotCenter / dotCount; const parentRect = wrapper.getBoundingClientRect(); const dotCenter = avgDotCenter - parentRect.left; const firstDot = fretMatrix[start][barreFretNum - 1]; const lastDot = fretMatrix[end][barreFretNum - 1]; const rect1 = firstDot.getBoundingClientRect(); const rect2 = lastDot.getBoundingClientRect(); const top = Math.min(rect1.top, rect2.top) - parentRect.top; const bottom = Math.max(rect1.bottom, rect2.bottom) - parentRect.top; const height = bottom - top; line.style.top = Math.round(top) + 'px'; line.style.height = Math.round(height) + 'px'; line.style.left = Math.round(dotCenter) + 'px'; line.textContent = '|'; wrapper.appendChild(line); }); } }); } container.addEventListener('click', (e) => { const fb = e.target.closest('[data-fingering]'); if (!fb || !window.playChord || !window.currentTuningMIDI) return; const fing = JSON.parse(fb.dataset.fingering); const midi = []; fing.forEach((f, i) => { if (f === 'x') return; midi.push(window.currentTuningMIDI[i] + parseInt(f)); }); if (midi.length) window.playChord(midi); }); window.buildChordCards = buildChordCards; initFilters(); })();