diff options
Diffstat (limited to 'layouts/_default/baseof.html')
| -rw-r--r-- | layouts/_default/baseof.html | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html new file mode 100644 index 0000000..d8fafb6 --- /dev/null +++ b/layouts/_default/baseof.html | |||
| @@ -0,0 +1,138 @@ | |||
| 1 | <!DOCTYPE html> | ||
| 2 | <html lang="{{ or site.Language.LanguageCode site.Language.Lang }}" | ||
| 3 | dir="{{ or site.Language.LanguageDirection `ltr` }}"> | ||
| 4 | |||
| 5 | <head> | ||
| 6 | {{ partial "head.html" . }} | ||
| 7 | </head> | ||
| 8 | |||
| 9 | {{ $theme := "auto"}} | ||
| 10 | |||
| 11 | {{ with .Param "theme" }} | ||
| 12 | {{ $theme = .}} | ||
| 13 | {{ end }} | ||
| 14 | |||
| 15 | <body class="{{ $theme }}"> | ||
| 16 | |||
| 17 | <div class="content"> | ||
| 18 | <header> | ||
| 19 | <h1 class="center"> | ||
| 20 | <a class="hover-underline" href="/">{{ .Site.Title }}</a> | ||
| 21 | </h1> | ||
| 22 | <hr> | ||
| 23 | </header> | ||
| 24 | |||
| 25 | <main class="main"> | ||
| 26 | |||
| 27 | {{ block "main" . }}{{ end }} | ||
| 28 | </main> | ||
| 29 | </div> | ||
| 30 | |||
| 31 | </body> | ||
| 32 | |||
| 33 | <script> | ||
| 34 | |||
| 35 | function isAuto() { | ||
| 36 | return document.body.classList.contains("auto"); | ||
| 37 | } | ||
| 38 | |||
| 39 | function setTheme() { | ||
| 40 | if (!isAuto()) { | ||
| 41 | return | ||
| 42 | } | ||
| 43 | |||
| 44 | document.body.classList.remove("auto"); | ||
| 45 | let cls = "light"; | ||
| 46 | if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { | ||
| 47 | cls = "dark"; | ||
| 48 | } | ||
| 49 | |||
| 50 | document.body.classList.add(cls); | ||
| 51 | } | ||
| 52 | |||
| 53 | function invertBody() { | ||
| 54 | document.body.classList.toggle("dark"); | ||
| 55 | document.body.classList.toggle("light"); | ||
| 56 | } | ||
| 57 | |||
| 58 | if (isAuto()) { | ||
| 59 | window.matchMedia('(prefers-color-scheme: dark)').addListener(invertBody); | ||
| 60 | } | ||
| 61 | |||
| 62 | setTheme(); | ||
| 63 | |||
| 64 | |||
| 65 | </script> | ||
| 66 | |||
| 67 | |||
| 68 | {{ if .HasShortcode "chords" }} | ||
| 69 | <script> | ||
| 70 | document.addEventListener("DOMContentLoaded", () => { | ||
| 71 | const semitones = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; | ||
| 72 | const flatToSharp = { 'Db': 'C#', 'Eb': 'D#', 'Gb': 'F#', 'Ab': 'G#', 'Bb': 'A#' }; | ||
| 73 | |||
| 74 | function normalize(note) { | ||
| 75 | return flatToSharp[note] || note; | ||
| 76 | } | ||
| 77 | |||
| 78 | function transposeNote(note, shift) { | ||
| 79 | note = normalize(note); | ||
| 80 | const i = semitones.indexOf(note); | ||
| 81 | return i === -1 ? note : semitones[(i + shift + 12) % 12]; | ||
| 82 | } | ||
| 83 | |||
| 84 | function transposeChord(fullChord, shift) { | ||
| 85 | const match = fullChord.match(/^([A-G][#b]?)([^\/]*)(?:\/([A-G][#b]?))?$/); | ||
| 86 | if (!match) return fullChord; | ||
| 87 | let [, root, suffix, bass] = match; | ||
| 88 | const transposedRoot = transposeNote(root, shift); | ||
| 89 | const transposedBass = bass ? transposeNote(bass, shift) : null; | ||
| 90 | return transposedRoot + suffix + (transposedBass ? `/${transposedBass}` : ''); | ||
| 91 | } | ||
| 92 | |||
| 93 | function renderChords() { | ||
| 94 | const pattern = /\[([A-G][#b]?[^ \[\]]*)\]/g; | ||
| 95 | document.querySelectorAll('.chord-line').forEach(line => { | ||
| 96 | line.innerHTML = line.innerHTML.replace(pattern, (_, chord) => { | ||
| 97 | return `<span class="chord-anchor"><span class="chord" data-original="${chord}">${chord}</span></span>`; | ||
| 98 | }); | ||
| 99 | }); | ||
| 100 | } | ||
| 101 | |||
| 102 | function transposeToKey(fromKey, toKey) { | ||
| 103 | const fromIndex = semitones.indexOf(normalize(fromKey)); | ||
| 104 | const toIndex = semitones.indexOf(normalize(toKey)); | ||
| 105 | const shift = fromIndex === -1 || toIndex === -1 ? 0 : (toIndex - fromIndex + 12) % 12; | ||
| 106 | |||
| 107 | document.querySelectorAll('.chord').forEach(el => { | ||
| 108 | const original = el.dataset.original; | ||
| 109 | el.textContent = transposeChord(original, shift); | ||
| 110 | }); | ||
| 111 | } | ||
| 112 | |||
| 113 | // Init | ||
| 114 | renderChords(); | ||
| 115 | |||
| 116 | const selector = document.getElementById('key-selector'); | ||
| 117 | const originalKey = selector?.dataset.originalKey || 'C'; | ||
| 118 | |||
| 119 | // Trigger initial transpose to match the default selected key | ||
| 120 | transposeToKey(originalKey, selector.value); | ||
| 121 | |||
| 122 | selector?.addEventListener("change", () => { | ||
| 123 | transposeToKey(originalKey, selector.value); | ||
| 124 | }); | ||
| 125 | }); | ||
| 126 | |||
| 127 | document.addEventListener("DOMContentLoaded", () => { | ||
| 128 | const toggle = document.getElementById("toggle-chords"); | ||
| 129 | |||
| 130 | toggle?.addEventListener("change", () => { | ||
| 131 | document.body.classList.toggle("hide-chords", !toggle.checked); | ||
| 132 | }); | ||
| 133 | }); | ||
| 134 | </script> | ||
| 135 | {{ end }} | ||
| 136 | |||
| 137 | |||
| 138 | </html> \ No newline at end of file | ||
