← banjotuna

Extract CSS and JS into separate files

ddb8faf28774c1091f8c21c017088e9f0dd43872 · Claude · Feb 6, 2026 16:05

Split the monolithic index.html (3,363 lines) into modular files:

CSS (css/):
- styles.css: 1,112 lines of styling

JavaScript (js/):
- utils.js: Chromatic scale constants and note utility functions
- tunings.js: TUNINGS_DATA for all 24 banjo tunings
- chords.js: Chord data, voicing algorithm, diagram generation
- fretboard.js: Note formatting, inversions, fretboard SVG generator
- app.js: State management, UI event handlers, initialization

index.html reduced from 3,363 to 343 lines (HTML structure only)

Still works with file:// protocol - no web server required.

https://claude.ai/code/session_01VSHtwaMJKL1ZgH33JDcwbY

+3037 −3027 · 7 files changed

  • css/styles.css +1112 −0
  • index.html +7 −3027
  • js/app.js +528 −0
  • js/chords.js +386 −0
  • js/fretboard.js +633 −0
  • js/tunings.js +306 −0
  • js/utils.js +65 −0
diff --git a/css/styles.css b/css/styles.css
new file mode 100644
index 0000000..c816d86
--- /dev/null
+++ b/css/styles.css
@@ -0,0 +1,1112 @@
+/* ============================================
+   CSS VARIABLES & BASE STYLES
+   ============================================ */
+:root {
+    --c-family: #C74242;
+    --c-family-light: #F8E3E3;
+    --d-family: #2D7A4F;
+    --d-family-light: #E3F5E9;
+    --g-family: #2E5C8A;
+    --g-family-light: #E3EEF8;
+    --other-family: #7B5BA6;
+    --other-family-light: #F0E8F5;
+    --text-primary: #1a1a1a;
+    --text-secondary: #444;
+    --text-muted: #666;
+    --bg-dark: #1a1a1a;
+    --bg-light: #f5f5f5;
+    --border-color: #ddd;
+}
+
+* {
+    box-sizing: border-box;
+}
+
+body {
+    font-family: 'Helvetica Neue', Arial, sans-serif;
+    margin: 0;
+    padding: 0;
+    background: #f0f0f0;
+    color: var(--text-primary);
+    line-height: 1.5;
+}
+
+/* ============================================
+   PRINT STYLES
+   ============================================ */
+@page {
+    size: A4 portrait;
+    margin: 12mm;
+}
+
+/* Landscape orientation for chord grid view */
+@page chord-grid-landscape {
+    size: A4 landscape;
+    margin: 10mm;
+}
+
+@media print {
+    body {
+        background: white;
+    }
+
+    .no-print {
+        display: none !important;
+    }
+
+    .print-only {
+        display: block !important;
+    }
+
+    .page-break {
+        page-break-after: always;
+    }
+
+    .page-break-before {
+        page-break-before: always;
+    }
+
+    .tuning-card {
+        break-inside: avoid;
+    }
+
+    .app-container {
+        max-width: 100%;
+        padding: 0;
+    }
+
+    .tunings-grid {
+        grid-template-columns: 1fr !important;
+    }
+
+    /* Chord grid landscape printing */
+    .chord-keys-grid-container {
+        page: chord-grid-landscape;
+    }
+
+    /* Table header repetition on each page */
+    .chord-keys-grid thead {
+        display: table-header-group;
+    }
+
+    .chord-keys-grid tbody {
+        display: table-row-group;
+    }
+
+    /* Force page break before F#/Gb row */
+    .chord-keys-grid tr.page-break-before {
+        page-break-before: always;
+    }
+
+    /* Keep key rows together */
+    .chord-keys-grid tr {
+        page-break-inside: avoid;
+    }
+}
+
+.print-only {
+    display: none;
+}
+
+/* ============================================
+   LAYOUT
+   ============================================ */
+.app-container {
+    max-width: 1400px;
+    margin: 0 auto;
+    padding: 20px;
+}
+
+/* ============================================
+   HEADER
+   ============================================ */
+.app-header {
+    text-align: center;
+    border: 3px solid #000;
+    padding: 20px;
+    margin-bottom: 20px;
+    background: var(--bg-light);
+    border-radius: 8px;
+}
+
+.app-header h1 {
+    margin: 0 0 10px 0;
+    font-size: 28px;
+    font-weight: bold;
+    letter-spacing: 0.5px;
+}
+
+.app-header .subtitle {
+    font-size: 14px;
+    color: var(--text-secondary);
+    margin: 5px 0;
+}
+
+/* ============================================
+   CONTROL PANEL
+   ============================================ */
+.control-panel {
+    background: var(--bg-dark);
+    color: white;
+    padding: 20px;
+    border-radius: 8px;
+    margin-bottom: 20px;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 20px;
+    align-items: center;
+    justify-content: space-between;
+}
+
+.control-group {
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+}
+
+.control-group label {
+    font-size: 11px;
+    text-transform: uppercase;
+    letter-spacing: 1px;
+    color: #aaa;
+}
+
+.control-group select,
+.control-group input[type="number"] {
+    padding: 8px 12px;
+    border: 2px solid #444;
+    border-radius: 4px;
+    background: #333;
+    color: white;
+    font-size: 14px;
+    min-width: 150px;
+}
+
+.control-group select:focus,
+.control-group input:focus {
+    outline: none;
+    border-color: var(--g-family);
+}
+
+/* Toggle Switch */
+.toggle-container {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+}
+
+.toggle-switch {
+    position: relative;
+    width: 60px;
+    height: 30px;
+}
+
+.toggle-switch input {
+    opacity: 0;
+    width: 0;
+    height: 0;
+}
+
+.toggle-slider {
+    position: absolute;
+    cursor: pointer;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background-color: #444;
+    transition: 0.3s;
+    border-radius: 30px;
+}
+
+.toggle-slider:before {
+    position: absolute;
+    content: "";
+    height: 22px;
+    width: 22px;
+    left: 4px;
+    bottom: 4px;
+    background-color: white;
+    transition: 0.3s;
+    border-radius: 50%;
+}
+
+.toggle-switch input:checked + .toggle-slider {
+    background-color: var(--g-family);
+}
+
+.toggle-switch input:checked + .toggle-slider:before {
+    transform: translateX(30px);
+}
+
+.toggle-label {
+    font-size: 14px;
+    font-weight: bold;
+    min-width: 50px;
+}
+
+/* Checkbox toggle for notes */
+.checkbox-container {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+}
+
+.checkbox-container input[type="checkbox"] {
+    width: 18px;
+    height: 18px;
+    cursor: pointer;
+}
+
+.checkbox-container span {
+    font-size: 14px;
+}
+
+/* Buttons */
+.btn {
+    padding: 10px 20px;
+    border: none;
+    border-radius: 4px;
+    font-size: 14px;
+    font-weight: bold;
+    cursor: pointer;
+    transition: all 0.2s;
+}
+
+.btn-primary {
+    background: var(--g-family);
+    color: white;
+}
+
+.btn-primary:hover {
+    background: #1e4a6e;
+}
+
+.btn-secondary {
+    background: #444;
+    color: white;
+}
+
+.btn-secondary:hover {
+    background: #555;
+}
+
+/* ============================================
+   PAGE TITLE
+   ============================================ */
+.page-title {
+    font-size: 18px;
+    font-weight: bold;
+    text-align: center;
+    padding: 12px;
+    background: #333;
+    color: white;
+    margin-bottom: 20px;
+    border-radius: 4px;
+    text-transform: uppercase;
+    letter-spacing: 1px;
+}
+
+/* ============================================
+   FAMILY INTRO
+   ============================================ */
+.family-intro {
+    background: #f0f0f0;
+    border-left: 5px solid;
+    padding: 15px;
+    margin-bottom: 15px;
+    font-size: 14px;
+    line-height: 1.6;
+    border-radius: 0 4px 4px 0;
+}
+
+.family-intro.c-family-intro { border-color: var(--c-family); }
+.family-intro.d-family-intro { border-color: var(--d-family); }
+.family-intro.g-family-intro { border-color: var(--g-family); }
+.family-intro.other-family-intro { border-color: var(--other-family); }
+
+/* ============================================
+   TUNINGS GRID
+   ============================================ */
+.tunings-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
+    gap: 20px;
+    margin-bottom: 20px;
+}
+
+/* ============================================
+   TUNING CARD
+   ============================================ */
+.tuning-card {
+    border: 3px solid;
+    border-radius: 8px;
+    padding: 16px;
+    background: white;
+    box-shadow: 0 3px 6px rgba(0,0,0,0.15);
+    transition: transform 0.2s, box-shadow 0.2s;
+}
+
+.tuning-card:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 5px 15px rgba(0,0,0,0.2);
+}
+
+.tuning-card.c-family {
+    border-color: var(--c-family);
+    background: linear-gradient(to bottom, var(--c-family-light) 0%, #FEF8F8 100%);
+}
+
+.tuning-card.d-family {
+    border-color: var(--d-family);
+    background: linear-gradient(to bottom, var(--d-family-light) 0%, #F8FEF9 100%);
+}
+
+.tuning-card.g-family {
+    border-color: var(--g-family);
+    background: linear-gradient(to bottom, var(--g-family-light) 0%, #F8FBFE 100%);
+}
+
+.tuning-card.other-family {
+    border-color: var(--other-family);
+    background: linear-gradient(to bottom, var(--other-family-light) 0%, #FDFBFE 100%);
+}
+
+.tuning-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: baseline;
+    margin-bottom: 8px;
+    border-bottom: 3px solid;
+    padding-bottom: 8px;
+}
+
+.c-family .tuning-header { border-color: var(--c-family); }
+.d-family .tuning-header { border-color: var(--d-family); }
+.g-family .tuning-header { border-color: var(--g-family); }
+.other-family .tuning-header { border-color: var(--other-family); }
+
+.tuning-name {
+    font-size: 16px;
+    font-weight: bold;
+    text-transform: uppercase;
+}
+
+.genre-tag {
+    font-size: 12px;
+    font-style: italic;
+    color: var(--text-muted);
+}
+
+.string-count-badge {
+    display: inline-block;
+    background: #666;
+    color: white;
+    font-size: 10px;
+    padding: 2px 6px;
+    border-radius: 3px;
+    margin-left: 8px;
+}
+
+.note-sequence {
+    font-family: 'Courier New', Consolas, monospace;
+    font-size: 20px;
+    font-weight: bold;
+    letter-spacing: 4px;
+    margin: 12px 0;
+    text-align: center;
+    background: white;
+    padding: 12px 8px;
+    border-radius: 4px;
+    border: 2px solid var(--border-color);
+}
+
+.note-sequence sub {
+    font-size: 0.6em;
+    vertical-align: sub;
+}
+
+/* Nut Visual */
+.nut-visual {
+    display: flex;
+    justify-content: center;
+    gap: 12px;
+    margin: 12px 0;
+}
+
+.string-circle {
+    width: 28px;
+    height: 28px;
+    border: 3px solid #333;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 11px;
+    font-weight: bold;
+    background: white;
+}
+
+.string-circle.changed {
+    background: #333;
+    color: white;
+}
+
+/* Tuning Details */
+.tuning-details {
+    font-size: 13px;
+    line-height: 1.7;
+    color: #333;
+    margin-top: 10px;
+}
+
+.tuning-details div {
+    margin: 5px 0;
+}
+
+.delta-line {
+    font-weight: bold;
+}
+
+.capo-line {
+    font-weight: bold;
+    color: #0066cc;
+}
+
+.songs-line {
+    font-style: italic;
+    color: #555;
+}
+
+/* Effective Tuning (with capo) */
+.effective-tuning {
+    background: #fff8e1;
+    border: 2px solid #ff9800;
+    padding: 8px;
+    border-radius: 4px;
+    margin-top: 10px;
+    font-size: 12px;
+}
+
+.effective-tuning strong {
+    color: #e65100;
+}
+
+/* ============================================
+   FRETBOARD DIAGRAM
+   ============================================ */
+.fretboard-container {
+    margin: 15px 0;
+    overflow-x: auto;
+}
+
+.fretboard-svg {
+    display: block;
+    margin: 0 auto;
+}
+
+/* ============================================
+   FRETBOARD PAGE LAYOUT (Side-by-side)
+   ============================================ */
+.fretboard-layout {
+    display: flex;
+    gap: 20px;
+    align-items: flex-start;
+}
+
+.tuning-selector {
+    width: 280px;
+    flex-shrink: 0;
+    background: white;
+    border: 2px solid var(--border-color);
+    border-radius: 8px;
+    overflow: hidden;
+}
+
+.tuning-selector-header {
+    background: var(--bg-dark);
+    color: white;
+    padding: 12px 15px;
+    font-weight: bold;
+    font-size: 14px;
+}
+
+.tuning-selector-filter {
+    padding: 10px;
+    border-bottom: 1px solid var(--border-color);
+    background: #f9f9f9;
+}
+
+.tuning-selector-filter select {
+    width: 100%;
+    padding: 8px;
+    border: 1px solid var(--border-color);
+    border-radius: 4px;
+    font-size: 13px;
+}
+
+.tuning-list {
+    max-height: 500px;
+    overflow-y: auto;
+}
+
+.tuning-list-item {
+    padding: 10px 15px;
+    cursor: pointer;
+    border-bottom: 1px solid #eee;
+    transition: background 0.15s;
+    font-size: 13px;
+}
+
+.tuning-list-item:hover {
+    background: #f5f5f5;
+}
+
+.tuning-list-item.selected {
+    background: var(--g-family-light);
+    border-left: 4px solid var(--g-family);
+    font-weight: bold;
+}
+
+.tuning-list-item.c-family.selected {
+    background: var(--c-family-light);
+    border-left-color: var(--c-family);
+}
+
+.tuning-list-item.d-family.selected {
+    background: var(--d-family-light);
+    border-left-color: var(--d-family);
+}
+
+.tuning-list-item.other-family.selected {
+    background: var(--other-family-light);
+    border-left-color: var(--other-family);
+}
+
+.tuning-list-item .tuning-item-name {
+    display: block;
+    font-weight: 600;
+}
+
+.tuning-list-item .tuning-item-notes {
+    display: block;
+    font-size: 11px;
+    color: var(--text-muted);
+    font-family: 'Courier New', monospace;
+    margin-top: 2px;
+}
+
+.tuning-list-group {
+    background: #f0f0f0;
+    padding: 8px 15px;
+    font-size: 11px;
+    font-weight: bold;
+    text-transform: uppercase;
+    letter-spacing: 0.5px;
+    color: var(--text-secondary);
+    border-bottom: 1px solid var(--border-color);
+}
+
+.fretboard-display {
+    flex: 1;
+    min-width: 0;
+}
+
+@media print {
+    .tuning-selector {
+        display: none !important;
+    }
+
+    .fretboard-layout {
+        display: block;
+    }
+
+    .fretboard-display {
+        width: 100%;
+    }
+}
+
+@media (max-width: 900px) {
+    .fretboard-layout {
+        flex-direction: column;
+    }
+
+    .tuning-selector {
+        width: 100%;
+    }
+
+    .tuning-list {
+        max-height: 200px;
+    }
+}
+
+/* ============================================
+   REFERENCE BOX
+   ============================================ */
+.reference-box {
+    background: var(--bg-dark);
+    color: white;
+    padding: 15px;
+    border-radius: 4px;
+    font-size: 13px;
+    line-height: 1.8;
+    margin-bottom: 20px;
+}
+
+.reference-box strong {
+    font-size: 14px;
+    display: block;
+    margin-bottom: 8px;
+}
+
+/* ============================================
+   REFERENCE SECTIONS
+   ============================================ */
+.reference-section {
+    background: white;
+    border: 2px solid var(--border-color);
+    padding: 20px;
+    border-radius: 8px;
+    margin-bottom: 20px;
+}
+
+.reference-section h3 {
+    margin: 0 0 15px 0;
+    padding-bottom: 10px;
+    border-bottom: 2px solid var(--border-color);
+}
+
+.fret-reference-box {
+    padding: 15px;
+    margin-bottom: 15px;
+    border-radius: 6px;
+}
+
+.fret-reference-box.blue {
+    background: var(--g-family-light);
+    border: 3px solid var(--g-family);
+}
+
+.fret-reference-box.red {
+    background: var(--c-family-light);
+    border: 3px solid var(--c-family);
+}
+
+.fret-reference-box.green {
+    background: var(--d-family-light);
+    border: 3px solid var(--d-family);
+}
+
+.fret-reference-box h4 {
+    margin: 0 0 10px 0;
+    font-size: 14px;
+}
+
+.fret-reference-box ul {
+    margin: 0;
+    padding-left: 20px;
+    line-height: 1.8;
+}
+
+/* ============================================
+   WARNING BOX
+   ============================================ */
+.warning-box {
+    background: #fff8e1;
+    border: 3px solid #ff9800;
+    padding: 15px;
+    margin: 15px 0;
+    border-radius: 6px;
+}
+
+.warning-box h4 {
+    margin: 0 0 8px 0;
+    color: #e65100;
+}
+
+/* ============================================
+   LEGEND
+   ============================================ */
+.legend {
+    font-size: 12px;
+    color: var(--text-muted);
+    text-align: center;
+    margin-top: 20px;
+    padding-top: 15px;
+    border-top: 2px solid var(--border-color);
+}
+
+/* ============================================
+   TABS
+   ============================================ */
+.tabs {
+    display: flex;
+    gap: 5px;
+    margin-bottom: 20px;
+    border-bottom: 3px solid var(--border-color);
+    padding-bottom: 0;
+}
+
+.tab {
+    padding: 12px 24px;
+    background: #e0e0e0;
+    border: none;
+    border-radius: 8px 8px 0 0;
+    cursor: pointer;
+    font-size: 14px;
+    font-weight: bold;
+    transition: all 0.2s;
+    margin-bottom: -3px;
+    border: 3px solid transparent;
+    border-bottom: none;
+}
+
+.tab:hover {
+    background: #d0d0d0;
+}
+
+.tab.active {
+    background: white;
+    border-color: var(--border-color);
+}
+
+.tab-content {
+    display: none;
+}
+
+.tab-content.active {
+    display: block;
+}
+
+/* ============================================
+   CAPO CHART
+   ============================================ */
+.capo-chart {
+    width: 100%;
+    border-collapse: collapse;
+    margin: 15px 0;
+}
+
+.capo-chart th,
+.capo-chart td {
+    padding: 10px;
+    text-align: center;
+    border: 1px solid var(--border-color);
+}
+
+.capo-chart th {
+    background: var(--bg-dark);
+    color: white;
+}
+
+.capo-chart tr:nth-child(even) {
+    background: #f9f9f9;
+}
+
+/* ============================================
+   RESPONSIVE
+   ============================================ */
+@media (max-width: 768px) {
+    .control-panel {
+        flex-direction: column;
+        align-items: stretch;
+    }
+
+    .tunings-grid {
+        grid-template-columns: 1fr;
+    }
+
+    .app-header h1 {
+        font-size: 22px;
+    }
+}
+
+/* ============================================
+   CHORD DIAGRAM STYLES
+   ============================================ */
+.chord-controls {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 15px;
+    padding: 15px;
+    background: white;
+    border: 2px solid var(--border-color);
+    margin-bottom: 20px;
+    align-items: flex-end;
+}
+
+.chord-control-group {
+    display: flex;
+    flex-direction: column;
+    gap: 5px;
+}
+
+.chord-control-group label {
+    font-size: 12px;
+    font-weight: bold;
+    color: var(--text-secondary);
+}
+
+.key-buttons {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 5px;
+}
+
+.key-btn {
+    padding: 8px 12px;
+    border: 2px solid var(--border-color);
+    background: white;
+    cursor: pointer;
+    font-size: 13px;
+    font-weight: bold;
+    border-radius: 4px;
+    transition: all 0.2s;
+    min-width: 45px;
+    text-align: center;
+}
+
+.key-btn:hover {
+    background: #f0f0f0;
+}
+
+.key-btn.active {
+    background: var(--bg-dark);
+    color: white;
+    border-color: var(--bg-dark);
+}
+
+.chord-type-buttons {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 5px;
+}
+
+.chord-type-btn {
+    padding: 6px 10px;
+    border: 2px solid var(--border-color);
+    background: white;
+    cursor: pointer;
+    font-size: 12px;
+    border-radius: 4px;
+    transition: all 0.2s;
+}
+
+.chord-type-btn:hover {
+    background: #f0f0f0;
+}
+
+.chord-type-btn.active {
+    background: var(--g-family);
+    color: white;
+    border-color: var(--g-family);
+}
+
+.view-mode-buttons {
+    display: flex;
+    gap: 5px;
+}
+
+.view-mode-btn {
+    padding: 6px 12px;
+    border: 2px solid var(--border-color);
+    background: white;
+    cursor: pointer;
+    font-size: 12px;
+    border-radius: 4px;
+    transition: all 0.2s;
+}
+
+.view-mode-btn:hover {
+    background: #f0f0f0;
+}
+
+.view-mode-btn.active {
+    background: var(--d-family);
+    color: white;
+    border-color: var(--d-family);
+}
+
+/* All Keys Grid View */
+.chord-keys-grid-container {
+    overflow-x: auto;
+    padding: 10px;
+}
+
+.chord-keys-grid {
+    border-collapse: collapse;
+    width: 100%;
+    background: white;
+    border: 2px solid var(--border-color);
+}
+
+.chord-keys-grid th,
+.chord-keys-grid td {
+    border: 1px solid var(--border-color);
+    padding: 8px;
+    text-align: center;
+    vertical-align: top;
+}
+
+.chord-keys-grid th {
+    background: var(--bg-dark);
+    color: white;
+    font-size: 13px;
+    font-weight: bold;
+    position: sticky;
+    top: 0;
+    z-index: 1;
+}
+
+.chord-keys-grid th.key-header {
+    background: var(--g-family);
+    min-width: 60px;
+}
+
+.chord-keys-grid td.key-cell {
+    background: var(--g-family-light);
+    font-weight: bold;
+    font-size: 16px;
+    vertical-align: middle;
+    min-width: 50px;
+}
+
+.chord-keys-grid .grid-chord-cell {
+    min-width: 150px;
+    padding: 5px;
+}
+
+.chord-keys-grid .grid-chord-diagram {
+    background: #fafafa;
+    border-radius: 4px;
+    padding: 5px;
+}
+
+.chord-keys-grid .grid-chord-title {
+    font-size: 14px;
+    font-weight: bold;
+    margin-bottom: 3px;
+}
+
+.chord-keys-grid .grid-chord-notes {
+    font-size: 10px;
+    color: var(--text-muted);
+    margin-top: 3px;
+}
+
+.chord-keys-grid .grid-no-voicing {
+    color: var(--text-muted);
+    font-style: italic;
+    font-size: 11px;
+    padding: 20px 5px;
+}
+
+.chord-keys-grid .group-separator {
+    background: #1a1a1a;
+    width: 4px;
+    padding: 0;
+    border: none;
+}
+
+.chord-keys-grid th.group-separator {
+    background: #1a1a1a;
+}
+
+@media print {
+    .chord-keys-grid-container {
+        overflow: visible;
+    }
+
+    .chord-keys-grid {
+        font-size: 10px;
+    }
+
+    .chord-keys-grid th,
+    .chord-keys-grid td {
+        padding: 4px;
+    }
+
+    .chord-keys-grid .grid-chord-cell {
+        min-width: 100px;
+    }
+}
+
+.chord-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+    gap: 20px;
+    padding: 10px;
+}
+
+.chord-diagram-container {
+    background: white;
+    border: 2px solid var(--border-color);
+    border-radius: 8px;
+    padding: 15px;
+    text-align: center;
+}
+
+.chord-diagram-title {
+    font-size: 18px;
+    font-weight: bold;
+    margin-bottom: 10px;
+    color: var(--text-primary);
+}
+
+.chord-diagram-subtitle {
+    font-size: 12px;
+    color: var(--text-muted);
+    margin-bottom: 10px;
+}
+
+.chord-notes-display {
+    font-size: 11px;
+    color: var(--text-secondary);
+    margin-top: 8px;
+    padding-top: 8px;
+    border-top: 1px solid var(--border-color);
+}
+
+.finger-toggle {
+    display: flex;
+    gap: 10px;
+    align-items: center;
+}
+
+.finger-toggle label {
+    display: flex;
+    align-items: center;
+    gap: 5px;
+    cursor: pointer;
+    font-size: 13px;
+}
+
+.chord-info-box {
+    background: #f9f9f9;
+    border: 1px solid var(--border-color);
+    padding: 10px 15px;
+    margin-bottom: 20px;
+    font-size: 13px;
+}
+
+.no-chord-message {
+    padding: 40px;
+    text-align: center;
+    color: var(--text-muted);
+    font-style: italic;
+}
+
+@media print {
+    .chord-controls {
+        display: none;
+    }
+
+    .chord-grid {
+        grid-template-columns: repeat(4, 1fr);
+    }
+
+    .chord-diagram-container {
+        break-inside: avoid;
+        page-break-inside: avoid;
+    }
+}
diff --git a/index.html b/index.html
index c5a1af0..bdd4370 100644
--- a/index.html
+++ b/index.html
@@ -4,1120 +4,7 @@
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Interactive Banjo Tunings Reference</title>
-    <style>
-        /* ============================================
-           CSS VARIABLES & BASE STYLES
-           ============================================ */
-        :root {
-            --c-family: #C74242;
-            --c-family-light: #F8E3E3;
-            --d-family: #2D7A4F;
-            --d-family-light: #E3F5E9;
-            --g-family: #2E5C8A;
-            --g-family-light: #E3EEF8;
-            --other-family: #7B5BA6;
-            --other-family-light: #F0E8F5;
-            --text-primary: #1a1a1a;
-            --text-secondary: #444;
-            --text-muted: #666;
-            --bg-dark: #1a1a1a;
-            --bg-light: #f5f5f5;
-            --border-color: #ddd;
-        }
-
-        * {
-            box-sizing: border-box;
-        }
-
-        body {
-            font-family: 'Helvetica Neue', Arial, sans-serif;
-            margin: 0;
-            padding: 0;
-            background: #f0f0f0;
-            color: var(--text-primary);
-            line-height: 1.5;
-        }
-
-        /* ============================================
-           PRINT STYLES
-           ============================================ */
-        @page {
-            size: A4 portrait;
-            margin: 12mm;
-        }
-
-        /* Landscape orientation for chord grid view */
-        @page chord-grid-landscape {
-            size: A4 landscape;
-            margin: 10mm;
-        }
-
-        @media print {
-            body {
-                background: white;
-            }
-
-            .no-print {
-                display: none !important;
-            }
-
-            .print-only {
-                display: block !important;
-            }
-
-            .page-break {
-                page-break-after: always;
-            }
-
-            .page-break-before {
-                page-break-before: always;
-            }
-
-            .tuning-card {
-                break-inside: avoid;
-            }
-
-            .app-container {
-                max-width: 100%;
-                padding: 0;
-            }
-
-            .tunings-grid {
-                grid-template-columns: 1fr !important;
-            }
-
-            /* Chord grid landscape printing */
-            .chord-keys-grid-container {
-                page: chord-grid-landscape;
-            }
-
-            /* Table header repetition on each page */
-            .chord-keys-grid thead {
-                display: table-header-group;
-            }
-
-            .chord-keys-grid tbody {
-                display: table-row-group;
-            }
-
-            /* Force page break before F#/Gb row */
-            .chord-keys-grid tr.page-break-before {
-                page-break-before: always;
-            }
-
-            /* Keep key rows together */
-            .chord-keys-grid tr {
-                page-break-inside: avoid;
-            }
-        }
-
-        .print-only {
-            display: none;
-        }
-
-        /* ============================================
-           LAYOUT
-           ============================================ */
-        .app-container {
-            max-width: 1400px;
-            margin: 0 auto;
-            padding: 20px;
-        }
-
-        /* ============================================
-           HEADER
-           ============================================ */
-        .app-header {
-            text-align: center;
-            border: 3px solid #000;
-            padding: 20px;
-            margin-bottom: 20px;
-            background: var(--bg-light);
-            border-radius: 8px;
-        }
-
-        .app-header h1 {
-            margin: 0 0 10px 0;
-            font-size: 28px;
-            font-weight: bold;
-            letter-spacing: 0.5px;
-        }
-
-        .app-header .subtitle {
-            font-size: 14px;
-            color: var(--text-secondary);
-            margin: 5px 0;
-        }
-
-        /* ============================================
-           CONTROL PANEL
-           ============================================ */
-        .control-panel {
-            background: var(--bg-dark);
-            color: white;
-            padding: 20px;
-            border-radius: 8px;
-            margin-bottom: 20px;
-            display: flex;
-            flex-wrap: wrap;
-            gap: 20px;
-            align-items: center;
-            justify-content: space-between;
-        }
-
-        .control-group {
-            display: flex;
-            flex-direction: column;
-            gap: 6px;
-        }
-
-        .control-group label {
-            font-size: 11px;
-            text-transform: uppercase;
-            letter-spacing: 1px;
-            color: #aaa;
-        }
-
-        .control-group select,
-        .control-group input[type="number"] {
-            padding: 8px 12px;
-            border: 2px solid #444;
-            border-radius: 4px;
-            background: #333;
-            color: white;
-            font-size: 14px;
-            min-width: 150px;
-        }
-
-        .control-group select:focus,
-        .control-group input:focus {
-            outline: none;
-            border-color: var(--g-family);
-        }
-
-        /* Toggle Switch */
-        .toggle-container {
-            display: flex;
-            align-items: center;
-            gap: 10px;
-        }
-
-        .toggle-switch {
-            position: relative;
-            width: 60px;
-            height: 30px;
-        }
-
-        .toggle-switch input {
-            opacity: 0;
-            width: 0;
-            height: 0;
-        }
-
-        .toggle-slider {
-            position: absolute;
-            cursor: pointer;
-            top: 0;
-            left: 0;
-            right: 0;
-            bottom: 0;
-            background-color: #444;
-            transition: 0.3s;
-            border-radius: 30px;
-        }
-
-        .toggle-slider:before {
-            position: absolute;
-            content: "";
-            height: 22px;
-            width: 22px;
-            left: 4px;
-            bottom: 4px;
-            background-color: white;
-            transition: 0.3s;
-            border-radius: 50%;
-        }
-
-        .toggle-switch input:checked + .toggle-slider {
-            background-color: var(--g-family);
-        }
-
-        .toggle-switch input:checked + .toggle-slider:before {
-            transform: translateX(30px);
-        }
-
-        .toggle-label {
-            font-size: 14px;
-            font-weight: bold;
-            min-width: 50px;
-        }
-
-        /* Checkbox toggle for notes */
-        .checkbox-container {
-            display: flex;
-            align-items: center;
-            gap: 8px;
-        }
-
-        .checkbox-container input[type="checkbox"] {
-            width: 18px;
-            height: 18px;
-            cursor: pointer;
-        }
-
-        .checkbox-container span {
-            font-size: 14px;
-        }
-
-        /* Buttons */
-        .btn {
-            padding: 10px 20px;
-            border: none;
-            border-radius: 4px;
-            font-size: 14px;
-            font-weight: bold;
-            cursor: pointer;
-            transition: all 0.2s;
-        }
-
-        .btn-primary {
-            background: var(--g-family);
-            color: white;
-        }
-
-        .btn-primary:hover {
-            background: #1e4a6e;
-        }
-
-        .btn-secondary {
-            background: #444;
-            color: white;
-        }
-
-        .btn-secondary:hover {
-            background: #555;
-        }
-
-        /* ============================================
-           PAGE TITLE
-           ============================================ */
-        .page-title {
-            font-size: 18px;
-            font-weight: bold;
-            text-align: center;
-            padding: 12px;
-            background: #333;
-            color: white;
-            margin-bottom: 20px;
-            border-radius: 4px;
-            text-transform: uppercase;
-            letter-spacing: 1px;
-        }
-
-        /* ============================================
-           FAMILY INTRO
-           ============================================ */
-        .family-intro {
-            background: #f0f0f0;
-            border-left: 5px solid;
-            padding: 15px;
-            margin-bottom: 15px;
-            font-size: 14px;
-            line-height: 1.6;
-            border-radius: 0 4px 4px 0;
-        }
-
-        .family-intro.c-family-intro { border-color: var(--c-family); }
-        .family-intro.d-family-intro { border-color: var(--d-family); }
-        .family-intro.g-family-intro { border-color: var(--g-family); }
-        .family-intro.other-family-intro { border-color: var(--other-family); }
-
-        /* ============================================
-           TUNINGS GRID
-           ============================================ */
-        .tunings-grid {
-            display: grid;
-            grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
-            gap: 20px;
-            margin-bottom: 20px;
-        }
-
-        /* ============================================
-           TUNING CARD
-           ============================================ */
-        .tuning-card {
-            border: 3px solid;
-            border-radius: 8px;
-            padding: 16px;
-            background: white;
-            box-shadow: 0 3px 6px rgba(0,0,0,0.15);
-            transition: transform 0.2s, box-shadow 0.2s;
-        }
-
-        .tuning-card:hover {
-            transform: translateY(-2px);
-            box-shadow: 0 5px 15px rgba(0,0,0,0.2);
-        }
-
-        .tuning-card.c-family {
-            border-color: var(--c-family);
-            background: linear-gradient(to bottom, var(--c-family-light) 0%, #FEF8F8 100%);
-        }
-
-        .tuning-card.d-family {
-            border-color: var(--d-family);
-            background: linear-gradient(to bottom, var(--d-family-light) 0%, #F8FEF9 100%);
-        }
-
-        .tuning-card.g-family {
-            border-color: var(--g-family);
-            background: linear-gradient(to bottom, var(--g-family-light) 0%, #F8FBFE 100%);
-        }
-
-        .tuning-card.other-family {
-            border-color: var(--other-family);
-            background: linear-gradient(to bottom, var(--other-family-light) 0%, #FDFBFE 100%);
-        }
-
-        .tuning-header {
-            display: flex;
-            justify-content: space-between;
-            align-items: baseline;
-            margin-bottom: 8px;
-            border-bottom: 3px solid;
-            padding-bottom: 8px;
-        }
-
-        .c-family .tuning-header { border-color: var(--c-family); }
-        .d-family .tuning-header { border-color: var(--d-family); }
-        .g-family .tuning-header { border-color: var(--g-family); }
-        .other-family .tuning-header { border-color: var(--other-family); }
-
-        .tuning-name {
-            font-size: 16px;
-            font-weight: bold;
-            text-transform: uppercase;
-        }
-
-        .genre-tag {
-            font-size: 12px;
-            font-style: italic;
-            color: var(--text-muted);
-        }
-
-        .string-count-badge {
-            display: inline-block;
-            background: #666;
-            color: white;
-            font-size: 10px;
-            padding: 2px 6px;
-            border-radius: 3px;
-            margin-left: 8px;
-        }
-
-        .note-sequence {
-            font-family: 'Courier New', Consolas, monospace;
-            font-size: 20px;
-            font-weight: bold;
-            letter-spacing: 4px;
-            margin: 12px 0;
-            text-align: center;
-            background: white;
-            padding: 12px 8px;
-            border-radius: 4px;
-            border: 2px solid var(--border-color);
-        }
-
-        .note-sequence sub {
-            font-size: 0.6em;
-            vertical-align: sub;
-        }
-
-        /* Nut Visual */
-        .nut-visual {
-            display: flex;
-            justify-content: center;
-            gap: 12px;
-            margin: 12px 0;
-        }
-
-        .string-circle {
-            width: 28px;
-            height: 28px;
-            border: 3px solid #333;
-            border-radius: 50%;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-            font-size: 11px;
-            font-weight: bold;
-            background: white;
-        }
-
-        .string-circle.changed {
-            background: #333;
-            color: white;
-        }
-
-        /* Tuning Details */
-        .tuning-details {
-            font-size: 13px;
-            line-height: 1.7;
-            color: #333;
-            margin-top: 10px;
-        }
-
-        .tuning-details div {
-            margin: 5px 0;
-        }
-
-        .delta-line {
-            font-weight: bold;
-        }
-
-        .capo-line {
-            font-weight: bold;
-            color: #0066cc;
-        }
-
-        .songs-line {
-            font-style: italic;
-            color: #555;
-        }
-
-        /* Effective Tuning (with capo) */
-        .effective-tuning {
-            background: #fff8e1;
-            border: 2px solid #ff9800;
-            padding: 8px;
-            border-radius: 4px;
-            margin-top: 10px;
-            font-size: 12px;
-        }
-
-        .effective-tuning strong {
-            color: #e65100;
-        }
-
-        /* ============================================
-           FRETBOARD DIAGRAM
-           ============================================ */
-        .fretboard-container {
-            margin: 15px 0;
-            overflow-x: auto;
-        }
-
-        .fretboard-svg {
-            display: block;
-            margin: 0 auto;
-        }
-
-        /* ============================================
-           FRETBOARD PAGE LAYOUT (Side-by-side)
-           ============================================ */
-        .fretboard-layout {
-            display: flex;
-            gap: 20px;
-            align-items: flex-start;
-        }
-
-        .tuning-selector {
-            width: 280px;
-            flex-shrink: 0;
-            background: white;
-            border: 2px solid var(--border-color);
-            border-radius: 8px;
-            overflow: hidden;
-        }
-
-        .tuning-selector-header {
-            background: var(--bg-dark);
-            color: white;
-            padding: 12px 15px;
-            font-weight: bold;
-            font-size: 14px;
-        }
-
-        .tuning-selector-filter {
-            padding: 10px;
-            border-bottom: 1px solid var(--border-color);
-            background: #f9f9f9;
-        }
-
-        .tuning-selector-filter select {
-            width: 100%;
-            padding: 8px;
-            border: 1px solid var(--border-color);
-            border-radius: 4px;
-            font-size: 13px;
-        }
-
-        .tuning-list {
-            max-height: 500px;
-            overflow-y: auto;
-        }
-
-        .tuning-list-item {
-            padding: 10px 15px;
-            cursor: pointer;
-            border-bottom: 1px solid #eee;
-            transition: background 0.15s;
-            font-size: 13px;
-        }
-
-        .tuning-list-item:hover {
-            background: #f5f5f5;
-        }
-
-        .tuning-list-item.selected {
-            background: var(--g-family-light);
-            border-left: 4px solid var(--g-family);
-            font-weight: bold;
-        }
-
-        .tuning-list-item.c-family.selected {
-            background: var(--c-family-light);
-            border-left-color: var(--c-family);
-        }
-
-        .tuning-list-item.d-family.selected {
-            background: var(--d-family-light);
-            border-left-color: var(--d-family);
-        }
-
-        .tuning-list-item.other-family.selected {
-            background: var(--other-family-light);
-            border-left-color: var(--other-family);
-        }
-
-        .tuning-list-item .tuning-item-name {
-            display: block;
-            font-weight: 600;
-        }
-
-        .tuning-list-item .tuning-item-notes {
-            display: block;
-            font-size: 11px;
-            color: var(--text-muted);
-            font-family: 'Courier New', monospace;
-            margin-top: 2px;
-        }
-
-        .tuning-list-group {
-            background: #f0f0f0;
-            padding: 8px 15px;
-            font-size: 11px;
-            font-weight: bold;
-            text-transform: uppercase;
-            letter-spacing: 0.5px;
-            color: var(--text-secondary);
-            border-bottom: 1px solid var(--border-color);
-        }
-
-        .fretboard-display {
-            flex: 1;
-            min-width: 0;
-        }
-
-        @media print {
-            .tuning-selector {
-                display: none !important;
-            }
-
-            .fretboard-layout {
-                display: block;
-            }
-
-            .fretboard-display {
-                width: 100%;
-            }
-        }
-
-        @media (max-width: 900px) {
-            .fretboard-layout {
-                flex-direction: column;
-            }
-
-            .tuning-selector {
-                width: 100%;
-            }
-
-            .tuning-list {
-                max-height: 200px;
-            }
-        }
-
-        /* ============================================
-           REFERENCE BOX
-           ============================================ */
-        .reference-box {
-            background: var(--bg-dark);
-            color: white;
-            padding: 15px;
-            border-radius: 4px;
-            font-size: 13px;
-            line-height: 1.8;
-            margin-bottom: 20px;
-        }
-
-        .reference-box strong {
-            font-size: 14px;
-            display: block;
-            margin-bottom: 8px;
-        }
-
-        /* ============================================
-           REFERENCE SECTIONS
-           ============================================ */
-        .reference-section {
-            background: white;
-            border: 2px solid var(--border-color);
-            padding: 20px;
-            border-radius: 8px;
-            margin-bottom: 20px;
-        }
-
-        .reference-section h3 {
-            margin: 0 0 15px 0;
-            padding-bottom: 10px;
-            border-bottom: 2px solid var(--border-color);
-        }
-
-        .fret-reference-box {
-            padding: 15px;
-            margin-bottom: 15px;
-            border-radius: 6px;
-        }
-
-        .fret-reference-box.blue {
-            background: var(--g-family-light);
-            border: 3px solid var(--g-family);
-        }
-
-        .fret-reference-box.red {
-            background: var(--c-family-light);
-            border: 3px solid var(--c-family);
-        }
-
-        .fret-reference-box.green {
-            background: var(--d-family-light);
-            border: 3px solid var(--d-family);
-        }
-
-        .fret-reference-box h4 {
-            margin: 0 0 10px 0;
-            font-size: 14px;
-        }
-
-        .fret-reference-box ul {
-            margin: 0;
-            padding-left: 20px;
-            line-height: 1.8;
-        }
-
-        /* ============================================
-           WARNING BOX
-           ============================================ */
-        .warning-box {
-            background: #fff8e1;
-            border: 3px solid #ff9800;
-            padding: 15px;
-            margin: 15px 0;
-            border-radius: 6px;
-        }
-
-        .warning-box h4 {
-            margin: 0 0 8px 0;
-            color: #e65100;
-        }
-
-        /* ============================================
-           LEGEND
-           ============================================ */
-        .legend {
-            font-size: 12px;
-            color: var(--text-muted);
-            text-align: center;
-            margin-top: 20px;
-            padding-top: 15px;
-            border-top: 2px solid var(--border-color);
-        }
-
-        /* ============================================
-           TABS
-           ============================================ */
-        .tabs {
-            display: flex;
-            gap: 5px;
-            margin-bottom: 20px;
-            border-bottom: 3px solid var(--border-color);
-            padding-bottom: 0;
-        }
-
-        .tab {
-            padding: 12px 24px;
-            background: #e0e0e0;
-            border: none;
-            border-radius: 8px 8px 0 0;
-            cursor: pointer;
-            font-size: 14px;
-            font-weight: bold;
-            transition: all 0.2s;
-            margin-bottom: -3px;
-            border: 3px solid transparent;
-            border-bottom: none;
-        }
-
-        .tab:hover {
-            background: #d0d0d0;
-        }
-
-        .tab.active {
-            background: white;
-            border-color: var(--border-color);
-        }
-
-        .tab-content {
-            display: none;
-        }
-
-        .tab-content.active {
-            display: block;
-        }
-
-        /* ============================================
-           CAPO CHART
-           ============================================ */
-        .capo-chart {
-            width: 100%;
-            border-collapse: collapse;
-            margin: 15px 0;
-        }
-
-        .capo-chart th,
-        .capo-chart td {
-            padding: 10px;
-            text-align: center;
-            border: 1px solid var(--border-color);
-        }
-
-        .capo-chart th {
-            background: var(--bg-dark);
-            color: white;
-        }
-
-        .capo-chart tr:nth-child(even) {
-            background: #f9f9f9;
-        }
-
-        /* ============================================
-           RESPONSIVE
-           ============================================ */
-        @media (max-width: 768px) {
-            .control-panel {
-                flex-direction: column;
-                align-items: stretch;
-            }
-
-            .tunings-grid {
-                grid-template-columns: 1fr;
-            }
-
-            .app-header h1 {
-                font-size: 22px;
-            }
-        }
-
-        /* ============================================
-           CHORD DIAGRAM STYLES
-           ============================================ */
-        .chord-controls {
-            display: flex;
-            flex-wrap: wrap;
-            gap: 15px;
-            padding: 15px;
-            background: white;
-            border: 2px solid var(--border-color);
-            margin-bottom: 20px;
-            align-items: flex-end;
-        }
-
-        .chord-control-group {
-            display: flex;
-            flex-direction: column;
-            gap: 5px;
-        }
-
-        .chord-control-group label {
-            font-size: 12px;
-            font-weight: bold;
-            color: var(--text-secondary);
-        }
-
-        .key-buttons {
-            display: flex;
-            flex-wrap: wrap;
-            gap: 5px;
-        }
-
-        .key-btn {
-            padding: 8px 12px;
-            border: 2px solid var(--border-color);
-            background: white;
-            cursor: pointer;
-            font-size: 13px;
-            font-weight: bold;
-            border-radius: 4px;
-            transition: all 0.2s;
-            min-width: 45px;
-            text-align: center;
-        }
-
-        .key-btn:hover {
-            background: #f0f0f0;
-        }
-
-        .key-btn.active {
-            background: var(--bg-dark);
-            color: white;
-            border-color: var(--bg-dark);
-        }
-
-        .chord-type-buttons {
-            display: flex;
-            flex-wrap: wrap;
-            gap: 5px;
-        }
-
-        .chord-type-btn {
-            padding: 6px 10px;
-            border: 2px solid var(--border-color);
-            background: white;
-            cursor: pointer;
-            font-size: 12px;
-            border-radius: 4px;
-            transition: all 0.2s;
-        }
-
-        .chord-type-btn:hover {
-            background: #f0f0f0;
-        }
-
-        .chord-type-btn.active {
-            background: var(--g-family);
-            color: white;
-            border-color: var(--g-family);
-        }
-
-        .view-mode-buttons {
-            display: flex;
-            gap: 5px;
-        }
-
-        .view-mode-btn {
-            padding: 6px 12px;
-            border: 2px solid var(--border-color);
-            background: white;
-            cursor: pointer;
-            font-size: 12px;
-            border-radius: 4px;
-            transition: all 0.2s;
-        }
-
-        .view-mode-btn:hover {
-            background: #f0f0f0;
-        }
-
-        .view-mode-btn.active {
-            background: var(--d-family);
-            color: white;
-            border-color: var(--d-family);
-        }
-
-        /* All Keys Grid View */
-        .chord-keys-grid-container {
-            overflow-x: auto;
-            padding: 10px;
-        }
-
-        .chord-keys-grid {
-            border-collapse: collapse;
-            width: 100%;
-            background: white;
-            border: 2px solid var(--border-color);
-        }
-
-        .chord-keys-grid th,
-        .chord-keys-grid td {
-            border: 1px solid var(--border-color);
-            padding: 8px;
-            text-align: center;
-            vertical-align: top;
-        }
-
-        .chord-keys-grid th {
-            background: var(--bg-dark);
-            color: white;
-            font-size: 13px;
-            font-weight: bold;
-            position: sticky;
-            top: 0;
-            z-index: 1;
-        }
-
-        .chord-keys-grid th.key-header {
-            background: var(--g-family);
-            min-width: 60px;
-        }
-
-        .chord-keys-grid td.key-cell {
-            background: var(--g-family-light);
-            font-weight: bold;
-            font-size: 16px;
-            vertical-align: middle;
-            min-width: 50px;
-        }
-
-        .chord-keys-grid .grid-chord-cell {
-            min-width: 150px;
-            padding: 5px;
-        }
-
-        .chord-keys-grid .grid-chord-diagram {
-            background: #fafafa;
-            border-radius: 4px;
-            padding: 5px;
-        }
-
-        .chord-keys-grid .grid-chord-title {
-            font-size: 14px;
-            font-weight: bold;
-            margin-bottom: 3px;
-        }
-
-        .chord-keys-grid .grid-chord-notes {
-            font-size: 10px;
-            color: var(--text-muted);
-            margin-top: 3px;
-        }
-
-        .chord-keys-grid .grid-no-voicing {
-            color: var(--text-muted);
-            font-style: italic;
-            font-size: 11px;
-            padding: 20px 5px;
-        }
-
-        .chord-keys-grid .group-separator {
-            background: #1a1a1a;
-            width: 4px;
-            padding: 0;
-            border: none;
-        }
-
-        .chord-keys-grid th.group-separator {
-            background: #1a1a1a;
-        }
-
-        @media print {
-            .chord-keys-grid-container {
-                overflow: visible;
-            }
-
-            .chord-keys-grid {
-                font-size: 10px;
-            }
-
-            .chord-keys-grid th,
-            .chord-keys-grid td {
-                padding: 4px;
-            }
-
-            .chord-keys-grid .grid-chord-cell {
-                min-width: 100px;
-            }
-        }
-
-        .chord-grid {
-            display: grid;
-            grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
-            gap: 20px;
-            padding: 10px;
-        }
-
-        .chord-diagram-container {
-            background: white;
-            border: 2px solid var(--border-color);
-            border-radius: 8px;
-            padding: 15px;
-            text-align: center;
-        }
-
-        .chord-diagram-title {
-            font-size: 18px;
-            font-weight: bold;
-            margin-bottom: 10px;
-            color: var(--text-primary);
-        }
-
-        .chord-diagram-subtitle {
-            font-size: 12px;
-            color: var(--text-muted);
-            margin-bottom: 10px;
-        }
-
-        .chord-notes-display {
-            font-size: 11px;
-            color: var(--text-secondary);
-            margin-top: 8px;
-            padding-top: 8px;
-            border-top: 1px solid var(--border-color);
-        }
-
-        .finger-toggle {
-            display: flex;
-            gap: 10px;
-            align-items: center;
-        }
-
-        .finger-toggle label {
-            display: flex;
-            align-items: center;
-            gap: 5px;
-            cursor: pointer;
-            font-size: 13px;
-        }
-
-        .chord-info-box {
-            background: #f9f9f9;
-            border: 1px solid var(--border-color);
-            padding: 10px 15px;
-            margin-bottom: 20px;
-            font-size: 13px;
-        }
-
-        .no-chord-message {
-            padding: 40px;
-            text-align: center;
-            color: var(--text-muted);
-            font-style: italic;
-        }
-
-        @media print {
-            .chord-controls {
-                display: none;
-            }
-
-            .chord-grid {
-                grid-template-columns: repeat(4, 1fr);
-            }
-
-            .chord-diagram-container {
-                break-inside: avoid;
-                page-break-inside: avoid;
-            }
-        }
-    </style>
+    <link rel="stylesheet" href="css/styles.css">
 </head>
 <body>
     <div class="app-container">
@@ -1445,1919 +332,12 @@
         </div>
     </div>
 
-    <script>
-        // ============================================
-        // CHROMATIC SCALE & NOTE UTILITIES
-        // ============================================
-        const CHROMATIC_SCALE = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
-
-        // Normalize note names (flats to sharps, handle case)
-        const NOTE_MAP = {
-            'c': 'C', 'c#': 'C#', 'db': 'C#', 'Db': 'C#',
-            'd': 'D', 'd#': 'D#', 'eb': 'D#', 'Eb': 'D#',
-            'e': 'E', 'fb': 'E', 'Fb': 'E',
-            'f': 'F', 'e#': 'F', 'E#': 'F', 'f#': 'F#', 'gb': 'F#', 'Gb': 'F#',
-            'g': 'G', 'g#': 'G#', 'ab': 'G#', 'Ab': 'G#',
-            'a': 'A', 'a#': 'A#', 'bb': 'A#', 'Bb': 'A#',
-            'b': 'B', 'cb': 'B', 'Cb': 'B', 'b#': 'C', 'B#': 'C',
-            'C': 'C', 'C#': 'C#', 'D': 'D', 'D#': 'D#', 'E': 'E',
-            'F': 'F', 'F#': 'F#', 'G': 'G', 'G#': 'G#', 'A': 'A', 'A#': 'A#', 'B': 'B'
-        };
-
-        // Display note mapping - prefer flats for G# and A# (as per user's Python script)
-        const DISPLAY_NOTE_MAP = {
-            'G#': 'Ab',
-            'A#': 'Bb'
-            // Keep D#, C#, F# as sharps
-        };
-
-        // Solfège mapping: C=Do, D=Re, E=Mi, F=Fa, G=Sol, A=La, B=Si
-        const SOLFEGE_MAP = {
-            'C': 'Do', 'D': 'Re', 'E': 'Mi', 'F': 'Fa',
-            'G': 'Sol', 'A': 'La', 'B': 'Si'
-        };
-
-        function normalizeNote(note) {
-            return NOTE_MAP[note] || note.toUpperCase();
-        }
-
-        // Check if a note is natural (no sharps or flats)
-        function isNaturalNote(note) {
-            const normalized = normalizeNote(note);
-            return ['C', 'D', 'E', 'F', 'G', 'A', 'B'].includes(normalized);
-        }
-
-        function displayNote(note) {
-            // Convert internal sharp note to preferred display (mix of flats and sharps)
-            if (DISPLAY_NOTE_MAP[note]) {
-                return DISPLAY_NOTE_MAP[note];
-            }
-            return note;
-        }
-
-        function toSolfege(note) {
-            // Convert a note to solfège notation
-            // Handle sharps/flats: C# -> Do#, Db -> Reb, etc.
-            const baseNote = note.charAt(0).toUpperCase();
-            const accidental = note.slice(1); // # or b or empty
-            const solfege = SOLFEGE_MAP[baseNote] || baseNote;
-            return solfege + accidental;
-        }
-
-        function getNoteAtFret(openNote, fret) {
-            const normalized = normalizeNote(openNote);
-            const startIndex = CHROMATIC_SCALE.indexOf(normalized);
-            if (startIndex === -1) return openNote;
-            const newIndex = (startIndex + fret) % 12;
-            return CHROMATIC_SCALE[newIndex];
-        }
-
-        // ============================================
-        // CHORD DATA & FUNCTIONS
-        // ============================================
-
-        // 12 keys with display names (using slash for enharmonics)
-        const CHORD_KEYS = [
-            { root: 'C',  display: 'C' },
-            { root: 'C#', display: 'C#/Db' },
-            { root: 'D',  display: 'D' },
-            { root: 'D#', display: 'D#/Eb' },
-            { root: 'E',  display: 'E' },
-            { root: 'F',  display: 'F' },
-            { root: 'F#', display: 'F#/Gb' },
-            { root: 'G',  display: 'G' },
-            { root: 'G#', display: 'G#/Ab' },
-            { root: 'A',  display: 'A' },
-            { root: 'A#', display: 'A#/Bb' },
-            { root: 'B',  display: 'B' }
-        ];
-
-        // 8 chord types with intervals (semitones from root)
-        const CHORD_TYPES = {
-            'major': { symbol: '', name: 'Major', intervals: [0, 4, 7], degrees: ['1', '3', '5'] },
-            'minor': { symbol: 'm', name: 'Minor', intervals: [0, 3, 7], degrees: ['1', '♭3', '5'] },
-            'm7':    { symbol: 'm7', name: 'Minor 7', intervals: [0, 3, 7, 10], degrees: ['1', '♭3', '5', '♭7'] },
-            '6':     { symbol: '6', name: 'Sixth', intervals: [0, 4, 7, 9], degrees: ['1', '3', '5', '6'] },
-            '7':     { symbol: '7', name: 'Seventh', intervals: [0, 4, 7, 10], degrees: ['1', '3', '5', '♭7'] },
-            'dim':   { symbol: 'dim', name: 'Diminished', intervals: [0, 3, 6], degrees: ['1', '♭3', '♭5'] },
-            'sus4':  { symbol: 'sus4', name: 'Sus4', intervals: [0, 5, 7], degrees: ['1', '4', '5'] },
-            'aug':   { symbol: 'aug', name: 'Augmented', intervals: [0, 4, 8], degrees: ['1', '3', '#5'] }
-        };
-
-        // Chord types grouped for display with separators between groups
-        const CHORD_TYPE_GROUPS = [
-            ['major', 'minor', 'm7'],  // Group 1
-            ['6', '7'],                 // Group 2
-            ['dim'],                    // Group 3
-            ['sus4'],                   // Group 4
-            ['aug']                     // Group 5
-        ];
-
-        // Ordered list of chord types (flattened groups)
-        const CHORD_TYPE_ORDER = CHORD_TYPE_GROUPS.flat();
-
-        // Finger notation mapping
-        const FINGER_NOTATION = {
-            numbers: { 1: '1', 2: '2', 3: '3', 4: '4', T: 'T' },
-            pima: { 1: 'I', 2: 'M', 3: 'A', 4: 'P', T: 'P' }
-        };
-
-        // Convert note name to pitch class (0-11)
-        function noteToPitchClass(note) {
-            const normalized = normalizeNote(note);
-            return CHROMATIC_SCALE.indexOf(normalized);
-        }
-
-        // Get chord pitch classes from root and type
-        function getChordPitchClasses(rootNote, chordType) {
-            const type = CHORD_TYPES[chordType];
-            if (!type) return [];
-
-            const rootPitch = noteToPitchClass(rootNote);
-            if (rootPitch === -1) return [];
-
-            return type.intervals.map(interval => (rootPitch + interval) % 12);
-        }
-
-        // Get all notes in a chord (as note names)
-        function getChordNotes(rootNote, chordType) {
-            const pitchClasses = getChordPitchClasses(rootNote, chordType);
-            return pitchClasses.map(pc => CHROMATIC_SCALE[pc]);
-        }
-
-        // Find chord voicing for 4-string banjo (excludes 5th drone string for low positions)
-        // For 5-string banjo, we only use the 4 main strings for chords (DGBD in Open G)
-        function findChordVoicing(tuningData, rootNote, chordType, maxFret = 12) {
-            const chordPitchClasses = getChordPitchClasses(rootNote, chordType);
-            if (chordPitchClasses.length === 0) return null;
-
-            const type = CHORD_TYPES[chordType];
-            const rootPitch = noteToPitchClass(rootNote);
-
-            // Get the 4 main strings (exclude 5th/drone string for 5-string banjo)
-            const allStrings = tuningData.strings;
-            const shortStringIdx = tuningData.shortStringIndex;
-
-            // For 5-string banjo, use strings 1-4 (indices 1-4), exclude index 0 (5th string)
-            // For 4-string banjo, use all strings
-            let mainStrings;
-            if (shortStringIdx === 0 && allStrings.length === 5) {
-                // 5-string banjo: use strings at indices 1,2,3,4 (4th,3rd,2nd,1st strings)
-                mainStrings = allStrings.slice(1);
-            } else {
-                mainStrings = allStrings;
-            }
-
-            const numStrings = mainStrings.length;
-            const stringPitchClasses = mainStrings.map(note => noteToPitchClass(note));
-
-            // Find all valid voicings using recursive backtracking
-            const results = [];
-            const maxReach = 4;
-            const maxFingers = 4;
-
-            function explore(stringIdx, currentVoicing, usedTones) {
-                const fingersUsed = currentVoicing.filter(v => v && v.fret > 0).length;
-                if (fingersUsed > maxFingers) return;
-
-                const frettedFrets = currentVoicing.filter(v => v && v.fret > 0).map(v => v.fret);
-                if (frettedFrets.length > 1) {
-                    const reach = Math.max(...frettedFrets) - Math.min(...frettedFrets);
-                    if (reach > maxReach) return;
-                }
-
-                if (stringIdx === numStrings) {
-                    if (usedTones.size === chordPitchClasses.length) {
-                        results.push([...currentVoicing]);
-                    }
-                    return;
-                }
-
-                const stringPitch = stringPitchClasses[stringIdx];
-
-                // Option 1: Mute this string
-                explore(stringIdx + 1, [...currentVoicing, null], new Set(usedTones));
-
-                // Try each fret position (0 = open, up to maxFret)
-                for (let fret = 0; fret <= maxFret; fret++) {
-                    const pitchAtFret = (stringPitch + fret) % 12;
-                    const toneIndex = chordPitchClasses.indexOf(pitchAtFret);
-
-                    if (toneIndex !== -1) {
-                        const newUsedTones = new Set(usedTones);
-                        newUsedTones.add(pitchAtFret);
-
-                        explore(stringIdx + 1, [...currentVoicing, {
-                            fret: fret,
-                            pitchClass: pitchAtFret,
-                            toneIndex: toneIndex,
-                            note: CHROMATIC_SCALE[pitchAtFret]
-                        }], newUsedTones);
-                    }
-                }
-            }
-
-            explore(0, [], new Set());
-
-            if (results.length === 0) return null;
-
-            // Score and sort voicings
-            function scoreVoicing(voicing) {
-                const sounding = voicing.filter(v => v !== null);
-                const fretted = sounding.filter(v => v.fret > 0);
-                const minFret = fretted.length > 0 ? Math.min(...fretted.map(v => v.fret)) : 0;
-                const maxFretUsed = fretted.length > 0 ? Math.max(...fretted.map(v => v.fret)) : 0;
-                const span = maxFretUsed - minFret;
-                const openCount = sounding.filter(v => v.fret === 0).length;
-                const soundingCount = sounding.length;
-                return minFret * 100 + span * 50 - openCount * 30 - soundingCount * 10;
-            }
-
-            results.sort((a, b) => scoreVoicing(a) - scoreVoicing(b));
-            const bestVoicing = results[0];
-
-            // Convert to position format for rendering
-            // Positions are ordered: string 4 (index 0), string 3, string 2, string 1 (index 3)
-            const positions = bestVoicing.map((pos, idx) => {
-                const stringNum = 4 - idx; // 4, 3, 2, 1
-
-                if (pos === null) {
-                    return {
-                        stringNum: stringNum,
-                        fret: null,
-                        muted: true,
-                        note: null,
-                        degree: null,
-                        isRoot: false
-                    };
-                }
-
-                const degree = type.degrees[pos.toneIndex];
-                const isRoot = pos.pitchClass === rootPitch;
-
-                return {
-                    stringNum: stringNum,
-                    fret: pos.fret,
-                    muted: false,
-                    note: pos.note,
-                    degree: degree,
-                    isRoot: isRoot
-                };
-            });
-
-            // Assign finger numbers based on fret position
-            // Finger 1 = lowest fret, then 2, 3, 4 in ascending order
-            assignFingers(positions);
-
-            // Determine base fret for display
-            const frettedPositions = positions.filter(p => p.fret > 0);
-            const minFret = frettedPositions.length > 0 ? Math.min(...frettedPositions.map(p => p.fret)) : 1;
-            const baseFret = minFret > 4 ? minFret : 1;
-
-            // Check for barre chord
-            const barre = detectBarre(positions);
-
-            // Get chord notes for display
-            const chordNotes = getChordNotes(rootNote, chordType);
-
-            return {
-                positions,
-                baseFret,
-                barre,
-                chordNotes: chordNotes.map(n => displayNote(n)),
-                numStrings: numStrings
-            };
-        }
-
-        function assignFingers(positions) {
-            // Get fretted positions sorted by fret (ascending), then by string number (descending)
-            // This ensures: lowest fret first, and at same fret, higher string numbers first
-            const fretted = positions
-                .filter(p => p.fret > 0)
-                .sort((a, b) => {
-                    if (a.fret !== b.fret) return a.fret - b.fret;
-                    return b.stringNum - a.stringNum; // Higher string number first at same fret
-                });
-
-            if (fretted.length === 0) return;
-
-            // Assign unique finger to each position in sequence
-            fretted.forEach((p, idx) => {
-                p.finger = Math.min(idx + 1, 4);
-            });
-        }
-
-        function detectBarre(positions) {
-            const fretGroups = {};
-            positions.forEach((p, idx) => {
-                if (p.fret > 0) {
-                    if (!fretGroups[p.fret]) fretGroups[p.fret] = [];
-                    fretGroups[p.fret].push({ pos: p, idx: idx });
-                }
-            });
-
-            for (const [fret, group] of Object.entries(fretGroups)) {
-                if (group.length >= 2) {
-                    // Check if they're on adjacent strings
-                    const stringNums = group.map(g => g.pos.stringNum).sort((a, b) => b - a);
-                    const minStr = Math.min(...stringNums);
-                    const maxStr = Math.max(...stringNums);
-
-                    if (maxStr - minStr <= 3) {
-                        return {
-                            fret: parseInt(fret),
-                            fromString: minStr,
-                            toString: maxStr
-                        };
-                    }
-                }
-            }
-
-            return null;
-        }
-
-        // Generate SVG chord diagram for 4 main strings
-        function generateChordDiagramSVG(voicing, chordName, tuningName, fingerNotation = 'numbers') {
-            const width = 140;
-            const height = 200;
-            const marginLeft = 35;
-            const marginTop = 35;
-            const marginRight = 15;
-            const marginBottom = 25;
-
-            const positions = voicing.positions;
-            const numStrings = 4; // Always 4 main strings for chord diagrams
-            const numFrets = 6;
-            const baseFret = voicing.baseFret;
-
-            const stringSpacing = (width - marginLeft - marginRight) / (numStrings - 1);
-            const fretSpacing = (height - marginTop - marginBottom) / numFrets;
-
-            let svg = `<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">`;
-
-            // Background
-            svg += `<rect width="${width}" height="${height}" fill="white"/>`;
-
-            // Nut (thick line at top) - only show if baseFret is 1
-            if (baseFret === 1) {
-                svg += `<rect x="${marginLeft - 3}" y="${marginTop - 3}" width="${(numStrings - 1) * stringSpacing + 6}" height="5" fill="#1a1a1a"/>`;
-            } else {
-                // Show fret number for higher positions
-                svg += `<text x="${marginLeft - 15}" y="${marginTop + fretSpacing / 2 + 4}" font-size="11" font-weight="bold" text-anchor="middle">${baseFret}fr</text>`;
-            }
-
-            // Fret lines
-            for (let i = 0; i <= numFrets; i++) {
-                const y = marginTop + i * fretSpacing;
-                svg += `<line x1="${marginLeft}" y1="${y}" x2="${marginLeft + (numStrings - 1) * stringSpacing}" y2="${y}" stroke="#333" stroke-width="${i === 0 ? 2 : 1}"/>`;
-            }
-
-            // String lines (left to right: string 4, 3, 2, 1)
-            for (let i = 0; i < numStrings; i++) {
-                const x = marginLeft + i * stringSpacing;
-                svg += `<line x1="${x}" y1="${marginTop}" x2="${x}" y2="${marginTop + numFrets * fretSpacing}" stroke="#333" stroke-width="1.5"/>`;
-            }
-
-            // String labels at top (4, 3, 2, 1 from left to right)
-            for (let i = 0; i < numStrings; i++) {
-                const x = marginLeft + i * stringSpacing;
-                const stringNum = 4 - i;
-                svg += `<text x="${x}" y="${marginTop - 20}" font-size="9" text-anchor="middle" fill="#666">${stringNum}</text>`;
-            }
-
-            // 5th string box (same size cell as main grid, flush with it, at fret 5-6)
-            // Only show if baseFret is 1 (low position chords)
-            if (baseFret === 1) {
-                const fret5Y = marginTop + 5 * fretSpacing; // Fret 5 wire
-                const fret6Y = marginTop + 6 * fretSpacing; // Fret 6 wire
-                const boxLeft = marginLeft - stringSpacing; // One cell width to the left
-
-                // Draw the box - same size as grid cells, flush with main grid
-                // Top line (fret 5)
-                svg += `<line x1="${boxLeft}" y1="${fret5Y}" x2="${marginLeft}" y2="${fret5Y}" stroke="#333" stroke-width="1"/>`;
-                // Bottom line (fret 6)
-                svg += `<line x1="${boxLeft}" y1="${fret6Y}" x2="${marginLeft}" y2="${fret6Y}" stroke="#333" stroke-width="1"/>`;
-                // Left edge
-                svg += `<line x1="${boxLeft}" y1="${fret5Y}" x2="${boxLeft}" y2="${fret6Y}" stroke="#333" stroke-width="1"/>`;
-
-                // Label for 5th string (centered in the box)
-                const boxCenterX = boxLeft + stringSpacing / 2;
-                svg += `<text x="${boxCenterX}" y="${fret5Y - 8}" font-size="8" text-anchor="middle" fill="#666">5</text>`;
-            }
-
-            // Finger positions and open/muted indicators
-            positions.forEach((pos, idx) => {
-                // idx 0 = string 4 (leftmost), idx 3 = string 1 (rightmost)
-                const x = marginLeft + idx * stringSpacing;
-
-                if (pos.muted) {
-                    // X for muted string
-                    svg += `<text x="${x}" y="${marginTop - 8}" font-size="12" font-weight="bold" text-anchor="middle" fill="#666">✕</text>`;
-                } else if (pos.fret === 0) {
-                    // Open string circle
-                    svg += `<circle cx="${x}" cy="${marginTop - 10}" r="5" fill="none" stroke="#333" stroke-width="1.5"/>`;
-                } else {
-                    // Fretted position
-                    const displayFret = pos.fret - baseFret + 1;
-                    if (displayFret >= 1 && displayFret <= numFrets) {
-                        const y = marginTop + (displayFret - 0.5) * fretSpacing;
-
-                        // Root notes get filled circle
-                        if (pos.isRoot) {
-                            svg += `<circle cx="${x}" cy="${y}" r="9" fill="#1a1a1a" stroke="#1a1a1a" stroke-width="1.5"/>`;
-                            if (pos.finger) {
-                                const fingerLabel = FINGER_NOTATION[fingerNotation][pos.finger] || pos.finger;
-                                svg += `<text x="${x}" y="${y + 3}" font-size="10" font-weight="bold" text-anchor="middle" fill="white">${fingerLabel}</text>`;
-                            }
-                        } else {
-                            svg += `<circle cx="${x}" cy="${y}" r="9" fill="white" stroke="#1a1a1a" stroke-width="1.5"/>`;
-                            if (pos.finger) {
-                                const fingerLabel = FINGER_NOTATION[fingerNotation][pos.finger] || pos.finger;
-                                svg += `<text x="${x}" y="${y + 3}" font-size="10" font-weight="bold" text-anchor="middle">${fingerLabel}</text>`;
-                            }
-                        }
-                    }
-                }
-            });
-
-            // Degree labels below strings
-            positions.forEach((pos, idx) => {
-                const x = marginLeft + idx * stringSpacing;
-                const y = marginTop + numFrets * fretSpacing + 15;
-
-                if (pos.degree && !pos.muted) {
-                    svg += `<text x="${x}" y="${y}" font-size="9" text-anchor="middle" fill="#666">${pos.degree}</text>`;
-                } else if (pos.muted) {
-                    svg += `<text x="${x}" y="${y}" font-size="9" text-anchor="middle" fill="#999">-</text>`;
-                }
-            });
-
-            svg += '</svg>';
-            return svg;
-        }
-
-        // ============================================
-        // TUNINGS DATA (24 tunings including 4-string)
-        // ============================================
-        const TUNINGS_DATA = {
-            // C-Based Tunings
-            "Minstrel Tuning": {
-                strings: ["f", "C", "F", "A", "c"],
-                family: "c-family",
-                description: "Historical",
-                referenceTuning: "Standard F",
-                deltaChanges: "From Standard F: 4th↓C",
-                capoPositions: { 0: "Not commonly capoed" },
-                songs: "Briggs manual, Coon Hunt",
-                changedStrings: [true, true, true, true, true],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "Naomi Weiss": {
-                strings: ["g", "C", "G", "A", "d"],
-                family: "c-family",
-                description: "Song-Specific",
-                referenceTuning: "Minstrel",
-                deltaChanges: "From Minstrel: 5th↑G, 3rd↑G, 2nd→A (same), 1st↑D",
-                capoPositions: { 2: "Key of D" },
-                songs: '"Naomi Weiss"',
-                changedStrings: [false, false, true, false, true],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "Little Birdie": {
-                strings: ["e", "C", "G", "A", "d"],
-                family: "c-family",
-                description: "Appalachian",
-                referenceTuning: "Naomi Weiss",
-                deltaChanges: "From Naomi Weiss: 5th↓E",
-                capoPositions: { 2: "Key of F# (uncommon)" },
-                songs: '"Little Birdie"',
-                changedStrings: [true, false, false, false, false],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "Hook-and-Line": {
-                strings: ["g", "C", "G", "c", "d"],
-                family: "c-family",
-                description: "Open C Variant",
-                referenceTuning: "Little Birdie",
-                deltaChanges: "From Little Birdie: 5th↑G, 2nd↑C",
-                capoPositions: { 2: "Key of D", 5: "Key of F" },
-                songs: '"Hook and Line"',
-                changedStrings: [true, false, false, true, false],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "Cackling Hen": {
-                strings: ["g", "C", "G", "c", "e"],
-                family: "c-family",
-                description: "Open C Major",
-                referenceTuning: "Hook-and-Line",
-                deltaChanges: "From Hook-and-Line: 1st↑E",
-                capoPositions: { 0: "Key of C", 2: "Key of D" },
-                songs: "Open C tuning, Irish/Celtic",
-                changedStrings: [false, false, false, false, true],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "Darling Cora": {
-                strings: ["g", "C", "G", "c", "c"],
-                family: "c-family",
-                description: "Drone Heavy (Triple C)",
-                referenceTuning: "Cackling Hen",
-                deltaChanges: "From Cackling Hen: 1st↓C",
-                capoPositions: { 2: "Key of D", 5: "Key of F" },
-                songs: '"Darling Cora"',
-                changedStrings: [false, false, false, false, true],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "Double C": {
-                strings: ["g", "C", "G", "c", "d"],
-                family: "c-family",
-                description: "Old-Time Standard",
-                referenceTuning: "Open G",
-                deltaChanges: "From Open G: 4th↓C, 2nd↑C",
-                capoPositions: { 2: "Key of D", 5: "Key of F" },
-                songs: "Wildwood Flower, Billy in the Lowground",
-                changedStrings: [false, true, false, true, false],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "Standard C": {
-                strings: ["g", "C", "G", "B", "d"],
-                family: "c-family",
-                description: "C Tuning for Old-Time",
-                referenceTuning: "Open G",
-                deltaChanges: "From Open G: 4th↓C",
-                capoPositions: { 2: "Key of D", 5: "Key of F" },
-                songs: "Old-time repertoire",
-                changedStrings: [false, true, false, false, false],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-
-            // D-Based Tunings
-            "Moonshiner": {
-                strings: ["g", "D", "G", "A", "d"],
-                family: "d-family",
-                description: "Modal/Dark",
-                referenceTuning: "Standard F",
-                deltaChanges: "From Standard F: 4th↑D, 3rd↑G, 2nd→A (same), 1st↑D",
-                capoPositions: { 2: "Key of A", 5: "Key of C" },
-                songs: '"Moonshiner"',
-                changedStrings: [false, true, false, true, true],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "German War": {
-                strings: ["g", "D", "G", "A", "f#"],
-                family: "d-family",
-                description: "Historical",
-                referenceTuning: "Moonshiner",
-                deltaChanges: "From Moonshiner: 1st↓F#",
-                capoPositions: { 2: "Key of A (modal)" },
-                songs: "German war songs",
-                changedStrings: [false, false, false, false, true],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "Reuben": {
-                strings: ["g", "D", "F#", "A", "f#"],
-                family: "d-family",
-                description: "Song-Specific",
-                referenceTuning: "German War",
-                deltaChanges: "From German War: 3rd↓F#",
-                capoPositions: { 2: "Key of A (modal)" },
-                songs: '"Reuben"',
-                changedStrings: [false, false, true, false, false],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "F Tuning": {
-                strings: ["f", "D", "G", "c", "d"],
-                family: "d-family",
-                description: "Cumberland Gap",
-                referenceTuning: "Reuben",
-                deltaChanges: "From Reuben: 5th↓F, 3rd↑G, 2nd↑C, 1st↑D",
-                capoPositions: { 2: "Key of G", 5: "Key of Bb" },
-                songs: "Cumberland Gap, Drunkard's Doom",
-                changedStrings: [true, false, true, true, true],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "East Virginia": {
-                strings: ["f", "F", "G", "c", "d"],
-                family: "d-family",
-                description: "George Gibson Style",
-                referenceTuning: "F Tuning",
-                deltaChanges: "From F Tuning: 4th↑F",
-                capoPositions: { 2: "Key of G", 5: "Key of Bb" },
-                songs: "George Gibson style",
-                changedStrings: [false, true, false, false, false],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-
-            // G-Based Tunings
-            "Standard (F)": {
-                strings: ["f", "C", "F", "A", "c"],
-                family: "g-family",
-                description: "Historical Reference",
-                referenceTuning: null,
-                deltaChanges: "(Reference tuning for historical context)",
-                capoPositions: { 2: "Key of G", 5: "Key of Bb" },
-                songs: "General playing, historical reference",
-                changedStrings: [false, false, false, false, false],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "Open G": {
-                strings: ["g", "D", "G", "B", "d"],
-                family: "g-family",
-                description: "Bluegrass Standard",
-                referenceTuning: null,
-                deltaChanges: "(Modern standard reference - most common)",
-                capoPositions: { 2: "Key of A", 5: "Key of C", 7: "Key of D" },
-                songs: "Bill Cheatum, Old Joe Clark, Cripple Creek",
-                changedStrings: [true, true, true, true, true],
-                featured: true,
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "Open G (Low G)": {
-                strings: ["G", "D", "G", "B", "d"],
-                family: "g-family",
-                description: "Open G with Low 5th",
-                referenceTuning: "Open G",
-                deltaChanges: "From Open G: 5th↓G (one octave lower)",
-                capoPositions: { 2: "Key of A", 5: "Key of C" },
-                songs: "Low drone sound",
-                changedStrings: [true, false, false, false, false],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "Open A": {
-                strings: ["a", "E", "A", "C#", "e"],
-                family: "g-family",
-                description: "High Tension",
-                referenceTuning: "Open G",
-                deltaChanges: "From Open G: all strings ↑2 frets (whole step)",
-                capoPositions: { 2: "Key of B", 5: "Key of D" },
-                songs: "Tunes in A key - requires light gauge strings!",
-                changedStrings: [true, true, true, true, true],
-                warning: true,
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "G Variant": {
-                strings: ["g", "D", "G", "A", "d"],
-                family: "g-family",
-                description: "Modal G",
-                referenceTuning: "Open G",
-                deltaChanges: "From Open G: 2nd↓A",
-                capoPositions: { 2: "Key of A", 5: "Key of C" },
-                songs: "Leather Britches, Texas, Frosty Morning",
-                changedStrings: [false, false, false, true, false],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "G Modal (Sawmill)": {
-                strings: ["g", "D", "G", "c", "d"],
-                family: "g-family",
-                description: "Mountain/Modal",
-                referenceTuning: "Open G",
-                deltaChanges: "From Open G: 2nd↑C",
-                capoPositions: { 2: "Key of A", 5: "Key of C" },
-                songs: "Modal tunes, F key songs",
-                changedStrings: [false, false, false, true, false],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "Open D": {
-                strings: ["a", "D", "F#", "A", "d"],
-                family: "g-family",
-                description: "Blues/Folk (Graveyard)",
-                referenceTuning: "Open G",
-                deltaChanges: "From Open G: all strings adjusted for D major",
-                capoPositions: { 2: "Key of E", 3: "Key of F", 5: "Key of G" },
-                songs: 'Most D key tunes, "Graveyard Tuning" - use light strings!',
-                changedStrings: [true, true, true, true, true],
-                warning: true,
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "D Modal": {
-                strings: ["a", "D", "G", "A", "d"],
-                family: "g-family",
-                description: "Celtic/Medieval",
-                referenceTuning: "Open D",
-                deltaChanges: "From Open D: 3rd↓G",
-                capoPositions: { 2: "Key of E (modal)", 5: "Key of G (modal)" },
-                songs: "D modal tunes, medieval tonality",
-                changedStrings: [false, false, true, false, false],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-            "D Tuning (Reno)": {
-                strings: ["a", "D", "G", "B", "e"],
-                family: "g-family",
-                description: "Don Reno Style",
-                referenceTuning: "Open G",
-                deltaChanges: "From Open G: 5th↑A, 1st↑E",
-                capoPositions: { 2: "Key of E", 5: "Key of G" },
-                songs: "Don Reno repertoire",
-                changedStrings: [true, false, false, false, true],
-                shortStringIndex: 0,
-                shortStringStartFret: 6
-            },
-
-            // Other / 4-String Tunings
-            "Plectrum C": {
-                strings: ["C", "G", "B", "d"],
-                family: "other-family",
-                description: "4-String Plectrum",
-                referenceTuning: null,
-                deltaChanges: "(4-string plectrum standard)",
-                capoPositions: { 0: "Key of C" },
-                songs: "Jazz, Dixieland",
-                changedStrings: [false, false, false, false],
-                shortStringIndex: null,
-                shortStringStartFret: null,
-                stringCount: 4
-            },
-            "Tenor (Standard)": {
-                strings: ["C", "G", "d", "a"],
-                family: "other-family",
-                description: "4-String Tenor CGDA",
-                referenceTuning: null,
-                deltaChanges: "(4-string tenor standard - fifths tuning)",
-                capoPositions: { 0: "Key of C/G" },
-                songs: "Irish, Jazz, Traditional",
-                changedStrings: [false, false, false, false],
-                shortStringIndex: null,
-                shortStringStartFret: null,
-                stringCount: 4
-            }
-        };
-
-        // ============================================
-        // STATE MANAGEMENT
-        // ============================================
-        let state = {
-            familyFilter: 'all',
-            capoPosition: 0,
-            fretCount: 15,
-            showNotes: true,
-            selectedFretboardTuning: 'Open G',
-            fretboardFamilyFilter: 'all',
-            useSolfege: false,
-            // Chord inversion state
-            showInversions: false,
-            inversionRootNote: 'G',
-            // Chord tab state
-            chordTuning: 'Open G',
-            chordKey: 'G',
-            chordTypeFilter: 'all',
-            fingerNotation: 'numbers',
-            chordViewMode: 'single'  // 'single' or 'grid' (all keys)
-        };
-
-        function loadState() {
-            const saved = localStorage.getItem('banjoTuningsState');
-            if (saved) {
-                try {
-                    const parsed = JSON.parse(saved);
-                    state = { ...state, ...parsed };
-                } catch (e) {
-                    console.error('Failed to load state:', e);
-                }
-            }
-        }
-
-        function saveState() {
-            localStorage.setItem('banjoTuningsState', JSON.stringify(state));
-        }
-
-        // ============================================
-        // NOTE FORMATTING
-        // ============================================
-        function parseNoteWithOctave(noteStr) {
-            // Handle format like "G4", "C#3", "g" (lowercase = high octave)
-            const match = noteStr.match(/^([A-Ga-g][#b]?)(\d)?$/);
-            if (!match) return null;
-
-            let note = match[1];
-            let octave = match[2] ? parseInt(match[2]) : null;
-
-            // Lowercase note = high octave (convention from Python script)
-            if (note === note.toLowerCase() && !octave) {
-                octave = 4; // High octave for 5th string
-            } else if (!octave) {
-                octave = 3; // Default octave
-            }
-
-            return { note: normalizeNote(note), octave };
-        }
-
-        function formatNoteForDisplay(noteStr) {
-            const parsed = parseNoteWithOctave(noteStr);
-            if (!parsed) return noteStr;
-
-            let note = displayNote(parsed.note);
-            // Convert to solfège if enabled
-            if (state.useSolfege) {
-                note = toSolfege(note);
-            }
-            // Replace # with sharp symbol, b with flat symbol
-            note = note.replace('#', '\u266F').replace('b', '\u266D');
-
-            return `${note}<sub>${parsed.octave}</sub>`;
-        }
-
-        function formatSimpleNote(note) {
-            // Format a simple note (no octave) for fretboard display
-            let displayed = displayNote(normalizeNote(note));
-            // Convert to solfège if enabled
-            if (state.useSolfege) {
-                displayed = toSolfege(displayed);
-            }
-            return displayed.replace('#', '\u266F').replace('b', '\u266D');
-        }
-
-        // ============================================
-        // RENDERING FUNCTIONS
-        // ============================================
-        function renderNoteSequence(strings) {
-            return strings.map(s => formatNoteForDisplay(s)).join(' ');
-        }
-
-        function renderCapoInfo(capoPositions) {
-            return Object.entries(capoPositions)
-                .map(([pos, key]) => pos === '0' ? key : `Capo ${pos} → ${key}`)
-                .join(' | ');
-        }
-
-        function renderTuningCard(name, tuning) {
-            const featuredBadge = tuning.featured ? ' &#11088;' : '';
-            const warningBadge = tuning.warning ? ' &#9888;' : '';
-            const stringCount = tuning.stringCount || 5;
-            const stringBadge = stringCount === 4 ? '<span class="string-count-badge">4-string</span>' : '';
-
-            // Render nut visual
-            const nutVisual = tuning.changedStrings.map((changed, i) => {
-                const stringNum = stringCount - i;
-                return `<div class="string-circle ${changed ? 'changed' : ''}">${stringNum}</div>`;
-            }).join('');
-
-            return `
-                <div class="tuning-card ${tuning.family}">
-                    <div class="tuning-header">
-                        <span class="tuning-name">${name}${featuredBadge}${warningBadge}${stringBadge}</span>
-                        <span class="genre-tag">${tuning.description}</span>
-                    </div>
-                    <div class="note-sequence">${renderNoteSequence(tuning.strings)}</div>
-                    <div class="nut-visual">
-                        ${nutVisual}
-                    </div>
-                    <div class="tuning-details">
-                        <div class="delta-line">&#916;: ${tuning.deltaChanges}</div>
-                        <div class="capo-line">${renderCapoInfo(tuning.capoPositions)}</div>
-                        <div class="songs-line">${tuning.songs}</div>
-                    </div>
-                </div>
-            `;
-        }
-
-        function renderTunings() {
-            const container = document.getElementById('tuningsContainer');
-            const families = {
-                'c-family': { name: 'C-Based Tunings', intro: 'These tunings center around C tonality, ranging from historical minstrel styles to modern old-time favorites. They create bright, modal sounds ideal for melodic playing.' },
-                'd-family': { name: 'D-Based Tunings', intro: 'D-based tunings create darker, more mysterious tonal landscapes. These tunings are favored for modal old-time pieces and offer rich harmonic possibilities.' },
-                'g-family': { name: 'G-Based Tunings', intro: 'The most common family including the standard bluegrass tuning. These tunings range from bright major chords to modal sounds, offering the widest versatility for different playing styles.' },
-                'other-family': { name: 'Other Tunings (4-String)', intro: '4-string banjo tunings for plectrum and tenor banjos. These instruments are commonly used in jazz, Dixieland, and Irish traditional music.' }
-            };
-
-            let html = '';
-
-            for (const [familyKey, familyInfo] of Object.entries(families)) {
-                if (state.familyFilter !== 'all' && state.familyFilter !== familyKey) continue;
-
-                const tuningsInFamily = Object.entries(TUNINGS_DATA)
-                    .filter(([_, t]) => t.family === familyKey);
-
-                if (tuningsInFamily.length === 0) continue;
-
-                html += `
-                    <div class="page-title">${familyInfo.name}</div>
-                    <div class="family-intro ${familyKey}-intro">
-                        <strong>${familyKey.toUpperCase().replace('-', ' ')}:</strong> ${familyInfo.intro}
-                    </div>
-                    <div class="tunings-grid">
-                        ${tuningsInFamily.map(([name, tuning]) => renderTuningCard(name, tuning)).join('')}
-                    </div>
-                `;
-            }
-
-            container.innerHTML = html;
-        }
-
-        // ============================================
-        // CHORD INVERSION CALCULATION
-        // ============================================
-
-        // Calculate all major chord inversions for a root note on strings 4-3-2-1
-        // Returns array of inversion objects with fret positions
-        function calculateMajorInversions(rootNote, tuningStrings, maxFret = 15) {
-            // Major chord intervals: Root=0, 3rd=4, 5th=7
-            const ROOT = 0, THIRD = 4, FIFTH = 7;
-
-            // Get pitch class of root note
-            const rootPitch = noteToPitchClass(rootNote);
-            const thirdPitch = (rootPitch + THIRD) % 12;
-            const fifthPitch = (rootPitch + FIFTH) % 12;
-
-            // For Open G tuning, strings 4-3-2-1 are indices 1,2,3,4 (D,G,B,D)
-            // We use the 4 main strings for chord shapes
-            const mainStrings = tuningStrings.slice(1, 5); // D, G, B, D
-            const stringOpenPitches = mainStrings.map(s => noteToPitchClass(normalizeNote(s)));
-
-            // Find fret for a target pitch on a given string
-            function findFret(stringPitch, targetPitch, startFret = 0) {
-                const diff = (targetPitch - stringPitch + 12) % 12;
-                let fret = diff;
-                if (fret < startFret) fret += 12;
-                return fret;
-            }
-
-            // Three inversion patterns (intervals on strings 4-3-2-1):
-            // Root Form: 1-3-5-1 (root on string 4)
-            // 1st Inversion: 3-5-1-3 (3rd on string 4)
-            // 2nd Inversion: 5-1-3-5 (5th on string 4)
-            const inversionPatterns = [
-                { name: 'Root Form', type: 'root', intervals: [ROOT, THIRD, FIFTH, ROOT], bassNote: 'Root', color: '#3366cc', label: '1 - 3 - 5 - 1' },
-                { name: '1st Inversion', type: 'first', intervals: [THIRD, FIFTH, ROOT, THIRD], bassNote: '3rd', color: '#cc3333', label: '3 - 5 - 1 - 3' },
-                { name: '2nd Inversion', type: 'second', intervals: [FIFTH, ROOT, THIRD, FIFTH], bassNote: '5th', color: '#cc9900', label: '5 - 1 - 3 - 5' }
-            ];
-
-            const inversions = [];
-
-            // Find all occurrences of each inversion pattern
-            for (const pattern of inversionPatterns) {
-                // Calculate target pitches for each string
-                const targetPitches = pattern.intervals.map(interval => (rootPitch + interval) % 12);
-
-                // Find base fret positions (lowest occurrence)
-                const baseFrets = [];
-                for (let i = 0; i < 4; i++) {
-                    baseFrets.push(findFret(stringOpenPitches[i], targetPitches[i], 0));
-                }
-
-                // Find the minimum fret to normalize the shape
-                const minFret = Math.min(...baseFrets.filter(f => f > 0));
-                const maxFretInShape = Math.max(...baseFrets);
-
-                // Find all occurrences within the fret range
-                for (let offset = 0; offset <= maxFret; offset += 12) {
-                    const frets = baseFrets.map(f => f === 0 ? 0 : f + offset - (minFret > 0 ? minFret - baseFrets.find(bf => bf === minFret) : 0));
-                    const adjustedFrets = baseFrets.map(f => {
-                        let adjusted = f + offset;
-                        // Handle open strings (fret 0)
-                        if (f === 0 && offset === 0) return 0;
-                        if (f === 0 && offset > 0) adjusted = 12 + offset;
-                        while (adjusted > maxFret + 5) adjusted -= 12;
-                        return adjusted;
-                    });
-
-                    // Check if this shape is within the displayable range
-                    const shapeFrets = adjustedFrets.filter(f => f >= 0);
-                    if (shapeFrets.length > 0 && Math.min(...shapeFrets) <= maxFret) {
-                        inversions.push({
-                            ...pattern,
-                            frets: adjustedFrets,
-                            baseFret: Math.min(...adjustedFrets.filter(f => f > 0)) || 0
-                        });
-                    }
-                }
-            }
-
-            // Sort by base fret position
-            inversions.sort((a, b) => a.baseFret - b.baseFret);
-
-            return inversions;
-        }
-
-        // Render chord inversions on fretboard SVG
-        function renderInversionsOnFretboard(svg, rootNote, tuning, fbLeft, fbTop, stringSpacing, fretHeight, numFrets, svgWidth) {
-            const inversions = calculateMajorInversions(rootNote, tuning.strings, numFrets);
-            const displayedNote = displayNote(rootNote);
-
-            // Filter inversions that are fully visible
-            const visibleInversions = inversions.filter(inv => {
-                const maxFretInShape = Math.max(...inv.frets);
-                return maxFretInShape <= numFrets && inv.baseFret >= 1;
-            });
-
-            // Render each inversion
-            visibleInversions.forEach((inv, idx) => {
-                // Calculate positions (strings 4-3-2-1 are at indices 1,2,3,4 in the display)
-                const positions = [];
-                for (let i = 0; i < 4; i++) {
-                    const stringIndex = i + 1; // Skip 5th string (index 0)
-                    const x = fbLeft + stringIndex * stringSpacing;
-                    const fret = inv.frets[i];
-                    const y = fbTop + (fret - 0.5) * fretHeight;
-                    positions.push({ x, y, fret, stringIndex });
-                }
-
-                // Draw colored overlay connecting the notes (triangle or polygon)
-                const validPositions = positions.filter(p => p.fret > 0 && p.fret <= numFrets);
-                if (validPositions.length >= 3) {
-                    // Create polygon points
-                    const points = validPositions.map(p => `${p.x},${p.y}`).join(' ');
-                    svg += `<polygon points="${points}" fill="${inv.color}" fill-opacity="0.25" stroke="${inv.color}" stroke-width="2"/>`;
-                }
-
-                // Draw dots on chord positions
-                positions.forEach(pos => {
-                    if (pos.fret > 0 && pos.fret <= numFrets) {
-                        svg += `<circle cx="${pos.x}" cy="${pos.y}" r="12" fill="${inv.color}" fill-opacity="0.8" stroke="white" stroke-width="2"/>`;
-                    }
-                });
-
-                // Annotation box on the right side
-                const annotationX = fbLeft + 5 * stringSpacing + 30;
-                const annotationY = fbTop + (inv.baseFret - 0.5) * fretHeight;
-
-                if (inv.baseFret <= numFrets - 2) {
-                    // Background for annotation
-                    svg += `<rect x="${annotationX}" y="${annotationY - 30}" width="140" height="70" fill="white" stroke="${inv.color}" stroke-width="2" rx="5"/>`;
-
-                    // Chord name and inversion type
-                    svg += `<text x="${annotationX + 70}" y="${annotationY - 12}" text-anchor="middle" font-size="12" font-weight="bold" fill="${inv.color}">"${displayedNote}" CHORD</text>`;
-                    svg += `<text x="${annotationX + 70}" y="${annotationY + 5}" text-anchor="middle" font-size="11" font-weight="bold" fill="#333">${inv.name.toUpperCase()}</text>`;
-
-                    // Bass note info
-                    svg += `<text x="${annotationX + 70}" y="${annotationY + 20}" text-anchor="middle" font-size="10" fill="#666">${inv.bassNote} is bass note</text>`;
-
-                    // Interval structure
-                    svg += `<text x="${annotationX + 70}" y="${annotationY + 35}" text-anchor="middle" font-size="10" fill="#666">[${inv.label}]</text>`;
-                }
-            });
-
-            // Draw 5-fret rule connectors between consecutive inversions
-            for (let i = 0; i < visibleInversions.length - 1; i++) {
-                const current = visibleInversions[i];
-                const next = visibleInversions[i + 1];
-
-                // The rule: 3rd string of current -> 4th string of next (5 frets higher)
-                // String 3 is at index 2 (0-indexed in our positions), String 4 is at index 1
-                const fromStringIdx = 2; // 3rd string (G string)
-                const toStringIdx = 1;   // 4th string (D string)
-
-                const fromX = fbLeft + (fromStringIdx + 1) * stringSpacing;
-                const fromY = fbTop + (current.frets[fromStringIdx] - 0.5) * fretHeight;
-                const toX = fbLeft + (toStringIdx + 1) * stringSpacing;
-                const toY = fbTop + (next.frets[toStringIdx] - 0.5) * fretHeight;
-
-                if (current.frets[fromStringIdx] <= numFrets && next.frets[toStringIdx] <= numFrets) {
-                    // Draw arrow
-                    svg += `<line x1="${fromX}" y1="${fromY}" x2="${toX}" y2="${toY}" stroke="#cc0000" stroke-width="3" marker-end="url(#arrowhead)"/>`;
-
-                    // Label showing "5 frets"
-                    const midX = (fromX + toX) / 2 - 25;
-                    const midY = (fromY + toY) / 2;
-                    svg += `<text x="${midX}" y="${midY}" font-size="10" font-weight="bold" fill="#cc0000">5 frets</text>`;
-                }
-            }
-
-            // Add cycling indicator at bottom
-            if (visibleInversions.length > 0) {
-                const bottomY = fbTop + numFrets * fretHeight + 10;
-                svg += `<text x="${fbLeft + 2.5 * stringSpacing}" y="${bottomY}" text-anchor="middle" font-size="11" fill="#666">↓ Inversions keep cycling ↓</text>`;
-            }
-
-            return svg;
-        }
-
-        // ============================================
-        // FRETBOARD SVG GENERATOR (Enhanced from Python)
-        // ============================================
-        function generateFretboardSVG(tuningName) {
-            const tuning = TUNINGS_DATA[tuningName];
-            if (!tuning) return '';
-
-            const numFrets = state.fretCount;
-            const capo = state.capoPosition;
-            const showNotes = state.showNotes;
-
-            const strings = tuning.strings;
-            const numStrings = strings.length;
-            const shortStringIdx = tuning.shortStringIndex;
-            const shortStringStart = tuning.shortStringStartFret;
-
-            // Determine which strings should be lowercase (first occurrence of duplicate notes)
-            // e.g., for tuning with notes F C F A C, the first F and first C should be lowercase: f c F A C
-            const lowercaseIndices = new Set();
-            const seenNotes = new Map(); // normalized note -> first index
-            for (let i = 0; i < strings.length; i++) {
-                const normalized = normalizeNote(strings[i]).toUpperCase();
-                if (!seenNotes.has(normalized)) {
-                    seenNotes.set(normalized, i);
-                } else {
-                    // This note has appeared before, mark the first occurrence as lowercase
-                    lowercaseIndices.add(seenNotes.get(normalized));
-                }
-            }
-
-            // SVG dimensions
-            const marginLeft = 100;
-            const marginRight = 150;
-            const marginTop = capo > 0 ? 160 : 140;  // Extra space for effective tuning display
-            const marginBottom = 80;
-            const stringSpacing = 55;
-            const fretHeight = 50;
-
-            const fretboardWidth = (numStrings - 1) * stringSpacing;
-            const fretboardHeight = numFrets * fretHeight;
-            const svgWidth = marginLeft + fretboardWidth + marginRight;
-            const svgHeight = marginTop + fretboardHeight + marginBottom;
-
-            const fbLeft = marginLeft;
-            const fbTop = marginTop;
-            const fbRight = fbLeft + fretboardWidth;
-            const bottomY = fbTop + fretboardHeight;
-
-            // Fret markers
-            const markerFrets = [3, 5, 7, 10, 12, 15, 17];
-            const doubleMarkerFrets = [12];
-
-            let svg = `<svg class="fretboard-svg" width="${svgWidth}" height="${svgHeight}" xmlns="http://www.w3.org/2000/svg">`;
-
-            // Styles and definitions
-            svg += `
-            <defs>
-                <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
-                    <polygon points="0 0, 10 3.5, 0 7" fill="#cc0000"/>
-                </marker>
-            </defs>
-            <style>
-                .title { font-family: Georgia, serif; font-size: 24px; font-weight: bold; fill: #000; }
-                .subtitle { font-family: Georgia, serif; font-size: 16px; font-style: italic; fill: #333; }
-                .string-label { font-family: 'Courier New', monospace; font-size: 18px; font-weight: bold; fill: #000; }
-                .fret-number { font-family: Arial, sans-serif; font-size: 16px; font-weight: bold; fill: #333; }
-                .note { font-family: Arial, sans-serif; font-size: 12px; font-weight: bold; fill: #000; }
-                .note-circle { fill: white; stroke: #000; stroke-width: 1.5; }
-                .note-circle-natural { fill: white; stroke: #000; stroke-width: 4; }
-                .fret-line { stroke: #000; stroke-width: 2; }
-                .string-line { stroke: #444; stroke-width: 1.5; }
-                .nut { stroke: #000; stroke-width: 6; }
-                .fret-marker { fill: #ddd; stroke: none; }
-                .peg { fill: #333; stroke: #000; stroke-width: 1; }
-                .ghost-string { stroke: #999; stroke-width: 1; stroke-dasharray: 3,3; }
-                .fret-marker-symbol { fill: #000; stroke: none; }
-            </style>`;
-
-            // Background
-            svg += `<rect width="100%" height="100%" fill="white"/>`;
-
-            // Title - use lowercase for first occurrence of duplicate notes (e.g., fCFAC -> fcFAC)
-            const tuningStr = strings.map((s, idx) => {
-                let note = displayNote(normalizeNote(s));
-                // Convert to solfège if enabled
-                if (state.useSolfege) {
-                    note = toSolfege(note);
-                }
-                // Use lowercase if this index is marked as first occurrence of a duplicate
-                return lowercaseIndices.has(idx) ? note.toLowerCase() : note;
-            }).join('');
-
-            // Calculate effective tuning with capo
-            let effectiveTuningStr = '';
-            if (capo > 0) {
-                effectiveTuningStr = strings.map((s, idx) => {
-                    // For short string, calculate from its actual starting point
-                    let effectiveCapo = capo;
-                    if (idx === shortStringIdx && shortStringStart !== null) {
-                        // Short string: capo only affects it if capo >= shortStringStart
-                        if (capo < shortStringStart) {
-                            effectiveCapo = 0; // Capo doesn't affect short string
-                        } else {
-                            effectiveCapo = capo - (shortStringStart - 1);
-                        }
-                    }
-                    const transposedNote = getNoteAtFret(s, effectiveCapo);
-                    let note = displayNote(normalizeNote(transposedNote));
-                    if (state.useSolfege) {
-                        note = toSolfege(note);
-                    }
-                    return lowercaseIndices.has(idx) ? note.toLowerCase() : note;
-                }).join('');
-            }
-
-            svg += `<text x="${svgWidth / 2}" y="45" class="title" text-anchor="middle">Banjo Fretboard: ${tuningName}</text>`;
-
-            // Subtitle with effective tuning when capo is applied
-            if (capo > 0) {
-                svg += `<text x="${svgWidth / 2}" y="70" class="subtitle" text-anchor="middle">Open Tuning: ${tuningStr} — ${tuning.description}</text>`;
-                svg += `<text x="${svgWidth / 2}" y="90" class="subtitle" text-anchor="middle" style="fill: #c00; font-weight: bold;">Capo ${capo}: Effective Tuning: ${effectiveTuningStr}</text>`;
-            } else {
-                svg += `<text x="${svgWidth / 2}" y="70" class="subtitle" text-anchor="middle">Tuning: ${tuningStr} — ${tuning.description}</text>`;
-            }
-
-            // Fret markers (on fretboard face)
-            for (const fret of markerFrets) {
-                if (fret <= numFrets) {
-                    const y = fbTop + (fret - 0.5) * fretHeight;
-                    if (doubleMarkerFrets.includes(fret)) {
-                        svg += `<circle cx="${fbLeft + fretboardWidth/2 - 15}" cy="${y}" r="7" class="fret-marker"/>`;
-                        svg += `<circle cx="${fbLeft + fretboardWidth/2 + 15}" cy="${y}" r="7" class="fret-marker"/>`;
-                    } else {
-                        svg += `<circle cx="${fbLeft + fretboardWidth/2}" cy="${y}" r="7" class="fret-marker"/>`;
-                    }
-                }
-            }
-
-            // Nut
-            if (shortStringIdx !== null && shortStringStart !== null) {
-                const nutStartX = fbLeft + stringSpacing - 10;
-                svg += `<line x1="${nutStartX}" y1="${fbTop}" x2="${fbRight + 10}" y2="${fbTop}" class="nut"/>`;
-            } else {
-                svg += `<line x1="${fbLeft - 10}" y1="${fbTop}" x2="${fbRight + 10}" y2="${fbTop}" class="nut"/>`;
-            }
-
-            // Fret lines with ordinal labels on right
-            for (let fret = 1; fret <= numFrets; fret++) {
-                const y = fbTop + fret * fretHeight;
-
-                let lineStartX, lineEndX;
-                if (shortStringIdx !== null && shortStringStart !== null && fret < shortStringStart) {
-                    lineStartX = fbLeft + stringSpacing - 10;
-                    lineEndX = fbRight + 10;
-                } else {
-                    lineStartX = fbLeft - 10;
-                    lineEndX = fbRight + 10;
-                }
-
-                // Extend line for marker frets
-                if (markerFrets.includes(fret)) {
-                    lineEndX += 20;
-                    if (fret === 5) {
-                        // 5th fret extends to the left edge (where 5th string starts)
-                        lineStartX = 90;
-                    }
-                }
-
-                svg += `<line x1="${lineStartX}" y1="${y}" x2="${lineEndX}" y2="${y}" class="fret-line"/>`;
-
-                // Ordinal fret labels on right
-                if (markerFrets.includes(fret)) {
-                    const ordinal = fret + (fret === 1 ? 'st' : fret === 2 ? 'nd' : fret === 3 ? 'rd' : 'th') + ' fret';
-                    const textX = fbRight + 70;
-                    svg += `<text x="${textX}" y="${y}" class="fret-number" text-anchor="middle" dominant-baseline="middle">${ordinal}</text>`;
-
-                    // Marker symbols
-                    const symbolX = textX + 50;
-                    if (fret === 5) {
-                        // Star for 5th fret
-                        const starPoints = `${symbolX},${y-7} ${symbolX+2},${y-2} ${symbolX+7},${y-2} ${symbolX+3},${y+1} ${symbolX+5},${y+6} ${symbolX},${y+3} ${symbolX-5},${y+6} ${symbolX-3},${y+1} ${symbolX-7},${y-2} ${symbolX-2},${y-2}`;
-                        svg += `<polygon points="${starPoints}" class="fret-marker-symbol"/>`;
-                    } else if (fret === 12) {
-                        // Double circles
-                        svg += `<circle cx="${symbolX - 8}" cy="${y}" r="6" class="fret-marker-symbol"/>`;
-                        svg += `<circle cx="${symbolX + 8}" cy="${y}" r="6" class="fret-marker-symbol"/>`;
-                    } else {
-                        // Single circle
-                        svg += `<circle cx="${symbolX}" cy="${y}" r="6" class="fret-marker-symbol"/>`;
-                    }
-                }
-            }
-
-            // Neck edges with diagonal for short string
-            if (shortStringIdx !== null && shortStringStart !== null) {
-                const nutLeftX = fbLeft + stringSpacing - 10;
-                const fret4Y = fbTop + 4 * fretHeight;
-                const fret5Y = fbTop + 5 * fretHeight;
-                const leftEdgeX = fbLeft - 10;
-
-                // Ghost string line (dotted)
-                svg += `<line x1="${fbLeft}" y1="${fbTop}" x2="${fbLeft}" y2="${fret5Y - fretHeight}" class="ghost-string"/>`;
-
-                // Polyline neck edge
-                svg += `<polyline points="${nutLeftX},${fbTop} ${nutLeftX},${fret4Y} ${leftEdgeX},${fret5Y} ${leftEdgeX},${bottomY}" style="stroke:#000; stroke-width:3; fill:none;"/>`;
-                svg += `<line x1="${fbRight + 10}" y1="${fbTop}" x2="${fbRight + 10}" y2="${bottomY}" style="stroke:#000; stroke-width:3;"/>`;
-                svg += `<line x1="${leftEdgeX}" y1="${bottomY}" x2="${fbRight + 10}" y2="${bottomY}" style="stroke:#000; stroke-width:3;"/>`;
-            } else {
-                // 4-string: rectangular edges
-                svg += `<line x1="${fbLeft - 10}" y1="${fbTop}" x2="${fbLeft - 10}" y2="${bottomY}" style="stroke:#000; stroke-width:3;"/>`;
-                svg += `<line x1="${fbRight + 10}" y1="${fbTop}" x2="${fbRight + 10}" y2="${bottomY}" style="stroke:#000; stroke-width:3;"/>`;
-                svg += `<line x1="${fbLeft - 10}" y1="${bottomY}" x2="${fbRight + 10}" y2="${bottomY}" style="stroke:#000; stroke-width:3;"/>`;
-            }
-
-            // String lines
-            for (let i = 0; i < numStrings; i++) {
-                const x = fbLeft + i * stringSpacing;
-
-                if (i === shortStringIdx && shortStringStart !== null) {
-                    const startY = fbTop + (shortStringStart - 1) * fretHeight;
-                    svg += `<line x1="${x}" y1="${startY}" x2="${x}" y2="${bottomY}" class="string-line" style="stroke-width:2;"/>`;
-
-                    // Peg indicator
-                    const pegY = startY - 50;
-                    const pegHeadX = fbLeft - 10;
-                    const pegShaftStartX = fbLeft + 30;
-                    svg += `<rect x="${pegHeadX}" y="${pegY - 5}" width="${pegShaftStartX - pegHeadX}" height="8" class="peg" rx="2"/>`;
-                    svg += `<circle cx="${pegHeadX}" cy="${pegY}" r="10" class="peg"/>`;
-                    svg += `<circle cx="${pegHeadX}" cy="${pegY}" r="4" fill="white"/>`;
-                } else {
-                    svg += `<line x1="${x}" y1="${fbTop}" x2="${x}" y2="${bottomY}" class="string-line"/>`;
-                }
-            }
-
-            // String labels at top (lowercase for first occurrence of duplicate notes)
-            for (let i = 0; i < numStrings; i++) {
-                const x = fbLeft + i * stringSpacing;
-                const stringNumber = numStrings - i;
-                const note = strings[i];
-
-                // Use lowercase if this is the first occurrence of a duplicate note
-                const baseNote = normalizeNote(note);
-                let displayedBase = displayNote(baseNote);
-                // Convert to solfège if enabled
-                if (state.useSolfege) {
-                    displayedBase = toSolfege(displayedBase);
-                }
-                // Apply lowercase if marked, then replace symbols
-                let displayed = lowercaseIndices.has(i) ? displayedBase.toLowerCase() : displayedBase;
-                displayed = displayed.replace('#', '\u266F').replace('b', '\u266D');
-
-                const numberY = fbTop - 35;
-                const labelY = fbTop - 12;
-
-                svg += `<text x="${x}" y="${numberY}" class="fret-number" text-anchor="middle">${stringNumber}</text>`;
-                svg += `<text x="${x}" y="${labelY}" class="string-label" text-anchor="middle">${displayed}</text>`;
-            }
-
-            // Notes at each fret position
-            if (showNotes) {
-                for (let fret = 1; fret <= numFrets; fret++) {
-                    const y = fbTop + (fret - 0.5) * fretHeight;
-
-                    // Check if this fret is above (behind) the capo
-                    const isBehindCapo = capo > 0 && fret < capo;
-
-                    for (let i = 0; i < numStrings; i++) {
-                        const x = fbLeft + i * stringSpacing;
-                        const openNote = strings[i];
-
-                        // Handle short string
-                        if (i === shortStringIdx && shortStringStart !== null) {
-                            if (fret < shortStringStart) {
-                                continue; // No notes in empty space
-                            }
-                            // Calculate effective fret for short string
-                            const effectiveFret = fret - (shortStringStart - 1);
-                            const note = getNoteAtFret(openNote, effectiveFret);
-                            const displayedNote = formatSimpleNote(note);
-
-                            // Gray out notes behind capo, use 8-ball style for natural notes
-                            const isNatural = isNaturalNote(note);
-                            const circleClass = isNatural ? 'note-circle-natural' : 'note-circle';
-                            const circleStyle = isBehindCapo ? 'fill: #e0e0e0; stroke: #bbb;' : '';
-                            const textStyle = isBehindCapo ? 'fill: #aaa;' : '';
-
-                            svg += `<circle cx="${x}" cy="${y}" r="16" class="${circleClass}" style="${circleStyle}"/>`;
-                            svg += `<text x="${x}" y="${y + 5}" class="note" text-anchor="middle" style="${textStyle}">${displayedNote}</text>`;
-                        } else {
-                            const note = getNoteAtFret(openNote, fret);
-                            const displayedNote = formatSimpleNote(note);
-
-                            // Gray out notes behind capo, use 8-ball style for natural notes
-                            const isNatural = isNaturalNote(note);
-                            const circleClass = isNatural ? 'note-circle-natural' : 'note-circle';
-                            const circleStyle = isBehindCapo ? 'fill: #e0e0e0; stroke: #bbb;' : '';
-                            const textStyle = isBehindCapo ? 'fill: #aaa;' : '';
-
-                            svg += `<circle cx="${x}" cy="${y}" r="16" class="${circleClass}" style="${circleStyle}"/>`;
-                            svg += `<text x="${x}" y="${y + 5}" class="note" text-anchor="middle" style="${textStyle}">${displayedNote}</text>`;
-                        }
-                    }
-                }
-            }
-
-            // Capo indicator
-            if (capo > 0) {
-                const capoY = fbTop + (capo - 0.5) * fretHeight;
-                svg += `<rect x="${fbLeft - 15}" y="${capoY - 8}" width="${fretboardWidth + 30}" height="16" fill="#333" rx="4" opacity="0.9"/>`;
-                svg += `<text x="${fbLeft + fretboardWidth/2}" y="${capoY + 4}" text-anchor="middle" font-size="10" fill="white" font-weight="bold">CAPO ${capo}</text>`;
-            }
-
-            // Chord inversions overlay (if enabled)
-            if (state.showInversions && numStrings >= 5) {
-                svg = renderInversionsOnFretboard(svg, state.inversionRootNote, tuning, fbLeft, fbTop, stringSpacing, fretHeight, numFrets, svgWidth);
-            }
-
-            // Footer
-            const footerY = fbTop + fretboardHeight + 30;
-            svg += `<rect x="${marginLeft-50}" y="${footerY}" width="${fretboardWidth+150}" height="30" rx="5" fill="#f5f5f5" stroke="#ccc"/>`;
-            const footerLine1 = shortStringStart
-                ? `String 1 (far right) = 1st string • 5th string starts at fret ${shortStringStart}`
-                : `String 1 (far right) = 1st string • ${numStrings}-string banjo`;
-            svg += `<text x="${svgWidth/2}" y="${footerY + 12}" text-anchor="middle" font-size="10" fill="#555">${footerLine1}</text>`;
-            svg += `<text x="${svgWidth/2}" y="${footerY + 24}" text-anchor="middle" font-size="10" fill="#555">Fret markers: 3,5,7,10,12,15,17 • Peg indicates 5th string tuning point</text>`;
-
-            svg += '</svg>';
-            return svg;
-        }
-
-        function renderFretboard() {
-            const container = document.getElementById('fretboardContainer');
-            container.innerHTML = generateFretboardSVG(state.selectedFretboardTuning);
-        }
-
-        function populateTuningList() {
-            const list = document.getElementById('tuningList');
-            const familyFilter = state.fretboardFamilyFilter || 'all';
-
-            const families = {
-                'c-family': 'C-Based Tunings',
-                'd-family': 'D-Based Tunings',
-                'g-family': 'G-Based Tunings',
-                'other-family': 'Other (4-String)'
-            };
-
-            let html = '';
-
-            for (const [familyKey, familyName] of Object.entries(families)) {
-                if (familyFilter !== 'all' && familyFilter !== familyKey) continue;
-
-                const tuningsInFamily = Object.entries(TUNINGS_DATA)
-                    .filter(([_, t]) => t.family === familyKey);
-
-                if (tuningsInFamily.length === 0) continue;
-
-                // Add group header if showing all families
-                if (familyFilter === 'all') {
-                    html += `<div class="tuning-list-group">${familyName}</div>`;
-                }
-
-                for (const [name, tuning] of tuningsInFamily) {
-                    const isSelected = name === state.selectedFretboardTuning;
-
-                    // Determine which indices should be lowercase (first occurrence of duplicates)
-                    const lowercaseIdx = new Set();
-                    const seenNotes = new Map();
-                    for (let i = 0; i < tuning.strings.length; i++) {
-                        const normalized = normalizeNote(tuning.strings[i]).toUpperCase();
-                        if (!seenNotes.has(normalized)) {
-                            seenNotes.set(normalized, i);
-                        } else {
-                            lowercaseIdx.add(seenNotes.get(normalized));
-                        }
-                    }
-
-                    const tuningStr = tuning.strings.map((s, idx) => {
-                        let note = displayNote(normalizeNote(s));
-                        // Convert to solfège if enabled
-                        if (state.useSolfege) {
-                            note = toSolfege(note);
-                        }
-                        return lowercaseIdx.has(idx) ? note.toLowerCase() : note;
-                    }).join(' ');
-
-                    html += `
-                        <div class="tuning-list-item ${tuning.family} ${isSelected ? 'selected' : ''}"
-                             data-tuning="${name}">
-                            <span class="tuning-item-name">${name}</span>
-                            <span class="tuning-item-notes">${tuningStr}</span>
-                        </div>
-                    `;
-                }
-            }
-
-            list.innerHTML = html;
-
-            // Add click handlers
-            list.querySelectorAll('.tuning-list-item').forEach(item => {
-                item.addEventListener('click', () => {
-                    const tuningName = item.dataset.tuning;
-                    state.selectedFretboardTuning = tuningName;
-                    saveState();
-                    populateTuningList(); // Re-render to update selection
-                    renderFretboard();
-                });
-            });
-        }
-
-        // ============================================
-        // EVENT HANDLERS
-        // ============================================
-        function setupEventListeners() {
-            document.getElementById('familyFilter').addEventListener('change', (e) => {
-                state.familyFilter = e.target.value;
-                saveState();
-                renderTunings();
-            });
-
-            document.getElementById('capoPosition').addEventListener('change', (e) => {
-                state.capoPosition = parseInt(e.target.value) || 0;
-                saveState();
-                renderTunings();
-                renderFretboard();
-            });
-
-            document.getElementById('fretCount').addEventListener('change', (e) => {
-                state.fretCount = parseInt(e.target.value);
-                saveState();
-                renderFretboard();
-            });
-
-            document.getElementById('showNotesToggle').addEventListener('change', (e) => {
-                state.showNotes = e.target.checked;
-                saveState();
-                renderFretboard();
-            });
-
-            document.getElementById('solfegeToggle').addEventListener('change', (e) => {
-                state.useSolfege = e.target.checked;
-                saveState();
-                renderTunings();
-                renderFretboard();
-                populateTuningList();
-            });
-
-            // Chord inversion controls
-            document.getElementById('showInversionsToggle').addEventListener('change', (e) => {
-                state.showInversions = e.target.checked;
-                saveState();
-                renderFretboard();
-            });
-
-            document.getElementById('inversionRootNote').addEventListener('change', (e) => {
-                state.inversionRootNote = e.target.value;
-                saveState();
-                renderFretboard();
-            });
-
-            // Fretboard family filter
-            document.getElementById('fretboardFamilyFilter').addEventListener('change', (e) => {
-                state.fretboardFamilyFilter = e.target.value;
-                saveState();
-                populateTuningList();
-            });
-
-            document.querySelectorAll('.tab').forEach(tab => {
-                tab.addEventListener('click', (e) => {
-                    const tabName = e.target.dataset.tab;
-                    document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
-                    document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
-                    e.target.classList.add('active');
-                    document.getElementById(`${tabName}-tab`).classList.add('active');
-                });
-            });
-        }
-
-        function resetSettings() {
-            state = {
-                familyFilter: 'all',
-                capoPosition: 0,
-                fretCount: 15,
-                showNotes: true,
-                selectedFretboardTuning: 'Open G',
-                fretboardFamilyFilter: 'all',
-                useSolfege: false,
-                showInversions: false,
-                inversionRootNote: 'G',
-                chordTuning: 'Open G',
-                chordKey: 'G',
-                chordTypeFilter: 'all',
-                fingerNotation: 'numbers',
-                chordViewMode: 'single'
-            };
-            saveState();
-            applyStateToUI();
-            populateTuningList();
-            renderTunings();
-            renderFretboard();
-            initChordTab();
-        }
-
-        function applyStateToUI() {
-            document.getElementById('familyFilter').value = state.familyFilter;
-            document.getElementById('capoPosition').value = state.capoPosition;
-            document.getElementById('fretCount').value = state.fretCount;
-            document.getElementById('showNotesToggle').checked = state.showNotes;
-            document.getElementById('solfegeToggle').checked = state.useSolfege || false;
-            document.getElementById('fretboardFamilyFilter').value = state.fretboardFamilyFilter || 'all';
-            document.getElementById('showInversionsToggle').checked = state.showInversions || false;
-            document.getElementById('inversionRootNote').value = state.inversionRootNote || 'G';
-
-            // Chord tab UI
-            const chordTuningSelect = document.getElementById('chordTuningSelect');
-            if (chordTuningSelect) {
-                chordTuningSelect.value = state.chordTuning || 'Open G';
-            }
-
-            // Finger notation radio
-            const fingerRadio = document.querySelector(`input[name="fingerNotation"][value="${state.fingerNotation || 'numbers'}"]`);
-            if (fingerRadio) {
-                fingerRadio.checked = true;
-            }
-        }
-
-        // ============================================
-        // CHORD TAB FUNCTIONS
-        // ============================================
-        function populateChordTuningSelect() {
-            const select = document.getElementById('chordTuningSelect');
-            if (!select) return;
-
-            select.innerHTML = '';
-
-            // Group tunings by family
-            const families = {
-                'g-family': 'G-Based',
-                'c-family': 'C-Based',
-                'd-family': 'D-Based',
-                'other-family': 'Other (4-String)'
-            };
-
-            for (const [familyKey, familyName] of Object.entries(families)) {
-                const group = document.createElement('optgroup');
-                group.label = familyName;
-
-                for (const [name, data] of Object.entries(TUNINGS_DATA)) {
-                    if (data.family === familyKey) {
-                        const option = document.createElement('option');
-                        option.value = name;
-                        option.textContent = name;
-                        if (name === state.chordTuning) {
-                            option.selected = true;
-                        }
-                        group.appendChild(option);
-                    }
-                }
-
-                select.appendChild(group);
-            }
-        }
-
-        function populateKeyButtons() {
-            const container = document.getElementById('keyButtons');
-            if (!container) return;
-
-            container.innerHTML = CHORD_KEYS.map(key => {
-                const isActive = state.chordKey === key.root ? 'active' : '';
-                return `<button class="key-btn ${isActive}" data-key="${key.root}">${key.display}</button>`;
-            }).join('');
-
-            // Add click handlers
-            container.querySelectorAll('.key-btn').forEach(btn => {
-                btn.addEventListener('click', () => {
-                    state.chordKey = btn.dataset.key;
-                    saveState();
-                    populateKeyButtons();
-                    renderChords();
-                });
-            });
-        }
-
-        function populateChordTypeButtons() {
-            const container = document.getElementById('chordTypeButtons');
-            if (!container) return;
-
-            container.innerHTML = `
-                <button class="chord-type-btn ${state.chordTypeFilter === 'all' ? 'active' : ''}" data-type="all">All</button>
-                ${CHORD_TYPE_ORDER.map(key => {
-                    const type = CHORD_TYPES[key];
-                    const isActive = state.chordTypeFilter === key ? 'active' : '';
-                    return `<button class="chord-type-btn ${isActive}" data-type="${key}">${type.name}</button>`;
-                }).join('')}
-            `;
-
-            // Add click handlers
-            container.querySelectorAll('.chord-type-btn').forEach(btn => {
-                btn.addEventListener('click', () => {
-                    state.chordTypeFilter = btn.dataset.type;
-                    saveState();
-                    populateChordTypeButtons();
-                    renderChords();
-                });
-            });
-        }
-
-        function populateViewModeButtons() {
-            const container = document.getElementById('viewModeButtons');
-            if (!container) return;
-
-            container.innerHTML = `
-                <button class="view-mode-btn ${state.chordViewMode === 'single' ? 'active' : ''}" data-mode="single">Single Key</button>
-                <button class="view-mode-btn ${state.chordViewMode === 'grid' ? 'active' : ''}" data-mode="grid">All Keys Grid</button>
-            `;
-
-            // Add click handlers
-            container.querySelectorAll('.view-mode-btn').forEach(btn => {
-                btn.addEventListener('click', () => {
-                    state.chordViewMode = btn.dataset.mode;
-                    saveState();
-                    populateViewModeButtons();
-                    updateKeyButtonsVisibility();
-                    renderChords();
-                });
-            });
-        }
-
-        function updateKeyButtonsVisibility() {
-            const keyButtonsContainer = document.getElementById('keyButtonsContainer');
-            if (keyButtonsContainer) {
-                // Hide key buttons in grid mode (since all keys are shown)
-                keyButtonsContainer.style.display = state.chordViewMode === 'grid' ? 'none' : 'flex';
-            }
-        }
-
-        function renderChords() {
-            const container = document.getElementById('chordContainer');
-            if (!container) return;
-
-            const tuningData = TUNINGS_DATA[state.chordTuning];
-            if (!tuningData) {
-                container.innerHTML = '<div class="no-chord-message">Select a tuning to view chords</div>';
-                return;
-            }
-
-            const fingerNotation = document.querySelector('input[name="fingerNotation"]:checked')?.value || 'numbers';
-
-            // Check view mode
-            if (state.chordViewMode === 'grid') {
-                renderChordsGrid(container, tuningData, fingerNotation);
-            } else {
-                renderChordsSingle(container, tuningData, fingerNotation);
-            }
-        }
-
-        function renderChordsSingle(container, tuningData, fingerNotation) {
-            // Determine which chord types to show (use ordered list)
-            const typesToShow = state.chordTypeFilter === 'all'
-                ? CHORD_TYPE_ORDER
-                : [state.chordTypeFilter];
-
-            let html = '';
-
-            typesToShow.forEach(chordType => {
-                const type = CHORD_TYPES[chordType];
-                const chordName = `${state.chordKey}${type.symbol}`;
-
-                // Find voicing for this chord
-                const voicing = findChordVoicing(tuningData, state.chordKey, chordType, 12);
-
-                if (voicing) {
-                    const svg = generateChordDiagramSVG(voicing, chordName, state.chordTuning, fingerNotation);
-
-                    html += `
-                        <div class="chord-diagram-container">
-                            <div class="chord-diagram-title">${displayNote(state.chordKey)}${type.symbol}</div>
-                            <div class="chord-diagram-subtitle">${type.name}</div>
-                            ${svg}
-                            <div class="chord-notes-display">
-                                Notes: ${voicing.chordNotes.join(' - ')}
-                            </div>
-                        </div>
-                    `;
-                } else {
-                    html += `
-                        <div class="chord-diagram-container">
-                            <div class="chord-diagram-title">${displayNote(state.chordKey)}${type.symbol}</div>
-                            <div class="chord-diagram-subtitle">${type.name}</div>
-                            <div class="no-chord-message">Voicing not available for this tuning</div>
-                        </div>
-                    `;
-                }
-            });
-
-            container.className = 'chord-grid';
-            container.innerHTML = html;
-        }
-
-        function renderChordsGrid(container, tuningData, fingerNotation) {
-            // Determine which chord types to show as columns (use ordered groups)
-            const typesToShow = state.chordTypeFilter === 'all'
-                ? CHORD_TYPE_ORDER
-                : [state.chordTypeFilter];
-
-            // Helper to check if we need a separator after this chord type
-            function needsSeparatorAfter(chordType) {
-                if (state.chordTypeFilter !== 'all') return false;
-                for (let i = 0; i < CHORD_TYPE_GROUPS.length - 1; i++) {
-                    const group = CHORD_TYPE_GROUPS[i];
-                    if (group[group.length - 1] === chordType) return true;
-                }
-                return false;
-            }
-
-            // Build table header with chord types and separators
-            let headerHtml = '<tr><th class="key-header">Key</th>';
-            typesToShow.forEach(chordType => {
-                const type = CHORD_TYPES[chordType];
-                headerHtml += `<th>${type.name}</th>`;
-                if (needsSeparatorAfter(chordType)) {
-                    headerHtml += '<th class="group-separator"></th>';
-                }
-            });
-            headerHtml += '</tr>';
-
-            // Build table rows for each key
-            let rowsHtml = '';
-            CHORD_KEYS.forEach((key, keyIndex) => {
-                // Add page break class before F#/Gb (index 6) for print
-                const pageBreakClass = key.root === 'F#' ? ' class="page-break-before"' : '';
-                rowsHtml += `<tr${pageBreakClass}><td class="key-cell">${key.display}</td>`;
-
-                typesToShow.forEach(chordType => {
-                    const type = CHORD_TYPES[chordType];
-                    const voicing = findChordVoicing(tuningData, key.root, chordType, 12);
-
-                    if (voicing) {
-                        const svg = generateChordDiagramSVG(voicing, `${key.root}${type.symbol}`, state.chordTuning, fingerNotation);
-                        rowsHtml += `
-                            <td class="grid-chord-cell">
-                                <div class="grid-chord-diagram">
-                                    <div class="grid-chord-title">${displayNote(key.root)}${type.symbol}</div>
-                                    ${svg}
-                                    <div class="grid-chord-notes">${voicing.chordNotes.join(' - ')}</div>
-                                </div>
-                            </td>
-                        `;
-                    } else {
-                        rowsHtml += `
-                            <td class="grid-chord-cell">
-                                <div class="grid-no-voicing">Not available</div>
-                            </td>
-                        `;
-                    }
-
-                    if (needsSeparatorAfter(chordType)) {
-                        rowsHtml += '<td class="group-separator"></td>';
-                    }
-                });
-
-                rowsHtml += '</tr>';
-            });
-
-            container.className = 'chord-keys-grid-container';
-            container.innerHTML = `
-                <table class="chord-keys-grid">
-                    <thead>${headerHtml}</thead>
-                    <tbody>${rowsHtml}</tbody>
-                </table>
-            `;
-        }
-
-        function setupChordEventListeners() {
-            // Tuning selector
-            const tuningSelect = document.getElementById('chordTuningSelect');
-            if (tuningSelect) {
-                tuningSelect.addEventListener('change', (e) => {
-                    state.chordTuning = e.target.value;
-                    saveState();
-                    renderChords();
-                });
-            }
-
-            // Finger notation toggle
-            document.querySelectorAll('input[name="fingerNotation"]').forEach(radio => {
-                radio.addEventListener('change', () => {
-                    state.fingerNotation = document.querySelector('input[name="fingerNotation"]:checked')?.value || 'numbers';
-                    saveState();
-                    renderChords();
-                });
-            });
-        }
-
-        function initChordTab() {
-            populateChordTuningSelect();
-            populateViewModeButtons();
-            populateKeyButtons();
-            populateChordTypeButtons();
-            setupChordEventListeners();
-            updateKeyButtonsVisibility();
-            renderChords();
-        }
-
-        // ============================================
-        // INITIALIZATION
-        // ============================================
-        function init() {
-            loadState();
-            applyStateToUI();
-            populateTuningList();
-            setupEventListeners();
-            renderTunings();
-            renderFretboard();
-            initChordTab();
-        }
 
-        document.addEventListener('DOMContentLoaded', init);
-    </script>
+    <!-- JavaScript files (order matters) -->
+    <script src="js/utils.js"></script>
+    <script src="js/tunings.js"></script>
+    <script src="js/chords.js"></script>
+    <script src="js/fretboard.js"></script>
+    <script src="js/app.js"></script>
 </body>
 </html>
diff --git a/js/app.js b/js/app.js
new file mode 100644
index 0000000..88f8ccf
--- /dev/null
+++ b/js/app.js
@@ -0,0 +1,528 @@
+// Application State and UI Logic
+
+// ============================================
+// STATE MANAGEMENT
+// ============================================
+let state = {
+    familyFilter: 'all',
+    capoPosition: 0,
+    fretCount: 15,
+    showNotes: true,
+    selectedFretboardTuning: 'Open G',
+    fretboardFamilyFilter: 'all',
+    useSolfege: false,
+    // Chord inversion state
+    showInversions: false,
+    inversionRootNote: 'G',
+    // Chord tab state
+    chordTuning: 'Open G',
+    chordKey: 'G',
+    chordTypeFilter: 'all',
+    fingerNotation: 'numbers',
+    chordViewMode: 'single'  // 'single' or 'grid' (all keys)
+};
+
+function loadState() {
+    const saved = localStorage.getItem('banjoTuningsState');
+    if (saved) {
+        try {
+            const parsed = JSON.parse(saved);
+            state = { ...state, ...parsed };
+        } catch (e) {
+            console.error('Failed to load state:', e);
+        }
+    }
+}
+
+function saveState() {
+    localStorage.setItem('banjoTuningsState', JSON.stringify(state));
+}
+
+function renderFretboard() {
+    const container = document.getElementById('fretboardContainer');
+    container.innerHTML = generateFretboardSVG(state.selectedFretboardTuning);
+}
+
+function populateTuningList() {
+    const list = document.getElementById('tuningList');
+    const familyFilter = state.fretboardFamilyFilter || 'all';
+
+    const families = {
+        'c-family': 'C-Based Tunings',
+        'd-family': 'D-Based Tunings',
+        'g-family': 'G-Based Tunings',
+        'other-family': 'Other (4-String)'
+    };
+
+    let html = '';
+
+    for (const [familyKey, familyName] of Object.entries(families)) {
+        if (familyFilter !== 'all' && familyFilter !== familyKey) continue;
+
+        const tuningsInFamily = Object.entries(TUNINGS_DATA)
+            .filter(([_, t]) => t.family === familyKey);
+
+        if (tuningsInFamily.length === 0) continue;
+
+        // Add group header if showing all families
+        if (familyFilter === 'all') {
+            html += `<div class="tuning-list-group">${familyName}</div>`;
+        }
+
+        for (const [name, tuning] of tuningsInFamily) {
+            const isSelected = name === state.selectedFretboardTuning;
+
+            // Determine which indices should be lowercase (first occurrence of duplicates)
+            const lowercaseIdx = new Set();
+            const seenNotes = new Map();
+            for (let i = 0; i < tuning.strings.length; i++) {
+                const normalized = normalizeNote(tuning.strings[i]).toUpperCase();
+                if (!seenNotes.has(normalized)) {
+                    seenNotes.set(normalized, i);
+                } else {
+                    lowercaseIdx.add(seenNotes.get(normalized));
+                }
+            }
+
+            const tuningStr = tuning.strings.map((s, idx) => {
+                let note = displayNote(normalizeNote(s));
+                // Convert to solfège if enabled
+                if (state.useSolfege) {
+                    note = toSolfege(note);
+                }
+                return lowercaseIdx.has(idx) ? note.toLowerCase() : note;
+            }).join(' ');
+
+            html += `
+                <div class="tuning-list-item ${tuning.family} ${isSelected ? 'selected' : ''}"
+                     data-tuning="${name}">
+                    <span class="tuning-item-name">${name}</span>
+                    <span class="tuning-item-notes">${tuningStr}</span>
+                </div>
+            `;
+        }
+    }
+
+    list.innerHTML = html;
+
+    // Add click handlers
+    list.querySelectorAll('.tuning-list-item').forEach(item => {
+        item.addEventListener('click', () => {
+            const tuningName = item.dataset.tuning;
+            state.selectedFretboardTuning = tuningName;
+            saveState();
+            populateTuningList(); // Re-render to update selection
+            renderFretboard();
+        });
+    });
+}
+
+// ============================================
+// EVENT HANDLERS
+// ============================================
+function setupEventListeners() {
+    document.getElementById('familyFilter').addEventListener('change', (e) => {
+        state.familyFilter = e.target.value;
+        saveState();
+        renderTunings();
+    });
+
+    document.getElementById('capoPosition').addEventListener('change', (e) => {
+        state.capoPosition = parseInt(e.target.value) || 0;
+        saveState();
+        renderTunings();
+        renderFretboard();
+    });
+
+    document.getElementById('fretCount').addEventListener('change', (e) => {
+        state.fretCount = parseInt(e.target.value);
+        saveState();
+        renderFretboard();
+    });
+
+    document.getElementById('showNotesToggle').addEventListener('change', (e) => {
+        state.showNotes = e.target.checked;
+        saveState();
+        renderFretboard();
+    });
+
+    document.getElementById('solfegeToggle').addEventListener('change', (e) => {
+        state.useSolfege = e.target.checked;
+        saveState();
+        renderTunings();
+        renderFretboard();
+        populateTuningList();
+    });
+
+    // Chord inversion controls
+    document.getElementById('showInversionsToggle').addEventListener('change', (e) => {
+        state.showInversions = e.target.checked;
+        saveState();
+        renderFretboard();
+    });
+
+    document.getElementById('inversionRootNote').addEventListener('change', (e) => {
+        state.inversionRootNote = e.target.value;
+        saveState();
+        renderFretboard();
+    });
+
+    // Fretboard family filter
+    document.getElementById('fretboardFamilyFilter').addEventListener('change', (e) => {
+        state.fretboardFamilyFilter = e.target.value;
+        saveState();
+        populateTuningList();
+    });
+
+    document.querySelectorAll('.tab').forEach(tab => {
+        tab.addEventListener('click', (e) => {
+            const tabName = e.target.dataset.tab;
+            document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
+            document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
+            e.target.classList.add('active');
+            document.getElementById(`${tabName}-tab`).classList.add('active');
+        });
+    });
+}
+
+function resetSettings() {
+    state = {
+        familyFilter: 'all',
+        capoPosition: 0,
+        fretCount: 15,
+        showNotes: true,
+        selectedFretboardTuning: 'Open G',
+        fretboardFamilyFilter: 'all',
+        useSolfege: false,
+        showInversions: false,
+        inversionRootNote: 'G',
+        chordTuning: 'Open G',
+        chordKey: 'G',
+        chordTypeFilter: 'all',
+        fingerNotation: 'numbers',
+        chordViewMode: 'single'
+    };
+    saveState();
+    applyStateToUI();
+    populateTuningList();
+    renderTunings();
+    renderFretboard();
+    initChordTab();
+}
+
+function applyStateToUI() {
+    document.getElementById('familyFilter').value = state.familyFilter;
+    document.getElementById('capoPosition').value = state.capoPosition;
+    document.getElementById('fretCount').value = state.fretCount;
+    document.getElementById('showNotesToggle').checked = state.showNotes;
+    document.getElementById('solfegeToggle').checked = state.useSolfege || false;
+    document.getElementById('fretboardFamilyFilter').value = state.fretboardFamilyFilter || 'all';
+    document.getElementById('showInversionsToggle').checked = state.showInversions || false;
+    document.getElementById('inversionRootNote').value = state.inversionRootNote || 'G';
+
+    // Chord tab UI
+    const chordTuningSelect = document.getElementById('chordTuningSelect');
+    if (chordTuningSelect) {
+        chordTuningSelect.value = state.chordTuning || 'Open G';
+    }
+
+    // Finger notation radio
+    const fingerRadio = document.querySelector(`input[name="fingerNotation"][value="${state.fingerNotation || 'numbers'}"]`);
+    if (fingerRadio) {
+        fingerRadio.checked = true;
+    }
+}
+
+// ============================================
+// CHORD TAB FUNCTIONS
+// ============================================
+function populateChordTuningSelect() {
+    const select = document.getElementById('chordTuningSelect');
+    if (!select) return;
+
+    select.innerHTML = '';
+
+    // Group tunings by family
+    const families = {
+        'g-family': 'G-Based',
+        'c-family': 'C-Based',
+        'd-family': 'D-Based',
+        'other-family': 'Other (4-String)'
+    };
+
+    for (const [familyKey, familyName] of Object.entries(families)) {
+        const group = document.createElement('optgroup');
+        group.label = familyName;
+
+        for (const [name, data] of Object.entries(TUNINGS_DATA)) {
+            if (data.family === familyKey) {
+                const option = document.createElement('option');
+                option.value = name;
+                option.textContent = name;
+                if (name === state.chordTuning) {
+                    option.selected = true;
+                }
+                group.appendChild(option);
+            }
+        }
+
+        select.appendChild(group);
+    }
+}
+
+function populateKeyButtons() {
+    const container = document.getElementById('keyButtons');
+    if (!container) return;
+
+    container.innerHTML = CHORD_KEYS.map(key => {
+        const isActive = state.chordKey === key.root ? 'active' : '';
+        return `<button class="key-btn ${isActive}" data-key="${key.root}">${key.display}</button>`;
+    }).join('');
+
+    // Add click handlers
+    container.querySelectorAll('.key-btn').forEach(btn => {
+        btn.addEventListener('click', () => {
+            state.chordKey = btn.dataset.key;
+            saveState();
+            populateKeyButtons();
+            renderChords();
+        });
+    });
+}
+
+function populateChordTypeButtons() {
+    const container = document.getElementById('chordTypeButtons');
+    if (!container) return;
+
+    container.innerHTML = `
+        <button class="chord-type-btn ${state.chordTypeFilter === 'all' ? 'active' : ''}" data-type="all">All</button>
+        ${CHORD_TYPE_ORDER.map(key => {
+            const type = CHORD_TYPES[key];
+            const isActive = state.chordTypeFilter === key ? 'active' : '';
+            return `<button class="chord-type-btn ${isActive}" data-type="${key}">${type.name}</button>`;
+        }).join('')}
+    `;
+
+    // Add click handlers
+    container.querySelectorAll('.chord-type-btn').forEach(btn => {
+        btn.addEventListener('click', () => {
+            state.chordTypeFilter = btn.dataset.type;
+            saveState();
+            populateChordTypeButtons();
+            renderChords();
+        });
+    });
+}
+
+function populateViewModeButtons() {
+    const container = document.getElementById('viewModeButtons');
+    if (!container) return;
+
+    container.innerHTML = `
+        <button class="view-mode-btn ${state.chordViewMode === 'single' ? 'active' : ''}" data-mode="single">Single Key</button>
+        <button class="view-mode-btn ${state.chordViewMode === 'grid' ? 'active' : ''}" data-mode="grid">All Keys Grid</button>
+    `;
+
+    // Add click handlers
+    container.querySelectorAll('.view-mode-btn').forEach(btn => {
+        btn.addEventListener('click', () => {
+            state.chordViewMode = btn.dataset.mode;
+            saveState();
+            populateViewModeButtons();
+            updateKeyButtonsVisibility();
+            renderChords();
+        });
+    });
+}
+
+function updateKeyButtonsVisibility() {
+    const keyButtonsContainer = document.getElementById('keyButtonsContainer');
+    if (keyButtonsContainer) {
+        // Hide key buttons in grid mode (since all keys are shown)
+        keyButtonsContainer.style.display = state.chordViewMode === 'grid' ? 'none' : 'flex';
+    }
+}
+
+function renderChords() {
+    const container = document.getElementById('chordContainer');
+    if (!container) return;
+
+    const tuningData = TUNINGS_DATA[state.chordTuning];
+    if (!tuningData) {
+        container.innerHTML = '<div class="no-chord-message">Select a tuning to view chords</div>';
+        return;
+    }
+
+    const fingerNotation = document.querySelector('input[name="fingerNotation"]:checked')?.value || 'numbers';
+
+    // Check view mode
+    if (state.chordViewMode === 'grid') {
+        renderChordsGrid(container, tuningData, fingerNotation);
+    } else {
+        renderChordsSingle(container, tuningData, fingerNotation);
+    }
+}
+
+function renderChordsSingle(container, tuningData, fingerNotation) {
+    // Determine which chord types to show (use ordered list)
+    const typesToShow = state.chordTypeFilter === 'all'
+        ? CHORD_TYPE_ORDER
+        : [state.chordTypeFilter];
+
+    let html = '';
+
+    typesToShow.forEach(chordType => {
+        const type = CHORD_TYPES[chordType];
+        const chordName = `${state.chordKey}${type.symbol}`;
+
+        // Find voicing for this chord
+        const voicing = findChordVoicing(tuningData, state.chordKey, chordType, 12);
+
+        if (voicing) {
+            const svg = generateChordDiagramSVG(voicing, chordName, state.chordTuning, fingerNotation);
+
+            html += `
+                <div class="chord-diagram-container">
+                    <div class="chord-diagram-title">${displayNote(state.chordKey)}${type.symbol}</div>
+                    <div class="chord-diagram-subtitle">${type.name}</div>
+                    ${svg}
+                    <div class="chord-notes-display">
+                        Notes: ${voicing.chordNotes.join(' - ')}
+                    </div>
+                </div>
+            `;
+        } else {
+            html += `
+                <div class="chord-diagram-container">
+                    <div class="chord-diagram-title">${displayNote(state.chordKey)}${type.symbol}</div>
+                    <div class="chord-diagram-subtitle">${type.name}</div>
+                    <div class="no-chord-message">Voicing not available for this tuning</div>
+                </div>
+            `;
+        }
+    });
+
+    container.className = 'chord-grid';
+    container.innerHTML = html;
+}
+
+function renderChordsGrid(container, tuningData, fingerNotation) {
+    // Determine which chord types to show as columns (use ordered groups)
+    const typesToShow = state.chordTypeFilter === 'all'
+        ? CHORD_TYPE_ORDER
+        : [state.chordTypeFilter];
+
+    // Helper to check if we need a separator after this chord type
+    function needsSeparatorAfter(chordType) {
+        if (state.chordTypeFilter !== 'all') return false;
+        for (let i = 0; i < CHORD_TYPE_GROUPS.length - 1; i++) {
+            const group = CHORD_TYPE_GROUPS[i];
+            if (group[group.length - 1] === chordType) return true;
+        }
+        return false;
+    }
+
+    // Build table header with chord types and separators
+    let headerHtml = '<tr><th class="key-header">Key</th>';
+    typesToShow.forEach(chordType => {
+        const type = CHORD_TYPES[chordType];
+        headerHtml += `<th>${type.name}</th>`;
+        if (needsSeparatorAfter(chordType)) {
+            headerHtml += '<th class="group-separator"></th>';
+        }
+    });
+    headerHtml += '</tr>';
+
+    // Build table rows for each key
+    let rowsHtml = '';
+    CHORD_KEYS.forEach((key, keyIndex) => {
+        // Add page break class before F#/Gb (index 6) for print
+        const pageBreakClass = key.root === 'F#' ? ' class="page-break-before"' : '';
+        rowsHtml += `<tr${pageBreakClass}><td class="key-cell">${key.display}</td>`;
+
+        typesToShow.forEach(chordType => {
+            const type = CHORD_TYPES[chordType];
+            const voicing = findChordVoicing(tuningData, key.root, chordType, 12);
+
+            if (voicing) {
+                const svg = generateChordDiagramSVG(voicing, `${key.root}${type.symbol}`, state.chordTuning, fingerNotation);
+                rowsHtml += `
+                    <td class="grid-chord-cell">
+                        <div class="grid-chord-diagram">
+                            <div class="grid-chord-title">${displayNote(key.root)}${type.symbol}</div>
+                            ${svg}
+                            <div class="grid-chord-notes">${voicing.chordNotes.join(' - ')}</div>
+                        </div>
+                    </td>
+                `;
+            } else {
+                rowsHtml += `
+                    <td class="grid-chord-cell">
+                        <div class="grid-no-voicing">Not available</div>
+                    </td>
+                `;
+            }
+
+            if (needsSeparatorAfter(chordType)) {
+                rowsHtml += '<td class="group-separator"></td>';
+            }
+        });
+
+        rowsHtml += '</tr>';
+    });
+
+    container.className = 'chord-keys-grid-container';
+    container.innerHTML = `
+        <table class="chord-keys-grid">
+            <thead>${headerHtml}</thead>
+            <tbody>${rowsHtml}</tbody>
+        </table>
+    `;
+}
+
+function setupChordEventListeners() {
+    // Tuning selector
+    const tuningSelect = document.getElementById('chordTuningSelect');
+    if (tuningSelect) {
+        tuningSelect.addEventListener('change', (e) => {
+            state.chordTuning = e.target.value;
+            saveState();
+            renderChords();
+        });
+    }
+
+    // Finger notation toggle
+    document.querySelectorAll('input[name="fingerNotation"]').forEach(radio => {
+        radio.addEventListener('change', () => {
+            state.fingerNotation = document.querySelector('input[name="fingerNotation"]:checked')?.value || 'numbers';
+            saveState();
+            renderChords();
+        });
+    });
+}
+
+function initChordTab() {
+    populateChordTuningSelect();
+    populateViewModeButtons();
+    populateKeyButtons();
+    populateChordTypeButtons();
+    setupChordEventListeners();
+    updateKeyButtonsVisibility();
+    renderChords();
+}
+
+// ============================================
+// INITIALIZATION
+// ============================================
+function init() {
+    loadState();
+    applyStateToUI();
+    populateTuningList();
+    setupEventListeners();
+    renderTunings();
+    renderFretboard();
+    initChordTab();
+}
+
+document.addEventListener('DOMContentLoaded', init);
+
diff --git a/js/chords.js b/js/chords.js
new file mode 100644
index 0000000..a9af6fc
--- /dev/null
+++ b/js/chords.js
@@ -0,0 +1,386 @@
+// Chord Data & Functions
+
+// ============================================
+// CHORD DATA & FUNCTIONS
+// ============================================
+
+// 12 keys with display names (using slash for enharmonics)
+const CHORD_KEYS = [
+    { root: 'C',  display: 'C' },
+    { root: 'C#', display: 'C#/Db' },
+    { root: 'D',  display: 'D' },
+    { root: 'D#', display: 'D#/Eb' },
+    { root: 'E',  display: 'E' },
+    { root: 'F',  display: 'F' },
+    { root: 'F#', display: 'F#/Gb' },
+    { root: 'G',  display: 'G' },
+    { root: 'G#', display: 'G#/Ab' },
+    { root: 'A',  display: 'A' },
+    { root: 'A#', display: 'A#/Bb' },
+    { root: 'B',  display: 'B' }
+];
+
+// 8 chord types with intervals (semitones from root)
+const CHORD_TYPES = {
+    'major': { symbol: '', name: 'Major', intervals: [0, 4, 7], degrees: ['1', '3', '5'] },
+    'minor': { symbol: 'm', name: 'Minor', intervals: [0, 3, 7], degrees: ['1', '♭3', '5'] },
+    'm7':    { symbol: 'm7', name: 'Minor 7', intervals: [0, 3, 7, 10], degrees: ['1', '♭3', '5', '♭7'] },
+    '6':     { symbol: '6', name: 'Sixth', intervals: [0, 4, 7, 9], degrees: ['1', '3', '5', '6'] },
+    '7':     { symbol: '7', name: 'Seventh', intervals: [0, 4, 7, 10], degrees: ['1', '3', '5', '♭7'] },
+    'dim':   { symbol: 'dim', name: 'Diminished', intervals: [0, 3, 6], degrees: ['1', '♭3', '♭5'] },
+    'sus4':  { symbol: 'sus4', name: 'Sus4', intervals: [0, 5, 7], degrees: ['1', '4', '5'] },
+    'aug':   { symbol: 'aug', name: 'Augmented', intervals: [0, 4, 8], degrees: ['1', '3', '#5'] }
+};
+
+// Chord types grouped for display with separators between groups
+const CHORD_TYPE_GROUPS = [
+    ['major', 'minor', 'm7'],  // Group 1
+    ['6', '7'],                 // Group 2
+    ['dim'],                    // Group 3
+    ['sus4'],                   // Group 4
+    ['aug']                     // Group 5
+];
+
+// Ordered list of chord types (flattened groups)
+const CHORD_TYPE_ORDER = CHORD_TYPE_GROUPS.flat();
+
+// Finger notation mapping
+const FINGER_NOTATION = {
+    numbers: { 1: '1', 2: '2', 3: '3', 4: '4', T: 'T' },
+    pima: { 1: 'I', 2: 'M', 3: 'A', 4: 'P', T: 'P' }
+};
+
+// Convert note name to pitch class (0-11)
+function noteToPitchClass(note) {
+    const normalized = normalizeNote(note);
+    return CHROMATIC_SCALE.indexOf(normalized);
+}
+
+// Get chord pitch classes from root and type
+function getChordPitchClasses(rootNote, chordType) {
+    const type = CHORD_TYPES[chordType];
+    if (!type) return [];
+
+    const rootPitch = noteToPitchClass(rootNote);
+    if (rootPitch === -1) return [];
+
+    return type.intervals.map(interval => (rootPitch + interval) % 12);
+}
+
+// Get all notes in a chord (as note names)
+function getChordNotes(rootNote, chordType) {
+    const pitchClasses = getChordPitchClasses(rootNote, chordType);
+    return pitchClasses.map(pc => CHROMATIC_SCALE[pc]);
+}
+
+// Find chord voicing for 4-string banjo (excludes 5th drone string for low positions)
+// For 5-string banjo, we only use the 4 main strings for chords (DGBD in Open G)
+function findChordVoicing(tuningData, rootNote, chordType, maxFret = 12) {
+    const chordPitchClasses = getChordPitchClasses(rootNote, chordType);
+    if (chordPitchClasses.length === 0) return null;
+
+    const type = CHORD_TYPES[chordType];
+    const rootPitch = noteToPitchClass(rootNote);
+
+    // Get the 4 main strings (exclude 5th/drone string for 5-string banjo)
+    const allStrings = tuningData.strings;
+    const shortStringIdx = tuningData.shortStringIndex;
+
+    // For 5-string banjo, use strings 1-4 (indices 1-4), exclude index 0 (5th string)
+    // For 4-string banjo, use all strings
+    let mainStrings;
+    if (shortStringIdx === 0 && allStrings.length === 5) {
+        // 5-string banjo: use strings at indices 1,2,3,4 (4th,3rd,2nd,1st strings)
+        mainStrings = allStrings.slice(1);
+    } else {
+        mainStrings = allStrings;
+    }
+
+    const numStrings = mainStrings.length;
+    const stringPitchClasses = mainStrings.map(note => noteToPitchClass(note));
+
+    // Find all valid voicings using recursive backtracking
+    const results = [];
+    const maxReach = 4;
+    const maxFingers = 4;
+
+    function explore(stringIdx, currentVoicing, usedTones) {
+        const fingersUsed = currentVoicing.filter(v => v && v.fret > 0).length;
+        if (fingersUsed > maxFingers) return;
+
+        const frettedFrets = currentVoicing.filter(v => v && v.fret > 0).map(v => v.fret);
+        if (frettedFrets.length > 1) {
+            const reach = Math.max(...frettedFrets) - Math.min(...frettedFrets);
+            if (reach > maxReach) return;
+        }
+
+        if (stringIdx === numStrings) {
+            if (usedTones.size === chordPitchClasses.length) {
+                results.push([...currentVoicing]);
+            }
+            return;
+        }
+
+        const stringPitch = stringPitchClasses[stringIdx];
+
+        // Option 1: Mute this string
+        explore(stringIdx + 1, [...currentVoicing, null], new Set(usedTones));
+
+        // Try each fret position (0 = open, up to maxFret)
+        for (let fret = 0; fret <= maxFret; fret++) {
+            const pitchAtFret = (stringPitch + fret) % 12;
+            const toneIndex = chordPitchClasses.indexOf(pitchAtFret);
+
+            if (toneIndex !== -1) {
+                const newUsedTones = new Set(usedTones);
+                newUsedTones.add(pitchAtFret);
+
+                explore(stringIdx + 1, [...currentVoicing, {
+                    fret: fret,
+                    pitchClass: pitchAtFret,
+                    toneIndex: toneIndex,
+                    note: CHROMATIC_SCALE[pitchAtFret]
+                }], newUsedTones);
+            }
+        }
+    }
+
+    explore(0, [], new Set());
+
+    if (results.length === 0) return null;
+
+    // Score and sort voicings
+    function scoreVoicing(voicing) {
+        const sounding = voicing.filter(v => v !== null);
+        const fretted = sounding.filter(v => v.fret > 0);
+        const minFret = fretted.length > 0 ? Math.min(...fretted.map(v => v.fret)) : 0;
+        const maxFretUsed = fretted.length > 0 ? Math.max(...fretted.map(v => v.fret)) : 0;
+        const span = maxFretUsed - minFret;
+        const openCount = sounding.filter(v => v.fret === 0).length;
+        const soundingCount = sounding.length;
+        return minFret * 100 + span * 50 - openCount * 30 - soundingCount * 10;
+    }
+
+    results.sort((a, b) => scoreVoicing(a) - scoreVoicing(b));
+    const bestVoicing = results[0];
+
+    // Convert to position format for rendering
+    // Positions are ordered: string 4 (index 0), string 3, string 2, string 1 (index 3)
+    const positions = bestVoicing.map((pos, idx) => {
+        const stringNum = 4 - idx; // 4, 3, 2, 1
+
+        if (pos === null) {
+            return {
+                stringNum: stringNum,
+                fret: null,
+                muted: true,
+                note: null,
+                degree: null,
+                isRoot: false
+            };
+        }
+
+        const degree = type.degrees[pos.toneIndex];
+        const isRoot = pos.pitchClass === rootPitch;
+
+        return {
+            stringNum: stringNum,
+            fret: pos.fret,
+            muted: false,
+            note: pos.note,
+            degree: degree,
+            isRoot: isRoot
+        };
+    });
+
+    // Assign finger numbers based on fret position
+    // Finger 1 = lowest fret, then 2, 3, 4 in ascending order
+    assignFingers(positions);
+
+    // Determine base fret for display
+    const frettedPositions = positions.filter(p => p.fret > 0);
+    const minFret = frettedPositions.length > 0 ? Math.min(...frettedPositions.map(p => p.fret)) : 1;
+    const baseFret = minFret > 4 ? minFret : 1;
+
+    // Check for barre chord
+    const barre = detectBarre(positions);
+
+    // Get chord notes for display
+    const chordNotes = getChordNotes(rootNote, chordType);
+
+    return {
+        positions,
+        baseFret,
+        barre,
+        chordNotes: chordNotes.map(n => displayNote(n)),
+        numStrings: numStrings
+    };
+}
+
+function assignFingers(positions) {
+    // Get fretted positions sorted by fret (ascending), then by string number (descending)
+    // This ensures: lowest fret first, and at same fret, higher string numbers first
+    const fretted = positions
+        .filter(p => p.fret > 0)
+        .sort((a, b) => {
+            if (a.fret !== b.fret) return a.fret - b.fret;
+            return b.stringNum - a.stringNum; // Higher string number first at same fret
+        });
+
+    if (fretted.length === 0) return;
+
+    // Assign unique finger to each position in sequence
+    fretted.forEach((p, idx) => {
+        p.finger = Math.min(idx + 1, 4);
+    });
+}
+
+function detectBarre(positions) {
+    const fretGroups = {};
+    positions.forEach((p, idx) => {
+        if (p.fret > 0) {
+            if (!fretGroups[p.fret]) fretGroups[p.fret] = [];
+            fretGroups[p.fret].push({ pos: p, idx: idx });
+        }
+    });
+
+    for (const [fret, group] of Object.entries(fretGroups)) {
+        if (group.length >= 2) {
+            // Check if they're on adjacent strings
+            const stringNums = group.map(g => g.pos.stringNum).sort((a, b) => b - a);
+            const minStr = Math.min(...stringNums);
+            const maxStr = Math.max(...stringNums);
+
+            if (maxStr - minStr <= 3) {
+                return {
+                    fret: parseInt(fret),
+                    fromString: minStr,
+                    toString: maxStr
+                };
+            }
+        }
+    }
+
+    return null;
+}
+
+// Generate SVG chord diagram for 4 main strings
+function generateChordDiagramSVG(voicing, chordName, tuningName, fingerNotation = 'numbers') {
+    const width = 140;
+    const height = 200;
+    const marginLeft = 35;
+    const marginTop = 35;
+    const marginRight = 15;
+    const marginBottom = 25;
+
+    const positions = voicing.positions;
+    const numStrings = 4; // Always 4 main strings for chord diagrams
+    const numFrets = 6;
+    const baseFret = voicing.baseFret;
+
+    const stringSpacing = (width - marginLeft - marginRight) / (numStrings - 1);
+    const fretSpacing = (height - marginTop - marginBottom) / numFrets;
+
+    let svg = `<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg">`;
+
+    // Background
+    svg += `<rect width="${width}" height="${height}" fill="white"/>`;
+
+    // Nut (thick line at top) - only show if baseFret is 1
+    if (baseFret === 1) {
+        svg += `<rect x="${marginLeft - 3}" y="${marginTop - 3}" width="${(numStrings - 1) * stringSpacing + 6}" height="5" fill="#1a1a1a"/>`;
+    } else {
+        // Show fret number for higher positions
+        svg += `<text x="${marginLeft - 15}" y="${marginTop + fretSpacing / 2 + 4}" font-size="11" font-weight="bold" text-anchor="middle">${baseFret}fr</text>`;
+    }
+
+    // Fret lines
+    for (let i = 0; i <= numFrets; i++) {
+        const y = marginTop + i * fretSpacing;
+        svg += `<line x1="${marginLeft}" y1="${y}" x2="${marginLeft + (numStrings - 1) * stringSpacing}" y2="${y}" stroke="#333" stroke-width="${i === 0 ? 2 : 1}"/>`;
+    }
+
+    // String lines (left to right: string 4, 3, 2, 1)
+    for (let i = 0; i < numStrings; i++) {
+        const x = marginLeft + i * stringSpacing;
+        svg += `<line x1="${x}" y1="${marginTop}" x2="${x}" y2="${marginTop + numFrets * fretSpacing}" stroke="#333" stroke-width="1.5"/>`;
+    }
+
+    // String labels at top (4, 3, 2, 1 from left to right)
+    for (let i = 0; i < numStrings; i++) {
+        const x = marginLeft + i * stringSpacing;
+        const stringNum = 4 - i;
+        svg += `<text x="${x}" y="${marginTop - 20}" font-size="9" text-anchor="middle" fill="#666">${stringNum}</text>`;
+    }
+
+    // 5th string box (same size cell as main grid, flush with it, at fret 5-6)
+    // Only show if baseFret is 1 (low position chords)
+    if (baseFret === 1) {
+        const fret5Y = marginTop + 5 * fretSpacing; // Fret 5 wire
+        const fret6Y = marginTop + 6 * fretSpacing; // Fret 6 wire
+        const boxLeft = marginLeft - stringSpacing; // One cell width to the left
+
+        // Draw the box - same size as grid cells, flush with main grid
+        // Top line (fret 5)
+        svg += `<line x1="${boxLeft}" y1="${fret5Y}" x2="${marginLeft}" y2="${fret5Y}" stroke="#333" stroke-width="1"/>`;
+        // Bottom line (fret 6)
+        svg += `<line x1="${boxLeft}" y1="${fret6Y}" x2="${marginLeft}" y2="${fret6Y}" stroke="#333" stroke-width="1"/>`;
+        // Left edge
+        svg += `<line x1="${boxLeft}" y1="${fret5Y}" x2="${boxLeft}" y2="${fret6Y}" stroke="#333" stroke-width="1"/>`;
+
+        // Label for 5th string (centered in the box)
+        const boxCenterX = boxLeft + stringSpacing / 2;
+        svg += `<text x="${boxCenterX}" y="${fret5Y - 8}" font-size="8" text-anchor="middle" fill="#666">5</text>`;
+    }
+
+    // Finger positions and open/muted indicators
+    positions.forEach((pos, idx) => {
+        // idx 0 = string 4 (leftmost), idx 3 = string 1 (rightmost)
+        const x = marginLeft + idx * stringSpacing;
+
+        if (pos.muted) {
+            // X for muted string
+            svg += `<text x="${x}" y="${marginTop - 8}" font-size="12" font-weight="bold" text-anchor="middle" fill="#666">✕</text>`;
+        } else if (pos.fret === 0) {
+            // Open string circle
+            svg += `<circle cx="${x}" cy="${marginTop - 10}" r="5" fill="none" stroke="#333" stroke-width="1.5"/>`;
+        } else {
+            // Fretted position
+            const displayFret = pos.fret - baseFret + 1;
+            if (displayFret >= 1 && displayFret <= numFrets) {
+                const y = marginTop + (displayFret - 0.5) * fretSpacing;
+
+                // Root notes get filled circle
+                if (pos.isRoot) {
+                    svg += `<circle cx="${x}" cy="${y}" r="9" fill="#1a1a1a" stroke="#1a1a1a" stroke-width="1.5"/>`;
+                    if (pos.finger) {
+                        const fingerLabel = FINGER_NOTATION[fingerNotation][pos.finger] || pos.finger;
+                        svg += `<text x="${x}" y="${y + 3}" font-size="10" font-weight="bold" text-anchor="middle" fill="white">${fingerLabel}</text>`;
+                    }
+                } else {
+                    svg += `<circle cx="${x}" cy="${y}" r="9" fill="white" stroke="#1a1a1a" stroke-width="1.5"/>`;
+                    if (pos.finger) {
+                        const fingerLabel = FINGER_NOTATION[fingerNotation][pos.finger] || pos.finger;
+                        svg += `<text x="${x}" y="${y + 3}" font-size="10" font-weight="bold" text-anchor="middle">${fingerLabel}</text>`;
+                    }
+                }
+            }
+        }
+    });
+
+    // Degree labels below strings
+    positions.forEach((pos, idx) => {
+        const x = marginLeft + idx * stringSpacing;
+        const y = marginTop + numFrets * fretSpacing + 15;
+
+        if (pos.degree && !pos.muted) {
+            svg += `<text x="${x}" y="${y}" font-size="9" text-anchor="middle" fill="#666">${pos.degree}</text>`;
+        } else if (pos.muted) {
+            svg += `<text x="${x}" y="${y}" font-size="9" text-anchor="middle" fill="#999">-</text>`;
+        }
+    });
+
+    svg += '</svg>';
+    return svg;
+}
+
diff --git a/js/fretboard.js b/js/fretboard.js
new file mode 100644
index 0000000..55a1525
--- /dev/null
+++ b/js/fretboard.js
@@ -0,0 +1,633 @@
+// Fretboard Rendering Functions
+
+// ============================================
+// NOTE FORMATTING
+// ============================================
+function parseNoteWithOctave(noteStr) {
+    // Handle format like "G4", "C#3", "g" (lowercase = high octave)
+    const match = noteStr.match(/^([A-Ga-g][#b]?)(\d)?$/);
+    if (!match) return null;
+
+    let note = match[1];
+    let octave = match[2] ? parseInt(match[2]) : null;
+
+    // Lowercase note = high octave (convention from Python script)
+    if (note === note.toLowerCase() && !octave) {
+        octave = 4; // High octave for 5th string
+    } else if (!octave) {
+        octave = 3; // Default octave
+    }
+
+    return { note: normalizeNote(note), octave };
+}
+
+function formatNoteForDisplay(noteStr) {
+    const parsed = parseNoteWithOctave(noteStr);
+    if (!parsed) return noteStr;
+
+    let note = displayNote(parsed.note);
+    // Convert to solfège if enabled
+    if (state.useSolfege) {
+        note = toSolfege(note);
+    }
+    // Replace # with sharp symbol, b with flat symbol
+    note = note.replace('#', '\u266F').replace('b', '\u266D');
+
+    return `${note}<sub>${parsed.octave}</sub>`;
+}
+
+function formatSimpleNote(note) {
+    // Format a simple note (no octave) for fretboard display
+    let displayed = displayNote(normalizeNote(note));
+    // Convert to solfège if enabled
+    if (state.useSolfege) {
+        displayed = toSolfege(displayed);
+    }
+    return displayed.replace('#', '\u266F').replace('b', '\u266D');
+}
+
+// ============================================
+// RENDERING FUNCTIONS
+// ============================================
+function renderNoteSequence(strings) {
+    return strings.map(s => formatNoteForDisplay(s)).join(' ');
+}
+
+function renderCapoInfo(capoPositions) {
+    return Object.entries(capoPositions)
+        .map(([pos, key]) => pos === '0' ? key : `Capo ${pos} → ${key}`)
+        .join(' | ');
+}
+
+function renderTuningCard(name, tuning) {
+    const featuredBadge = tuning.featured ? ' &#11088;' : '';
+    const warningBadge = tuning.warning ? ' &#9888;' : '';
+    const stringCount = tuning.stringCount || 5;
+    const stringBadge = stringCount === 4 ? '<span class="string-count-badge">4-string</span>' : '';
+
+    // Render nut visual
+    const nutVisual = tuning.changedStrings.map((changed, i) => {
+        const stringNum = stringCount - i;
+        return `<div class="string-circle ${changed ? 'changed' : ''}">${stringNum}</div>`;
+    }).join('');
+
+    return `
+        <div class="tuning-card ${tuning.family}">
+            <div class="tuning-header">
+                <span class="tuning-name">${name}${featuredBadge}${warningBadge}${stringBadge}</span>
+                <span class="genre-tag">${tuning.description}</span>
+            </div>
+            <div class="note-sequence">${renderNoteSequence(tuning.strings)}</div>
+            <div class="nut-visual">
+                ${nutVisual}
+            </div>
+            <div class="tuning-details">
+                <div class="delta-line">&#916;: ${tuning.deltaChanges}</div>
+                <div class="capo-line">${renderCapoInfo(tuning.capoPositions)}</div>
+                <div class="songs-line">${tuning.songs}</div>
+            </div>
+        </div>
+    `;
+}
+
+function renderTunings() {
+    const container = document.getElementById('tuningsContainer');
+    const families = {
+        'c-family': { name: 'C-Based Tunings', intro: 'These tunings center around C tonality, ranging from historical minstrel styles to modern old-time favorites. They create bright, modal sounds ideal for melodic playing.' },
+        'd-family': { name: 'D-Based Tunings', intro: 'D-based tunings create darker, more mysterious tonal landscapes. These tunings are favored for modal old-time pieces and offer rich harmonic possibilities.' },
+        'g-family': { name: 'G-Based Tunings', intro: 'The most common family including the standard bluegrass tuning. These tunings range from bright major chords to modal sounds, offering the widest versatility for different playing styles.' },
+        'other-family': { name: 'Other Tunings (4-String)', intro: '4-string banjo tunings for plectrum and tenor banjos. These instruments are commonly used in jazz, Dixieland, and Irish traditional music.' }
+    };
+
+    let html = '';
+
+    for (const [familyKey, familyInfo] of Object.entries(families)) {
+        if (state.familyFilter !== 'all' && state.familyFilter !== familyKey) continue;
+
+        const tuningsInFamily = Object.entries(TUNINGS_DATA)
+            .filter(([_, t]) => t.family === familyKey);
+
+        if (tuningsInFamily.length === 0) continue;
+
+        html += `
+            <div class="page-title">${familyInfo.name}</div>
+            <div class="family-intro ${familyKey}-intro">
+                <strong>${familyKey.toUpperCase().replace('-', ' ')}:</strong> ${familyInfo.intro}
+            </div>
+            <div class="tunings-grid">
+                ${tuningsInFamily.map(([name, tuning]) => renderTuningCard(name, tuning)).join('')}
+            </div>
+        `;
+    }
+
+    container.innerHTML = html;
+}
+
+// ============================================
+// CHORD INVERSION CALCULATION
+// ============================================
+
+// Calculate all major chord inversions for a root note on strings 4-3-2-1
+// Returns array of inversion objects with fret positions
+function calculateMajorInversions(rootNote, tuningStrings, maxFret = 15) {
+    // Major chord intervals: Root=0, 3rd=4, 5th=7
+    const ROOT = 0, THIRD = 4, FIFTH = 7;
+
+    // Get pitch class of root note
+    const rootPitch = noteToPitchClass(rootNote);
+    const thirdPitch = (rootPitch + THIRD) % 12;
+    const fifthPitch = (rootPitch + FIFTH) % 12;
+
+    // For Open G tuning, strings 4-3-2-1 are indices 1,2,3,4 (D,G,B,D)
+    // We use the 4 main strings for chord shapes
+    const mainStrings = tuningStrings.slice(1, 5); // D, G, B, D
+    const stringOpenPitches = mainStrings.map(s => noteToPitchClass(normalizeNote(s)));
+
+    // Find fret for a target pitch on a given string
+    function findFret(stringPitch, targetPitch, startFret = 0) {
+        const diff = (targetPitch - stringPitch + 12) % 12;
+        let fret = diff;
+        if (fret < startFret) fret += 12;
+        return fret;
+    }
+
+    // Three inversion patterns (intervals on strings 4-3-2-1):
+    // Root Form: 1-3-5-1 (root on string 4)
+    // 1st Inversion: 3-5-1-3 (3rd on string 4)
+    // 2nd Inversion: 5-1-3-5 (5th on string 4)
+    const inversionPatterns = [
+        { name: 'Root Form', type: 'root', intervals: [ROOT, THIRD, FIFTH, ROOT], bassNote: 'Root', color: '#3366cc', label: '1 - 3 - 5 - 1' },
+        { name: '1st Inversion', type: 'first', intervals: [THIRD, FIFTH, ROOT, THIRD], bassNote: '3rd', color: '#cc3333', label: '3 - 5 - 1 - 3' },
+        { name: '2nd Inversion', type: 'second', intervals: [FIFTH, ROOT, THIRD, FIFTH], bassNote: '5th', color: '#cc9900', label: '5 - 1 - 3 - 5' }
+    ];
+
+    const inversions = [];
+
+    // Find all occurrences of each inversion pattern
+    for (const pattern of inversionPatterns) {
+        // Calculate target pitches for each string
+        const targetPitches = pattern.intervals.map(interval => (rootPitch + interval) % 12);
+
+        // Find base fret positions (lowest occurrence)
+        const baseFrets = [];
+        for (let i = 0; i < 4; i++) {
+            baseFrets.push(findFret(stringOpenPitches[i], targetPitches[i], 0));
+        }
+
+        // Find the minimum fret to normalize the shape
+        const minFret = Math.min(...baseFrets.filter(f => f > 0));
+        const maxFretInShape = Math.max(...baseFrets);
+
+        // Find all occurrences within the fret range
+        for (let offset = 0; offset <= maxFret; offset += 12) {
+            const frets = baseFrets.map(f => f === 0 ? 0 : f + offset - (minFret > 0 ? minFret - baseFrets.find(bf => bf === minFret) : 0));
+            const adjustedFrets = baseFrets.map(f => {
+                let adjusted = f + offset;
+                // Handle open strings (fret 0)
+                if (f === 0 && offset === 0) return 0;
+                if (f === 0 && offset > 0) adjusted = 12 + offset;
+                while (adjusted > maxFret + 5) adjusted -= 12;
+                return adjusted;
+            });
+
+            // Check if this shape is within the displayable range
+            const shapeFrets = adjustedFrets.filter(f => f >= 0);
+            if (shapeFrets.length > 0 && Math.min(...shapeFrets) <= maxFret) {
+                inversions.push({
+                    ...pattern,
+                    frets: adjustedFrets,
+                    baseFret: Math.min(...adjustedFrets.filter(f => f > 0)) || 0
+                });
+            }
+        }
+    }
+
+    // Sort by base fret position
+    inversions.sort((a, b) => a.baseFret - b.baseFret);
+
+    return inversions;
+}
+
+// Render chord inversions on fretboard SVG
+function renderInversionsOnFretboard(svg, rootNote, tuning, fbLeft, fbTop, stringSpacing, fretHeight, numFrets, svgWidth) {
+    const inversions = calculateMajorInversions(rootNote, tuning.strings, numFrets);
+    const displayedNote = displayNote(rootNote);
+
+    // Filter inversions that are fully visible
+    const visibleInversions = inversions.filter(inv => {
+        const maxFretInShape = Math.max(...inv.frets);
+        return maxFretInShape <= numFrets && inv.baseFret >= 1;
+    });
+
+    // Render each inversion
+    visibleInversions.forEach((inv, idx) => {
+        // Calculate positions (strings 4-3-2-1 are at indices 1,2,3,4 in the display)
+        const positions = [];
+        for (let i = 0; i < 4; i++) {
+            const stringIndex = i + 1; // Skip 5th string (index 0)
+            const x = fbLeft + stringIndex * stringSpacing;
+            const fret = inv.frets[i];
+            const y = fbTop + (fret - 0.5) * fretHeight;
+            positions.push({ x, y, fret, stringIndex });
+        }
+
+        // Draw colored overlay connecting the notes (triangle or polygon)
+        const validPositions = positions.filter(p => p.fret > 0 && p.fret <= numFrets);
+        if (validPositions.length >= 3) {
+            // Create polygon points
+            const points = validPositions.map(p => `${p.x},${p.y}`).join(' ');
+            svg += `<polygon points="${points}" fill="${inv.color}" fill-opacity="0.25" stroke="${inv.color}" stroke-width="2"/>`;
+        }
+
+        // Draw dots on chord positions
+        positions.forEach(pos => {
+            if (pos.fret > 0 && pos.fret <= numFrets) {
+                svg += `<circle cx="${pos.x}" cy="${pos.y}" r="12" fill="${inv.color}" fill-opacity="0.8" stroke="white" stroke-width="2"/>`;
+            }
+        });
+
+        // Annotation box on the right side
+        const annotationX = fbLeft + 5 * stringSpacing + 30;
+        const annotationY = fbTop + (inv.baseFret - 0.5) * fretHeight;
+
+        if (inv.baseFret <= numFrets - 2) {
+            // Background for annotation
+            svg += `<rect x="${annotationX}" y="${annotationY - 30}" width="140" height="70" fill="white" stroke="${inv.color}" stroke-width="2" rx="5"/>`;
+
+            // Chord name and inversion type
+            svg += `<text x="${annotationX + 70}" y="${annotationY - 12}" text-anchor="middle" font-size="12" font-weight="bold" fill="${inv.color}">"${displayedNote}" CHORD</text>`;
+            svg += `<text x="${annotationX + 70}" y="${annotationY + 5}" text-anchor="middle" font-size="11" font-weight="bold" fill="#333">${inv.name.toUpperCase()}</text>`;
+
+            // Bass note info
+            svg += `<text x="${annotationX + 70}" y="${annotationY + 20}" text-anchor="middle" font-size="10" fill="#666">${inv.bassNote} is bass note</text>`;
+
+            // Interval structure
+            svg += `<text x="${annotationX + 70}" y="${annotationY + 35}" text-anchor="middle" font-size="10" fill="#666">[${inv.label}]</text>`;
+        }
+    });
+
+    // Draw 5-fret rule connectors between consecutive inversions
+    for (let i = 0; i < visibleInversions.length - 1; i++) {
+        const current = visibleInversions[i];
+        const next = visibleInversions[i + 1];
+
+        // The rule: 3rd string of current -> 4th string of next (5 frets higher)
+        // String 3 is at index 2 (0-indexed in our positions), String 4 is at index 1
+        const fromStringIdx = 2; // 3rd string (G string)
+        const toStringIdx = 1;   // 4th string (D string)
+
+        const fromX = fbLeft + (fromStringIdx + 1) * stringSpacing;
+        const fromY = fbTop + (current.frets[fromStringIdx] - 0.5) * fretHeight;
+        const toX = fbLeft + (toStringIdx + 1) * stringSpacing;
+        const toY = fbTop + (next.frets[toStringIdx] - 0.5) * fretHeight;
+
+        if (current.frets[fromStringIdx] <= numFrets && next.frets[toStringIdx] <= numFrets) {
+            // Draw arrow
+            svg += `<line x1="${fromX}" y1="${fromY}" x2="${toX}" y2="${toY}" stroke="#cc0000" stroke-width="3" marker-end="url(#arrowhead)"/>`;
+
+            // Label showing "5 frets"
+            const midX = (fromX + toX) / 2 - 25;
+            const midY = (fromY + toY) / 2;
+            svg += `<text x="${midX}" y="${midY}" font-size="10" font-weight="bold" fill="#cc0000">5 frets</text>`;
+        }
+    }
+
+    // Add cycling indicator at bottom
+    if (visibleInversions.length > 0) {
+        const bottomY = fbTop + numFrets * fretHeight + 10;
+        svg += `<text x="${fbLeft + 2.5 * stringSpacing}" y="${bottomY}" text-anchor="middle" font-size="11" fill="#666">↓ Inversions keep cycling ↓</text>`;
+    }
+
+    return svg;
+}
+
+// ============================================
+// FRETBOARD SVG GENERATOR (Enhanced from Python)
+// ============================================
+function generateFretboardSVG(tuningName) {
+    const tuning = TUNINGS_DATA[tuningName];
+    if (!tuning) return '';
+
+    const numFrets = state.fretCount;
+    const capo = state.capoPosition;
+    const showNotes = state.showNotes;
+
+    const strings = tuning.strings;
+    const numStrings = strings.length;
+    const shortStringIdx = tuning.shortStringIndex;
+    const shortStringStart = tuning.shortStringStartFret;
+
+    // Determine which strings should be lowercase (first occurrence of duplicate notes)
+    // e.g., for tuning with notes F C F A C, the first F and first C should be lowercase: f c F A C
+    const lowercaseIndices = new Set();
+    const seenNotes = new Map(); // normalized note -> first index
+    for (let i = 0; i < strings.length; i++) {
+        const normalized = normalizeNote(strings[i]).toUpperCase();
+        if (!seenNotes.has(normalized)) {
+            seenNotes.set(normalized, i);
+        } else {
+            // This note has appeared before, mark the first occurrence as lowercase
+            lowercaseIndices.add(seenNotes.get(normalized));
+        }
+    }
+
+    // SVG dimensions
+    const marginLeft = 100;
+    const marginRight = 150;
+    const marginTop = capo > 0 ? 160 : 140;  // Extra space for effective tuning display
+    const marginBottom = 80;
+    const stringSpacing = 55;
+    const fretHeight = 50;
+
+    const fretboardWidth = (numStrings - 1) * stringSpacing;
+    const fretboardHeight = numFrets * fretHeight;
+    const svgWidth = marginLeft + fretboardWidth + marginRight;
+    const svgHeight = marginTop + fretboardHeight + marginBottom;
+
+    const fbLeft = marginLeft;
+    const fbTop = marginTop;
+    const fbRight = fbLeft + fretboardWidth;
+    const bottomY = fbTop + fretboardHeight;
+
+    // Fret markers
+    const markerFrets = [3, 5, 7, 10, 12, 15, 17];
+    const doubleMarkerFrets = [12];
+
+    let svg = `<svg class="fretboard-svg" width="${svgWidth}" height="${svgHeight}" xmlns="http://www.w3.org/2000/svg">`;
+
+    // Styles and definitions
+    svg += `
+    <defs>
+        <marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
+            <polygon points="0 0, 10 3.5, 0 7" fill="#cc0000"/>
+        </marker>
+    </defs>
+    <style>
+        .title { font-family: Georgia, serif; font-size: 24px; font-weight: bold; fill: #000; }
+        .subtitle { font-family: Georgia, serif; font-size: 16px; font-style: italic; fill: #333; }
+        .string-label { font-family: 'Courier New', monospace; font-size: 18px; font-weight: bold; fill: #000; }
+        .fret-number { font-family: Arial, sans-serif; font-size: 16px; font-weight: bold; fill: #333; }
+        .note { font-family: Arial, sans-serif; font-size: 12px; font-weight: bold; fill: #000; }
+        .note-circle { fill: white; stroke: #000; stroke-width: 1.5; }
+        .note-circle-natural { fill: white; stroke: #000; stroke-width: 4; }
+        .fret-line { stroke: #000; stroke-width: 2; }
+        .string-line { stroke: #444; stroke-width: 1.5; }
+        .nut { stroke: #000; stroke-width: 6; }
+        .fret-marker { fill: #ddd; stroke: none; }
+        .peg { fill: #333; stroke: #000; stroke-width: 1; }
+        .ghost-string { stroke: #999; stroke-width: 1; stroke-dasharray: 3,3; }
+        .fret-marker-symbol { fill: #000; stroke: none; }
+    </style>`;
+
+    // Background
+    svg += `<rect width="100%" height="100%" fill="white"/>`;
+
+    // Title - use lowercase for first occurrence of duplicate notes (e.g., fCFAC -> fcFAC)
+    const tuningStr = strings.map((s, idx) => {
+        let note = displayNote(normalizeNote(s));
+        // Convert to solfège if enabled
+        if (state.useSolfege) {
+            note = toSolfege(note);
+        }
+        // Use lowercase if this index is marked as first occurrence of a duplicate
+        return lowercaseIndices.has(idx) ? note.toLowerCase() : note;
+    }).join('');
+
+    // Calculate effective tuning with capo
+    let effectiveTuningStr = '';
+    if (capo > 0) {
+        effectiveTuningStr = strings.map((s, idx) => {
+            // For short string, calculate from its actual starting point
+            let effectiveCapo = capo;
+            if (idx === shortStringIdx && shortStringStart !== null) {
+                // Short string: capo only affects it if capo >= shortStringStart
+                if (capo < shortStringStart) {
+                    effectiveCapo = 0; // Capo doesn't affect short string
+                } else {
+                    effectiveCapo = capo - (shortStringStart - 1);
+                }
+            }
+            const transposedNote = getNoteAtFret(s, effectiveCapo);
+            let note = displayNote(normalizeNote(transposedNote));
+            if (state.useSolfege) {
+                note = toSolfege(note);
+            }
+            return lowercaseIndices.has(idx) ? note.toLowerCase() : note;
+        }).join('');
+    }
+
+    svg += `<text x="${svgWidth / 2}" y="45" class="title" text-anchor="middle">Banjo Fretboard: ${tuningName}</text>`;
+
+    // Subtitle with effective tuning when capo is applied
+    if (capo > 0) {
+        svg += `<text x="${svgWidth / 2}" y="70" class="subtitle" text-anchor="middle">Open Tuning: ${tuningStr} — ${tuning.description}</text>`;
+        svg += `<text x="${svgWidth / 2}" y="90" class="subtitle" text-anchor="middle" style="fill: #c00; font-weight: bold;">Capo ${capo}: Effective Tuning: ${effectiveTuningStr}</text>`;
+    } else {
+        svg += `<text x="${svgWidth / 2}" y="70" class="subtitle" text-anchor="middle">Tuning: ${tuningStr} — ${tuning.description}</text>`;
+    }
+
+    // Fret markers (on fretboard face)
+    for (const fret of markerFrets) {
+        if (fret <= numFrets) {
+            const y = fbTop + (fret - 0.5) * fretHeight;
+            if (doubleMarkerFrets.includes(fret)) {
+                svg += `<circle cx="${fbLeft + fretboardWidth/2 - 15}" cy="${y}" r="7" class="fret-marker"/>`;
+                svg += `<circle cx="${fbLeft + fretboardWidth/2 + 15}" cy="${y}" r="7" class="fret-marker"/>`;
+            } else {
+                svg += `<circle cx="${fbLeft + fretboardWidth/2}" cy="${y}" r="7" class="fret-marker"/>`;
+            }
+        }
+    }
+
+    // Nut
+    if (shortStringIdx !== null && shortStringStart !== null) {
+        const nutStartX = fbLeft + stringSpacing - 10;
+        svg += `<line x1="${nutStartX}" y1="${fbTop}" x2="${fbRight + 10}" y2="${fbTop}" class="nut"/>`;
+    } else {
+        svg += `<line x1="${fbLeft - 10}" y1="${fbTop}" x2="${fbRight + 10}" y2="${fbTop}" class="nut"/>`;
+    }
+
+    // Fret lines with ordinal labels on right
+    for (let fret = 1; fret <= numFrets; fret++) {
+        const y = fbTop + fret * fretHeight;
+
+        let lineStartX, lineEndX;
+        if (shortStringIdx !== null && shortStringStart !== null && fret < shortStringStart) {
+            lineStartX = fbLeft + stringSpacing - 10;
+            lineEndX = fbRight + 10;
+        } else {
+            lineStartX = fbLeft - 10;
+            lineEndX = fbRight + 10;
+        }
+
+        // Extend line for marker frets
+        if (markerFrets.includes(fret)) {
+            lineEndX += 20;
+            if (fret === 5) {
+                // 5th fret extends to the left edge (where 5th string starts)
+                lineStartX = 90;
+            }
+        }
+
+        svg += `<line x1="${lineStartX}" y1="${y}" x2="${lineEndX}" y2="${y}" class="fret-line"/>`;
+
+        // Ordinal fret labels on right
+        if (markerFrets.includes(fret)) {
+            const ordinal = fret + (fret === 1 ? 'st' : fret === 2 ? 'nd' : fret === 3 ? 'rd' : 'th') + ' fret';
+            const textX = fbRight + 70;
+            svg += `<text x="${textX}" y="${y}" class="fret-number" text-anchor="middle" dominant-baseline="middle">${ordinal}</text>`;
+
+            // Marker symbols
+            const symbolX = textX + 50;
+            if (fret === 5) {
+                // Star for 5th fret
+                const starPoints = `${symbolX},${y-7} ${symbolX+2},${y-2} ${symbolX+7},${y-2} ${symbolX+3},${y+1} ${symbolX+5},${y+6} ${symbolX},${y+3} ${symbolX-5},${y+6} ${symbolX-3},${y+1} ${symbolX-7},${y-2} ${symbolX-2},${y-2}`;
+                svg += `<polygon points="${starPoints}" class="fret-marker-symbol"/>`;
+            } else if (fret === 12) {
+                // Double circles
+                svg += `<circle cx="${symbolX - 8}" cy="${y}" r="6" class="fret-marker-symbol"/>`;
+                svg += `<circle cx="${symbolX + 8}" cy="${y}" r="6" class="fret-marker-symbol"/>`;
+            } else {
+                // Single circle
+                svg += `<circle cx="${symbolX}" cy="${y}" r="6" class="fret-marker-symbol"/>`;
+            }
+        }
+    }
+
+    // Neck edges with diagonal for short string
+    if (shortStringIdx !== null && shortStringStart !== null) {
+        const nutLeftX = fbLeft + stringSpacing - 10;
+        const fret4Y = fbTop + 4 * fretHeight;
+        const fret5Y = fbTop + 5 * fretHeight;
+        const leftEdgeX = fbLeft - 10;
+
+        // Ghost string line (dotted)
+        svg += `<line x1="${fbLeft}" y1="${fbTop}" x2="${fbLeft}" y2="${fret5Y - fretHeight}" class="ghost-string"/>`;
+
+        // Polyline neck edge
+        svg += `<polyline points="${nutLeftX},${fbTop} ${nutLeftX},${fret4Y} ${leftEdgeX},${fret5Y} ${leftEdgeX},${bottomY}" style="stroke:#000; stroke-width:3; fill:none;"/>`;
+        svg += `<line x1="${fbRight + 10}" y1="${fbTop}" x2="${fbRight + 10}" y2="${bottomY}" style="stroke:#000; stroke-width:3;"/>`;
+        svg += `<line x1="${leftEdgeX}" y1="${bottomY}" x2="${fbRight + 10}" y2="${bottomY}" style="stroke:#000; stroke-width:3;"/>`;
+    } else {
+        // 4-string: rectangular edges
+        svg += `<line x1="${fbLeft - 10}" y1="${fbTop}" x2="${fbLeft - 10}" y2="${bottomY}" style="stroke:#000; stroke-width:3;"/>`;
+        svg += `<line x1="${fbRight + 10}" y1="${fbTop}" x2="${fbRight + 10}" y2="${bottomY}" style="stroke:#000; stroke-width:3;"/>`;
+        svg += `<line x1="${fbLeft - 10}" y1="${bottomY}" x2="${fbRight + 10}" y2="${bottomY}" style="stroke:#000; stroke-width:3;"/>`;
+    }
+
+    // String lines
+    for (let i = 0; i < numStrings; i++) {
+        const x = fbLeft + i * stringSpacing;
+
+        if (i === shortStringIdx && shortStringStart !== null) {
+            const startY = fbTop + (shortStringStart - 1) * fretHeight;
+            svg += `<line x1="${x}" y1="${startY}" x2="${x}" y2="${bottomY}" class="string-line" style="stroke-width:2;"/>`;
+
+            // Peg indicator
+            const pegY = startY - 50;
+            const pegHeadX = fbLeft - 10;
+            const pegShaftStartX = fbLeft + 30;
+            svg += `<rect x="${pegHeadX}" y="${pegY - 5}" width="${pegShaftStartX - pegHeadX}" height="8" class="peg" rx="2"/>`;
+            svg += `<circle cx="${pegHeadX}" cy="${pegY}" r="10" class="peg"/>`;
+            svg += `<circle cx="${pegHeadX}" cy="${pegY}" r="4" fill="white"/>`;
+        } else {
+            svg += `<line x1="${x}" y1="${fbTop}" x2="${x}" y2="${bottomY}" class="string-line"/>`;
+        }
+    }
+
+    // String labels at top (lowercase for first occurrence of duplicate notes)
+    for (let i = 0; i < numStrings; i++) {
+        const x = fbLeft + i * stringSpacing;
+        const stringNumber = numStrings - i;
+        const note = strings[i];
+
+        // Use lowercase if this is the first occurrence of a duplicate note
+        const baseNote = normalizeNote(note);
+        let displayedBase = displayNote(baseNote);
+        // Convert to solfège if enabled
+        if (state.useSolfege) {
+            displayedBase = toSolfege(displayedBase);
+        }
+        // Apply lowercase if marked, then replace symbols
+        let displayed = lowercaseIndices.has(i) ? displayedBase.toLowerCase() : displayedBase;
+        displayed = displayed.replace('#', '\u266F').replace('b', '\u266D');
+
+        const numberY = fbTop - 35;
+        const labelY = fbTop - 12;
+
+        svg += `<text x="${x}" y="${numberY}" class="fret-number" text-anchor="middle">${stringNumber}</text>`;
+        svg += `<text x="${x}" y="${labelY}" class="string-label" text-anchor="middle">${displayed}</text>`;
+    }
+
+    // Notes at each fret position
+    if (showNotes) {
+        for (let fret = 1; fret <= numFrets; fret++) {
+            const y = fbTop + (fret - 0.5) * fretHeight;
+
+            // Check if this fret is above (behind) the capo
+            const isBehindCapo = capo > 0 && fret < capo;
+
+            for (let i = 0; i < numStrings; i++) {
+                const x = fbLeft + i * stringSpacing;
+                const openNote = strings[i];
+
+                // Handle short string
+                if (i === shortStringIdx && shortStringStart !== null) {
+                    if (fret < shortStringStart) {
+                        continue; // No notes in empty space
+                    }
+                    // Calculate effective fret for short string
+                    const effectiveFret = fret - (shortStringStart - 1);
+                    const note = getNoteAtFret(openNote, effectiveFret);
+                    const displayedNote = formatSimpleNote(note);
+
+                    // Gray out notes behind capo, use 8-ball style for natural notes
+                    const isNatural = isNaturalNote(note);
+                    const circleClass = isNatural ? 'note-circle-natural' : 'note-circle';
+                    const circleStyle = isBehindCapo ? 'fill: #e0e0e0; stroke: #bbb;' : '';
+                    const textStyle = isBehindCapo ? 'fill: #aaa;' : '';
+
+                    svg += `<circle cx="${x}" cy="${y}" r="16" class="${circleClass}" style="${circleStyle}"/>`;
+                    svg += `<text x="${x}" y="${y + 5}" class="note" text-anchor="middle" style="${textStyle}">${displayedNote}</text>`;
+                } else {
+                    const note = getNoteAtFret(openNote, fret);
+                    const displayedNote = formatSimpleNote(note);
+
+                    // Gray out notes behind capo, use 8-ball style for natural notes
+                    const isNatural = isNaturalNote(note);
+                    const circleClass = isNatural ? 'note-circle-natural' : 'note-circle';
+                    const circleStyle = isBehindCapo ? 'fill: #e0e0e0; stroke: #bbb;' : '';
+                    const textStyle = isBehindCapo ? 'fill: #aaa;' : '';
+
+                    svg += `<circle cx="${x}" cy="${y}" r="16" class="${circleClass}" style="${circleStyle}"/>`;
+                    svg += `<text x="${x}" y="${y + 5}" class="note" text-anchor="middle" style="${textStyle}">${displayedNote}</text>`;
+                }
+            }
+        }
+    }
+
+    // Capo indicator
+    if (capo > 0) {
+        const capoY = fbTop + (capo - 0.5) * fretHeight;
+        svg += `<rect x="${fbLeft - 15}" y="${capoY - 8}" width="${fretboardWidth + 30}" height="16" fill="#333" rx="4" opacity="0.9"/>`;
+        svg += `<text x="${fbLeft + fretboardWidth/2}" y="${capoY + 4}" text-anchor="middle" font-size="10" fill="white" font-weight="bold">CAPO ${capo}</text>`;
+    }
+
+    // Chord inversions overlay (if enabled)
+    if (state.showInversions && numStrings >= 5) {
+        svg = renderInversionsOnFretboard(svg, state.inversionRootNote, tuning, fbLeft, fbTop, stringSpacing, fretHeight, numFrets, svgWidth);
+    }
+
+    // Footer
+    const footerY = fbTop + fretboardHeight + 30;
+    svg += `<rect x="${marginLeft-50}" y="${footerY}" width="${fretboardWidth+150}" height="30" rx="5" fill="#f5f5f5" stroke="#ccc"/>`;
+    const footerLine1 = shortStringStart
+        ? `String 1 (far right) = 1st string • 5th string starts at fret ${shortStringStart}`
+        : `String 1 (far right) = 1st string • ${numStrings}-string banjo`;
+    svg += `<text x="${svgWidth/2}" y="${footerY + 12}" text-anchor="middle" font-size="10" fill="#555">${footerLine1}</text>`;
+    svg += `<text x="${svgWidth/2}" y="${footerY + 24}" text-anchor="middle" font-size="10" fill="#555">Fret markers: 3,5,7,10,12,15,17 • Peg indicates 5th string tuning point</text>`;
+
+    svg += '</svg>';
+    return svg;
+}
+
diff --git a/js/tunings.js b/js/tunings.js
new file mode 100644
index 0000000..76452c5
--- /dev/null
+++ b/js/tunings.js
@@ -0,0 +1,306 @@
+// Banjo Tunings Data
+// 24 tunings including 4-string
+
+const TUNINGS_DATA = {
+    // C-Based Tunings
+    "Minstrel Tuning": {
+        strings: ["f", "C", "F", "A", "c"],
+        family: "c-family",
+        description: "Historical",
+        referenceTuning: "Standard F",
+        deltaChanges: "From Standard F: 4th↓C",
+        capoPositions: { 0: "Not commonly capoed" },
+        songs: "Briggs manual, Coon Hunt",
+        changedStrings: [true, true, true, true, true],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "Naomi Weiss": {
+        strings: ["g", "C", "G", "A", "d"],
+        family: "c-family",
+        description: "Song-Specific",
+        referenceTuning: "Minstrel",
+        deltaChanges: "From Minstrel: 5th↑G, 3rd↑G, 2nd→A (same), 1st↑D",
+        capoPositions: { 2: "Key of D" },
+        songs: '"Naomi Weiss"',
+        changedStrings: [false, false, true, false, true],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "Little Birdie": {
+        strings: ["e", "C", "G", "A", "d"],
+        family: "c-family",
+        description: "Appalachian",
+        referenceTuning: "Naomi Weiss",
+        deltaChanges: "From Naomi Weiss: 5th↓E",
+        capoPositions: { 2: "Key of F# (uncommon)" },
+        songs: '"Little Birdie"',
+        changedStrings: [true, false, false, false, false],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "Hook-and-Line": {
+        strings: ["g", "C", "G", "c", "d"],
+        family: "c-family",
+        description: "Open C Variant",
+        referenceTuning: "Little Birdie",
+        deltaChanges: "From Little Birdie: 5th↑G, 2nd↑C",
+        capoPositions: { 2: "Key of D", 5: "Key of F" },
+        songs: '"Hook and Line"',
+        changedStrings: [true, false, false, true, false],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "Cackling Hen": {
+        strings: ["g", "C", "G", "c", "e"],
+        family: "c-family",
+        description: "Open C Major",
+        referenceTuning: "Hook-and-Line",
+        deltaChanges: "From Hook-and-Line: 1st↑E",
+        capoPositions: { 0: "Key of C", 2: "Key of D" },
+        songs: "Open C tuning, Irish/Celtic",
+        changedStrings: [false, false, false, false, true],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "Darling Cora": {
+        strings: ["g", "C", "G", "c", "c"],
+        family: "c-family",
+        description: "Drone Heavy (Triple C)",
+        referenceTuning: "Cackling Hen",
+        deltaChanges: "From Cackling Hen: 1st↓C",
+        capoPositions: { 2: "Key of D", 5: "Key of F" },
+        songs: '"Darling Cora"',
+        changedStrings: [false, false, false, false, true],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "Double C": {
+        strings: ["g", "C", "G", "c", "d"],
+        family: "c-family",
+        description: "Old-Time Standard",
+        referenceTuning: "Open G",
+        deltaChanges: "From Open G: 4th↓C, 2nd↑C",
+        capoPositions: { 2: "Key of D", 5: "Key of F" },
+        songs: "Wildwood Flower, Billy in the Lowground",
+        changedStrings: [false, true, false, true, false],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "Standard C": {
+        strings: ["g", "C", "G", "B", "d"],
+        family: "c-family",
+        description: "C Tuning for Old-Time",
+        referenceTuning: "Open G",
+        deltaChanges: "From Open G: 4th↓C",
+        capoPositions: { 2: "Key of D", 5: "Key of F" },
+        songs: "Old-time repertoire",
+        changedStrings: [false, true, false, false, false],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+
+    // D-Based Tunings
+    "Moonshiner": {
+        strings: ["g", "D", "G", "A", "d"],
+        family: "d-family",
+        description: "Modal/Dark",
+        referenceTuning: "Standard F",
+        deltaChanges: "From Standard F: 4th↑D, 3rd↑G, 2nd→A (same), 1st↑D",
+        capoPositions: { 2: "Key of A", 5: "Key of C" },
+        songs: '"Moonshiner"',
+        changedStrings: [false, true, false, true, true],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "German War": {
+        strings: ["g", "D", "G", "A", "f#"],
+        family: "d-family",
+        description: "Historical",
+        referenceTuning: "Moonshiner",
+        deltaChanges: "From Moonshiner: 1st↓F#",
+        capoPositions: { 2: "Key of A (modal)" },
+        songs: "German war songs",
+        changedStrings: [false, false, false, false, true],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "Reuben": {
+        strings: ["g", "D", "F#", "A", "f#"],
+        family: "d-family",
+        description: "Song-Specific",
+        referenceTuning: "German War",
+        deltaChanges: "From German War: 3rd↓F#",
+        capoPositions: { 2: "Key of A (modal)" },
+        songs: '"Reuben"',
+        changedStrings: [false, false, true, false, false],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "F Tuning": {
+        strings: ["f", "D", "G", "c", "d"],
+        family: "d-family",
+        description: "Cumberland Gap",
+        referenceTuning: "Reuben",
+        deltaChanges: "From Reuben: 5th↓F, 3rd↑G, 2nd↑C, 1st↑D",
+        capoPositions: { 2: "Key of G", 5: "Key of Bb" },
+        songs: "Cumberland Gap, Drunkard's Doom",
+        changedStrings: [true, false, true, true, true],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "East Virginia": {
+        strings: ["f", "F", "G", "c", "d"],
+        family: "d-family",
+        description: "George Gibson Style",
+        referenceTuning: "F Tuning",
+        deltaChanges: "From F Tuning: 4th↑F",
+        capoPositions: { 2: "Key of G", 5: "Key of Bb" },
+        songs: "George Gibson style",
+        changedStrings: [false, true, false, false, false],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+
+    // G-Based Tunings
+    "Standard (F)": {
+        strings: ["f", "C", "F", "A", "c"],
+        family: "g-family",
+        description: "Historical Reference",
+        referenceTuning: null,
+        deltaChanges: "(Reference tuning for historical context)",
+        capoPositions: { 2: "Key of G", 5: "Key of Bb" },
+        songs: "General playing, historical reference",
+        changedStrings: [false, false, false, false, false],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "Open G": {
+        strings: ["g", "D", "G", "B", "d"],
+        family: "g-family",
+        description: "Bluegrass Standard",
+        referenceTuning: null,
+        deltaChanges: "(Modern standard reference - most common)",
+        capoPositions: { 2: "Key of A", 5: "Key of C", 7: "Key of D" },
+        songs: "Bill Cheatum, Old Joe Clark, Cripple Creek",
+        changedStrings: [true, true, true, true, true],
+        featured: true,
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "Open G (Low G)": {
+        strings: ["G", "D", "G", "B", "d"],
+        family: "g-family",
+        description: "Open G with Low 5th",
+        referenceTuning: "Open G",
+        deltaChanges: "From Open G: 5th↓G (one octave lower)",
+        capoPositions: { 2: "Key of A", 5: "Key of C" },
+        songs: "Low drone sound",
+        changedStrings: [true, false, false, false, false],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "Open A": {
+        strings: ["a", "E", "A", "C#", "e"],
+        family: "g-family",
+        description: "High Tension",
+        referenceTuning: "Open G",
+        deltaChanges: "From Open G: all strings ↑2 frets (whole step)",
+        capoPositions: { 2: "Key of B", 5: "Key of D" },
+        songs: "Tunes in A key - requires light gauge strings!",
+        changedStrings: [true, true, true, true, true],
+        warning: true,
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "G Variant": {
+        strings: ["g", "D", "G", "A", "d"],
+        family: "g-family",
+        description: "Modal G",
+        referenceTuning: "Open G",
+        deltaChanges: "From Open G: 2nd↓A",
+        capoPositions: { 2: "Key of A", 5: "Key of C" },
+        songs: "Leather Britches, Texas, Frosty Morning",
+        changedStrings: [false, false, false, true, false],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "G Modal (Sawmill)": {
+        strings: ["g", "D", "G", "c", "d"],
+        family: "g-family",
+        description: "Mountain/Modal",
+        referenceTuning: "Open G",
+        deltaChanges: "From Open G: 2nd↑C",
+        capoPositions: { 2: "Key of A", 5: "Key of C" },
+        songs: "Modal tunes, F key songs",
+        changedStrings: [false, false, false, true, false],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "Open D": {
+        strings: ["a", "D", "F#", "A", "d"],
+        family: "g-family",
+        description: "Blues/Folk (Graveyard)",
+        referenceTuning: "Open G",
+        deltaChanges: "From Open G: all strings adjusted for D major",
+        capoPositions: { 2: "Key of E", 3: "Key of F", 5: "Key of G" },
+        songs: 'Most D key tunes, "Graveyard Tuning" - use light strings!',
+        changedStrings: [true, true, true, true, true],
+        warning: true,
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "D Modal": {
+        strings: ["a", "D", "G", "A", "d"],
+        family: "g-family",
+        description: "Celtic/Medieval",
+        referenceTuning: "Open D",
+        deltaChanges: "From Open D: 3rd↓G",
+        capoPositions: { 2: "Key of E (modal)", 5: "Key of G (modal)" },
+        songs: "D modal tunes, medieval tonality",
+        changedStrings: [false, false, true, false, false],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+    "D Tuning (Reno)": {
+        strings: ["a", "D", "G", "B", "e"],
+        family: "g-family",
+        description: "Don Reno Style",
+        referenceTuning: "Open G",
+        deltaChanges: "From Open G: 5th↑A, 1st↑E",
+        capoPositions: { 2: "Key of E", 5: "Key of G" },
+        songs: "Don Reno repertoire",
+        changedStrings: [true, false, false, false, true],
+        shortStringIndex: 0,
+        shortStringStartFret: 6
+    },
+
+    // Other / 4-String Tunings
+    "Plectrum C": {
+        strings: ["C", "G", "B", "d"],
+        family: "other-family",
+        description: "4-String Plectrum",
+        referenceTuning: null,
+        deltaChanges: "(4-string plectrum standard)",
+        capoPositions: { 0: "Key of C" },
+        songs: "Jazz, Dixieland",
+        changedStrings: [false, false, false, false],
+        shortStringIndex: null,
+        shortStringStartFret: null,
+        stringCount: 4
+    },
+    "Tenor (Standard)": {
+        strings: ["C", "G", "d", "a"],
+        family: "other-family",
+        description: "4-String Tenor CGDA",
+        referenceTuning: null,
+        deltaChanges: "(4-string tenor standard - fifths tuning)",
+        capoPositions: { 0: "Key of C/G" },
+        songs: "Irish, Jazz, Traditional",
+        changedStrings: [false, false, false, false],
+        shortStringIndex: null,
+        shortStringStartFret: null,
+        stringCount: 4
+    }
+};
+
diff --git a/js/utils.js b/js/utils.js
new file mode 100644
index 0000000..350dac2
--- /dev/null
+++ b/js/utils.js
@@ -0,0 +1,65 @@
+// Chromatic Scale & Note Utilities
+
+const CHROMATIC_SCALE = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
+
+// Normalize note names (flats to sharps, handle case)
+const NOTE_MAP = {
+    'c': 'C', 'c#': 'C#', 'db': 'C#', 'Db': 'C#',
+    'd': 'D', 'd#': 'D#', 'eb': 'D#', 'Eb': 'D#',
+    'e': 'E', 'fb': 'E', 'Fb': 'E',
+    'f': 'F', 'e#': 'F', 'E#': 'F', 'f#': 'F#', 'gb': 'F#', 'Gb': 'F#',
+    'g': 'G', 'g#': 'G#', 'ab': 'G#', 'Ab': 'G#',
+    'a': 'A', 'a#': 'A#', 'bb': 'A#', 'Bb': 'A#',
+    'b': 'B', 'cb': 'B', 'Cb': 'B', 'b#': 'C', 'B#': 'C',
+    'C': 'C', 'C#': 'C#', 'D': 'D', 'D#': 'D#', 'E': 'E',
+    'F': 'F', 'F#': 'F#', 'G': 'G', 'G#': 'G#', 'A': 'A', 'A#': 'A#', 'B': 'B'
+};
+
+// Display note mapping - prefer flats for G# and A# (as per user's Python script)
+const DISPLAY_NOTE_MAP = {
+    'G#': 'Ab',
+    'A#': 'Bb'
+    // Keep D#, C#, F# as sharps
+};
+
+// Solfège mapping: C=Do, D=Re, E=Mi, F=Fa, G=Sol, A=La, B=Si
+const SOLFEGE_MAP = {
+    'C': 'Do', 'D': 'Re', 'E': 'Mi', 'F': 'Fa',
+    'G': 'Sol', 'A': 'La', 'B': 'Si'
+};
+
+function normalizeNote(note) {
+    return NOTE_MAP[note] || note.toUpperCase();
+}
+
+// Check if a note is natural (no sharps or flats)
+function isNaturalNote(note) {
+    const normalized = normalizeNote(note);
+    return ['C', 'D', 'E', 'F', 'G', 'A', 'B'].includes(normalized);
+}
+
+function displayNote(note) {
+    // Convert internal sharp note to preferred display (mix of flats and sharps)
+    if (DISPLAY_NOTE_MAP[note]) {
+        return DISPLAY_NOTE_MAP[note];
+    }
+    return note;
+}
+
+function toSolfege(note) {
+    // Convert a note to solfège notation
+    // Handle sharps/flats: C# -> Do#, Db -> Reb, etc.
+    const baseNote = note.charAt(0).toUpperCase();
+    const accidental = note.slice(1); // # or b or empty
+    const solfege = SOLFEGE_MAP[baseNote] || baseNote;
+    return solfege + accidental;
+}
+
+function getNoteAtFret(openNote, fret) {
+    const normalized = normalizeNote(openNote);
+    const startIndex = CHROMATIC_SCALE.indexOf(normalized);
+    if (startIndex === -1) return openNote;
+    const newIndex = (startIndex + fret) % 12;
+    return CHROMATIC_SCALE[newIndex];
+}
+