Add interactive banjo tunings reference web application
Convert static HTML to a fully interactive single-page application with: - All 19 tunings organized by family (C-Based, D-Based, G-Based) - Interactive controls: family filter, capo position (0-12), fret count selector - Sharps/flats toggle with persistent localStorage settings - SVG-based fretboard diagram generator with 5th string handling - Capo simulation engine showing transposed effective tuning - Three tabs: Tunings, Fretboards, Reference - Print-optimized CSS with A4 formatting - Zero external dependencies - pure HTML/CSS/JavaScript https://claude.ai/code/session_01JfA4LA5EzCPiZHbWJ2kyvj
+1477 · 1 file changed
-
index.html+1477 −0
@@ -0,0 +1,1477 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <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; + --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; + } + + @media print { + body { + background: white; + } + + .no-print { + display: none !important; + } + + .print-only { + display: block !important; + } + + .page-break { + page-break-after: always; + } + + .tuning-card { + break-inside: avoid; + } + + .app-container { + max-width: 100%; + padding: 0; + } + + .tunings-grid { + grid-template-columns: 1fr !important; + } + } + + .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; + } + + /* 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); } + + /* ============================================ + 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-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); } + + .tuning-name { + font-size: 16px; + font-weight: bold; + text-transform: uppercase; + } + + .genre-tag { + font-size: 12px; + font-style: italic; + color: var(--text-muted); + } + + .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; + } + + /* ============================================ + 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; + } + } + </style> +</head> +<body> + <div class="app-container"> + <!-- Header --> + <header class="app-header"> + <h1>INTERACTIVE BANJO TUNINGS REFERENCE</h1> + <div class="subtitle">19 Essential Tunings with Octave Notation, Capo Simulation & Fretboard Diagrams</div> + <div class="subtitle">5-String Banjo | Organized by Tuning Family</div> + </header> + + <!-- Control Panel --> + <div class="control-panel no-print"> + <div class="control-group"> + <label>Tuning Family</label> + <select id="familyFilter"> + <option value="all">All Families</option> + <option value="c-family">C-Based Tunings</option> + <option value="d-family">D-Based Tunings</option> + <option value="g-family">G-Based Tunings</option> + </select> + </div> + + <div class="control-group"> + <label>Capo Position</label> + <input type="number" id="capoPosition" min="0" max="12" value="0"> + </div> + + <div class="control-group"> + <label>Fret Count</label> + <select id="fretCount"> + <option value="12">12 Frets</option> + <option value="15" selected>15 Frets</option> + <option value="18">18 Frets</option> + </select> + </div> + + <div class="control-group"> + <label>Notation</label> + <div class="toggle-container"> + <span class="toggle-label" id="notationLabel">Sharps</span> + <label class="toggle-switch"> + <input type="checkbox" id="notationToggle"> + <span class="toggle-slider"></span> + </label> + </div> + </div> + + <div class="control-group"> + <label>Actions</label> + <div style="display: flex; gap: 10px;"> + <button class="btn btn-primary" onclick="window.print()">Print / PDF</button> + <button class="btn btn-secondary" onclick="resetSettings()">Reset</button> + </div> + </div> + </div> + + <!-- Tabs --> + <div class="tabs no-print"> + <button class="tab active" data-tab="tunings">Tunings</button> + <button class="tab" data-tab="fretboards">Fretboards</button> + <button class="tab" data-tab="reference">Reference</button> + </div> + + <!-- Tunings Tab --> + <div id="tunings-tab" class="tab-content active"> + <div class="reference-box"> + <strong>NOTATION SYSTEM:</strong> + Notes shown with Scientific Pitch Notation (SPN) subscripts (e.g., G₄ = G above middle C)<br> + String order: 5th (drone) → 4th → 3rd → 2nd → 1st<br> + Δ symbol = changes required from reference tuning | ↑ = tune up | ↓ = tune down<br> + Circle Key: ○ = Standard Open String | ● = Changed String + </div> + + <div id="tuningsContainer"></div> + </div> + + <!-- Fretboards Tab --> + <div id="fretboards-tab" class="tab-content"> + <div class="reference-box"> + <strong>FRETBOARD DIAGRAMS:</strong> + Select a tuning below to view its fretboard diagram. The 5th string starts at fret 5 (shown with diagonal neck edge). + Fret markers: ● at frets 3, 7, 10, 15, 17 | ★ at fret 5 | ●● at fret 12 + </div> + + <div class="control-group" style="margin-bottom: 20px;"> + <label style="color: #333;">Select Tuning</label> + <select id="fretboardTuningSelect" style="background: white; color: #333; border-color: #ccc;"> + </select> + </div> + + <div id="fretboardContainer"></div> + </div> + + <!-- Reference Tab --> + <div id="reference-tab" class="tab-content"> + <div class="reference-section"> + <h3>Fret Reference for Relative Tuning</h3> + + <div class="fret-reference-box blue"> + <h4>TUNING 5TH STRING:</h4> + <ul> + <li><strong>E:</strong> 1st string, 2nd fret when 1st = C (Little Birdie)</li> + <li><strong>F:</strong> 1st string, 3rd fret when 1st = C (F Tuning, East Virginia)</li> + <li><strong>F#:</strong> 1st string, 4th fret when 1st = C</li> + <li><strong>G:</strong> 1st string, 5th fret when 1st = C OR 4th string, 5th fret</li> + <li><strong>A:</strong> 1st string, 7th fret when 1st = C (Open D, D Modal)</li> + </ul> + </div> + + <div class="fret-reference-box red"> + <h4>TUNING 2ND STRING:</h4> + <ul> + <li><strong>A:</strong> 3rd string, 2nd fret when 3rd = G (G Variant, Moonshiner)</li> + <li><strong>B:</strong> When 2nd string is at C, fret 2nd string at 2nd fret = B (Open G)</li> + <li><strong>C:</strong> 3rd string, 5th fret when 3rd = G (Double C, Sawmill)</li> + <li><strong>C#:</strong> When 2nd string is at C, fret at 1st = C# (Open A)</li> + </ul> + </div> + + <div class="fret-reference-box green"> + <h4>TUNING OTHER STRINGS:</h4> + <ul> + <li><strong>1st String:</strong> Match to target pitch or use electronic tuner</li> + <li><strong>3rd String:</strong> Usually tuned relative to 4th string (perfect 4th interval)</li> + <li><strong>4th String:</strong> Foundation - tune first with electronic tuner</li> + </ul> + </div> + </div> + + <div class="reference-section"> + <h3>Capo Transposition Chart</h3> + + <table class="capo-chart"> + <thead> + <tr> + <th>Base Tuning</th> + <th>Capo 2</th> + <th>Capo 3</th> + <th>Capo 4</th> + <th>Capo 5</th> + <th>Capo 7</th> + </tr> + </thead> + <tbody> + <tr> + <td><strong>Open G</strong></td> + <td>A</td> + <td>Bb</td> + <td>B</td> + <td>C</td> + <td>D</td> + </tr> + <tr> + <td><strong>Double C</strong></td> + <td>D</td> + <td>Eb</td> + <td>E</td> + <td>F</td> + <td>G</td> + </tr> + <tr> + <td><strong>Open D</strong></td> + <td>E</td> + <td>F</td> + <td>F#</td> + <td>G</td> + <td>A</td> + </tr> + <tr> + <td><strong>G Modal</strong></td> + <td>A</td> + <td>Bb</td> + <td>B</td> + <td>C</td> + <td>D</td> + </tr> + </tbody> + </table> + + <div class="reference-box" style="margin-top: 20px;"> + <strong>5TH STRING SPIKES (when 5th string = G):</strong><br> + 7th fret = A | 9th fret = B | 10th fret = C | 12th fret = D + </div> + </div> + + <div class="reference-section"> + <h3>String Range Overview</h3> + <ul> + <li><strong>5th String Range:</strong> E₄ (Little Birdie) → A₄ (Open D, Open A, D Modal)</li> + <li><strong>4th String Range:</strong> C₂ (Minstrel family) → F₃ (East Virginia)</li> + <li><strong>3rd String Range:</strong> F₃ (Standard F, Minstrel) → A₄ (Open A)</li> + <li><strong>2nd String Range:</strong> A₃ (Multiple tunings) → C#₄ (Open A)</li> + <li><strong>1st String Range:</strong> C₄ (Standard F, Darling Cora) → E₄ (Cackling Hen, Open A)</li> + </ul> + </div> + + <div class="warning-box"> + <h4>HIGH TENSION WARNING:</h4> + <p><strong>Open A</strong> and <strong>Open D</strong> require significantly higher string tension than Open G. + Use lighter gauge strings (.009 or .010 for the 1st string) and tune up gradually. + Check your bridge position and neck relief when using these tunings regularly.</p> + </div> + + <div class="reference-section"> + <h3>Quick Tuning Strategies</h3> + <ol> + <li><strong>Start from a known tuning:</strong> Most players keep their banjo in Open G and adjust from there</li> + <li><strong>Use electronic tuners:</strong> Chromatic tuners are essential for accuracy with octave notation</li> + <li><strong>Relative tuning:</strong> Use fret references when tuning by ear</li> + <li><strong>String tension:</strong> High tunings (Open A, Open D) require lighter gauge strings</li> + <li><strong>Bridge setup:</strong> Moving between tuning families may require bridge repositioning</li> + <li><strong>Note your bridge position:</strong> Mark or measure bridge placement for each tuning family</li> + <li><strong>Tune gradually:</strong> When tuning up to high-tension tunings, go slowly to avoid breakage</li> + </ol> + </div> + + <div class="legend"> + Complete Banjo Tunings Reference | 19 Tunings Across 3 Families<br> + Symbols: ↑ = tune up | ↓ = tune down | Δ = change from reference | SPN = Scientific Pitch Notation<br> + ⭐ = Most common | ⚠ = Requires special consideration (string tension, setup) + </div> + </div> + </div> + + <script> + // ============================================ + // TUNINGS DATA + // ============================================ + const TUNINGS_DATA = { + // C-Based Tunings + "Minstrel Tuning": { + strings: ["F4", "C2", "F3", "A3", "C4"], + 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] + }, + "Naomi Weiss": { + strings: ["G4", "C2", "G3", "A3", "D4"], + 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] + }, + "Little Birdie": { + strings: ["E4", "C2", "G3", "A3", "D4"], + 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] + }, + "Hook-and-Line": { + strings: ["G4", "C2", "G3", "C4", "D4"], + 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] + }, + "Cackling Hen (Open C)": { + strings: ["G4", "C2", "G3", "C4", "E4"], + 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] + }, + "Darling Cora (Triple C)": { + strings: ["G4", "C2", "G3", "C4", "C4"], + family: "c-family", + description: "Drone Heavy", + 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] + }, + "Double C": { + strings: ["G4", "C2", "G3", "C4", "D4"], + 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] + }, + + // D-Based Tunings + "Moonshiner": { + strings: ["G4", "D3", "G3", "A3", "D4"], + 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] + }, + "German War": { + strings: ["G4", "D3", "G3", "A3", "F#4"], + 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] + }, + "Reuben": { + strings: ["G4", "D3", "F#3", "A3", "F#4"], + 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] + }, + "F Tuning (Cumberland Gap)": { + strings: ["F4", "D3", "G3", "C4", "D4"], + family: "d-family", + description: "High Tension", + 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] + }, + "East Virginia": { + strings: ["F4", "F3", "G3", "C4", "D4"], + 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] + }, + + // G-Based Tunings + "Standard F": { + strings: ["F4", "C3", "F3", "A3", "C4"], + family: "g-family", + description: "Historical Starting Point", + 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] + }, + "Open G": { + strings: ["G4", "D3", "G3", "B3", "D4"], + 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 + }, + "Open A": { + strings: ["A4", "E4", "A4", "C#4", "E4"], + 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 + }, + "G Variant": { + strings: ["G4", "D3", "G3", "A3", "D4"], + 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] + }, + "G Modal (Sawmill)": { + strings: ["G4", "D3", "G3", "C4", "D4"], + 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] + }, + "Open D": { + strings: ["A4", "D4", "F#4", "A4", "D4"], + family: "g-family", + description: "Blues/Folk", + referenceTuning: "Open G", + deltaChanges: "From Open G: all strings ↑2 frets (whole step)", + 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 + }, + "D Modal": { + strings: ["A4", "D4", "G4", "A4", "D4"], + 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] + } + }; + + // Note conversion maps + const NOTE_ORDER = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; + const SHARP_TO_FLAT = { + 'C#': 'Db', 'D#': 'Eb', 'F#': 'Gb', 'G#': 'Ab', 'A#': 'Bb' + }; + const FLAT_TO_SHARP = { + 'Db': 'C#', 'Eb': 'D#', 'Gb': 'F#', 'Ab': 'G#', 'Bb': 'A#' + }; + + // ============================================ + // STATE MANAGEMENT + // ============================================ + let state = { + familyFilter: 'all', + capoPosition: 0, + fretCount: 15, + useFlats: false, + selectedFretboardTuning: 'Open G' + }; + + // Load from localStorage + 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); + } + } + } + + // Save to localStorage + function saveState() { + localStorage.setItem('banjoTuningsState', JSON.stringify(state)); + } + + // ============================================ + // NOTE UTILITIES + // ============================================ + function parseNote(noteStr) { + const match = noteStr.match(/^([A-G][#b]?)(\d)$/); + if (!match) return null; + return { note: match[1], octave: parseInt(match[2]) }; + } + + function formatNote(noteStr, useFlats = false) { + const parsed = parseNote(noteStr); + if (!parsed) return noteStr; + + let note = parsed.note; + + // Normalize to sharps first + if (FLAT_TO_SHARP[note]) { + note = FLAT_TO_SHARP[note]; + } + + // Convert to flats if needed + if (useFlats && SHARP_TO_FLAT[note]) { + note = SHARP_TO_FLAT[note]; + } + + // Replace # with sharp symbol and b with flat symbol + note = note.replace('#', '\u266F').replace('b', '\u266D'); + + return `${note}<sub>${parsed.octave}</sub>`; + } + + function transposeNote(noteStr, semitones) { + const parsed = parseNote(noteStr); + if (!parsed) return noteStr; + + let note = parsed.note; + let octave = parsed.octave; + + // Normalize to sharps + if (FLAT_TO_SHARP[note]) { + note = FLAT_TO_SHARP[note]; + } + + let noteIndex = NOTE_ORDER.indexOf(note); + if (noteIndex === -1) return noteStr; + + noteIndex += semitones; + + // Handle octave changes + while (noteIndex >= 12) { + noteIndex -= 12; + octave++; + } + while (noteIndex < 0) { + noteIndex += 12; + octave--; + } + + return NOTE_ORDER[noteIndex] + octave; + } + + // ============================================ + // RENDERING FUNCTIONS + // ============================================ + function renderNoteSequence(strings, useFlats) { + return strings.map(s => formatNote(s, useFlats)).join(' '); + } + + function renderCapoInfo(capoPositions) { + return Object.entries(capoPositions) + .map(([pos, key]) => pos === '0' ? key : `Capo ${pos} → ${key}`) + .join(' | '); + } + + function renderEffectiveTuning(strings, capoPosition, useFlats) { + if (capoPosition === 0) return null; + + const transposed = strings.map(s => transposeNote(s, capoPosition)); + return transposed.map(s => formatNote(s, useFlats)).join(' '); + } + + function renderTuningCard(name, tuning) { + const effectiveTuning = renderEffectiveTuning(tuning.strings, state.capoPosition, state.useFlats); + const warningClass = tuning.warning ? 'warning' : ''; + const featuredBadge = tuning.featured ? ' ⭐' : ''; + const warningBadge = tuning.warning ? ' ⚠' : ''; + + return ` + <div class="tuning-card ${tuning.family}"> + <div class="tuning-header"> + <span class="tuning-name">${name}${featuredBadge}${warningBadge}</span> + <span class="genre-tag">${tuning.description}</span> + </div> + <div class="note-sequence">${renderNoteSequence(tuning.strings, state.useFlats)}</div> + <div class="nut-visual"> + ${tuning.changedStrings.map((changed, i) => + `<div class="string-circle ${changed ? 'changed' : ''}">${5 - i}</div>` + ).join('')} + </div> + <div class="tuning-details"> + <div class="delta-line">Δ: ${tuning.deltaChanges}</div> + <div class="capo-line">${renderCapoInfo(tuning.capoPositions)}</div> + <div class="songs-line">${tuning.songs}</div> + </div> + ${effectiveTuning ? ` + <div class="effective-tuning"> + <strong>With Capo ${state.capoPosition}:</strong> ${effectiveTuning} + </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.' } + }; + + 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; + } + + // ============================================ + // FRETBOARD SVG GENERATOR + // ============================================ + function generateFretboardSVG(tuningName) { + const tuning = TUNINGS_DATA[tuningName]; + if (!tuning) return ''; + + const fretCount = state.fretCount; + const capo = state.capoPosition; + + // Dimensions + const stringSpacing = 55; + const fretHeight = 50; + const leftMargin = 120; + const rightMargin = 80; + const topMargin = 80; + const bottomMargin = 40; + + const numStrings = 5; + const width = leftMargin + (fretCount * fretHeight) + rightMargin; + const height = topMargin + ((numStrings - 1) * stringSpacing) + bottomMargin; + + // Fret marker positions + const singleMarkers = [3, 5, 7, 10, 15, 17]; + const doubleMarkers = [12]; + const starMarker = 5; + + let svg = `<svg class="fretboard-svg" width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">`; + + // Background + svg += `<rect width="${width}" height="${height}" fill="#f5f0e6"/>`; + + // Fretboard wood grain + svg += `<rect x="${leftMargin}" y="${topMargin - 10}" width="${fretCount * fretHeight}" height="${(numStrings - 1) * stringSpacing + 20}" fill="#d4a574" rx="3"/>`; + + // Draw fret markers + for (let fret = 1; fret <= fretCount; fret++) { + const x = leftMargin + (fret - 0.5) * fretHeight; + const centerY = topMargin + ((numStrings - 1) * stringSpacing) / 2; + + if (doubleMarkers.includes(fret)) { + svg += `<circle cx="${x}" cy="${centerY - 20}" r="8" fill="#ddd" stroke="#bbb" stroke-width="1"/>`; + svg += `<circle cx="${x}" cy="${centerY + 20}" r="8" fill="#ddd" stroke="#bbb" stroke-width="1"/>`; + } else if (fret === starMarker) { + svg += `<text x="${x}" y="${centerY + 5}" text-anchor="middle" font-size="20" fill="#c9a86c">★</text>`; + } else if (singleMarkers.includes(fret) && fret !== starMarker) { + svg += `<circle cx="${x}" cy="${centerY}" r="8" fill="#ddd" stroke="#bbb" stroke-width="1"/>`; + } + } + + // Draw nut + svg += `<rect x="${leftMargin - 8}" y="${topMargin - 10}" width="8" height="${(numStrings - 1) * stringSpacing + 20}" fill="#f5f5dc" stroke="#333" stroke-width="2"/>`; + + // Draw frets + for (let fret = 1; fret <= fretCount; fret++) { + const x = leftMargin + fret * fretHeight; + // 5th string only starts at fret 5 + const startY = fret <= 5 ? topMargin + stringSpacing : topMargin; + svg += `<line x1="${x}" y1="${startY}" x2="${x}" y2="${topMargin + (numStrings - 1) * stringSpacing}" stroke="#666" stroke-width="2"/>`; + } + + // Draw 5th string diagonal transition + const fret5X = leftMargin + 5 * fretHeight; + svg += `<path d="M ${fret5X} ${topMargin} L ${fret5X - 15} ${topMargin + stringSpacing * 0.3}" stroke="#8b4513" stroke-width="3" fill="none"/>`; + + // 5th string peg indicator + svg += `<circle cx="${fret5X - 20}" cy="${topMargin + stringSpacing * 0.4}" r="6" fill="#c9a86c" stroke="#8b4513" stroke-width="2"/>`; + + // Draw strings + for (let string = 0; string < numStrings; string++) { + const y = topMargin + string * stringSpacing; + const stringWeight = 2 - (string * 0.2); + + if (string === 0) { + // 5th string - dotted from nut to fret 5, solid after + svg += `<line x1="${leftMargin}" y1="${y}" x2="${fret5X}" y2="${y}" stroke="#888" stroke-width="${stringWeight}" stroke-dasharray="4,4" opacity="0.4"/>`; + svg += `<line x1="${fret5X}" y1="${y}" x2="${leftMargin + fretCount * fretHeight}" y2="${y}" stroke="#444" stroke-width="${stringWeight}"/>`; + } else { + svg += `<line x1="${leftMargin}" y1="${y}" x2="${leftMargin + fretCount * fretHeight}" y2="${y}" stroke="#444" stroke-width="${stringWeight}"/>`; + } + } + + // Draw capo if set + if (capo > 0) { + const capoX = leftMargin + (capo - 0.5) * fretHeight; + svg += `<rect x="${capoX - 10}" y="${topMargin - 15}" width="20" height="${(numStrings - 1) * stringSpacing + 30}" fill="#333" rx="5" opacity="0.8"/>`; + svg += `<text x="${capoX}" y="${topMargin - 25}" text-anchor="middle" font-size="12" fill="#333" font-weight="bold">CAPO</text>`; + } + + // Draw string labels and open notes + const strings = tuning.strings; + for (let string = 0; string < numStrings; string++) { + const y = topMargin + string * stringSpacing; + const note = strings[string]; + const transposedNote = capo > 0 ? transposeNote(note, capo) : note; + const displayNote = formatNote(transposedNote, state.useFlats); + + // String number + svg += `<text x="${leftMargin - 50}" y="${y + 5}" text-anchor="middle" font-size="16" font-weight="bold" fill="#333">${5 - string}</text>`; + + // Open note circle + const isChanged = tuning.changedStrings[string]; + svg += `<circle cx="${leftMargin - 25}" cy="${y}" r="14" fill="${isChanged ? '#333' : 'white'}" stroke="#333" stroke-width="2"/>`; + + // Note text (need to handle subscript differently in SVG) + const parsed = parseNote(transposedNote); + if (parsed) { + let noteName = parsed.note; + if (state.useFlats && SHARP_TO_FLAT[noteName]) { + noteName = SHARP_TO_FLAT[noteName]; + } + noteName = noteName.replace('#', '\u266F').replace('b', '\u266D'); + + svg += `<text x="${leftMargin - 25}" y="${y + 2}" text-anchor="middle" font-size="10" font-weight="bold" fill="${isChanged ? 'white' : '#333'}">${noteName}</text>`; + svg += `<text x="${leftMargin - 17}" y="${y + 8}" text-anchor="middle" font-size="7" fill="${isChanged ? 'white' : '#333'}">${parsed.octave}</text>`; + } + } + + // Fret numbers + for (let fret = 1; fret <= fretCount; fret++) { + const x = leftMargin + (fret - 0.5) * fretHeight; + svg += `<text x="${x}" y="${topMargin + (numStrings - 1) * stringSpacing + 25}" text-anchor="middle" font-size="12" fill="#666">${fret}</text>`; + } + + // Title + svg += `<text x="${width / 2}" y="30" text-anchor="middle" font-size="18" font-weight="bold" fill="#333">${tuningName}</text>`; + svg += `<text x="${width / 2}" y="50" text-anchor="middle" font-size="12" fill="#666">${tuning.description}</text>`; + + svg += '</svg>'; + return svg; + } + + function renderFretboard() { + const container = document.getElementById('fretboardContainer'); + container.innerHTML = generateFretboardSVG(state.selectedFretboardTuning); + } + + function populateFretboardSelect() { + const select = document.getElementById('fretboardTuningSelect'); + select.innerHTML = Object.keys(TUNINGS_DATA) + .map(name => `<option value="${name}" ${name === state.selectedFretboardTuning ? 'selected' : ''}>${name}</option>`) + .join(''); + } + + // ============================================ + // EVENT HANDLERS + // ============================================ + function setupEventListeners() { + // Family filter + document.getElementById('familyFilter').addEventListener('change', (e) => { + state.familyFilter = e.target.value; + saveState(); + renderTunings(); + }); + + // Capo position + document.getElementById('capoPosition').addEventListener('change', (e) => { + state.capoPosition = parseInt(e.target.value) || 0; + saveState(); + renderTunings(); + renderFretboard(); + }); + + // Fret count + document.getElementById('fretCount').addEventListener('change', (e) => { + state.fretCount = parseInt(e.target.value); + saveState(); + renderFretboard(); + }); + + // Notation toggle + document.getElementById('notationToggle').addEventListener('change', (e) => { + state.useFlats = e.target.checked; + document.getElementById('notationLabel').textContent = state.useFlats ? 'Flats' : 'Sharps'; + saveState(); + renderTunings(); + renderFretboard(); + }); + + // Fretboard tuning select + document.getElementById('fretboardTuningSelect').addEventListener('change', (e) => { + state.selectedFretboardTuning = e.target.value; + saveState(); + renderFretboard(); + }); + + // Tabs + 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, + useFlats: false, + selectedFretboardTuning: 'Open G' + }; + saveState(); + applyStateToUI(); + renderTunings(); + renderFretboard(); + } + + function applyStateToUI() { + document.getElementById('familyFilter').value = state.familyFilter; + document.getElementById('capoPosition').value = state.capoPosition; + document.getElementById('fretCount').value = state.fretCount; + document.getElementById('notationToggle').checked = state.useFlats; + document.getElementById('notationLabel').textContent = state.useFlats ? 'Flats' : 'Sharps'; + document.getElementById('fretboardTuningSelect').value = state.selectedFretboardTuning; + } + + // ============================================ + // INITIALIZATION + // ============================================ + function init() { + loadState(); + applyStateToUI(); + populateFretboardSelect(); + setupEventListeners(); + renderTunings(); + renderFretboard(); + } + + // Start the app + document.addEventListener('DOMContentLoaded', init); + </script> +</body> +</html>