diff options
| author | leiyu3 <s444814187@gmail.com> | 2025-07-17 11:47:55 -0400 |
|---|---|---|
| committer | leiyu3 <s444814187@gmail.com> | 2025-07-17 11:47:55 -0400 |
| commit | 6758a1a26de82a6dc3661db880513cba75f01110 (patch) | |
| tree | caf67546bd0113f434ca3ae65b4bc9b854b2878e /layouts/_default | |
| download | letsworship-6758a1a26de82a6dc3661db880513cba75f01110.tar.gz letsworship-6758a1a26de82a6dc3661db880513cba75f01110.zip | |
init commit
Diffstat (limited to 'layouts/_default')
| -rw-r--r-- | layouts/_default/baseof.html | 138 | ||||
| -rw-r--r-- | layouts/_default/home.html | 71 | ||||
| -rw-r--r-- | layouts/_default/single.html | 74 |
3 files changed, 283 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 | ||
diff --git a/layouts/_default/home.html b/layouts/_default/home.html new file mode 100644 index 0000000..17f4249 --- /dev/null +++ b/layouts/_default/home.html | |||
| @@ -0,0 +1,71 @@ | |||
| 1 | {{ define "main" }} | ||
| 2 | <script> | ||
| 3 | document.addEventListener("DOMContentLoaded", () => { | ||
| 4 | for (const e of document.getElementsByClassName("js-only")) { | ||
| 5 | e.classList.remove("js-only"); | ||
| 6 | } | ||
| 7 | |||
| 8 | const songs = document.querySelectorAll("li"); | ||
| 9 | const search = document.getElementById("search"); | ||
| 10 | const songlist = document.getElementById("song-list"); | ||
| 11 | |||
| 12 | const normalize = str => str.normalize('NFD').replace(/\p{Diacritic}/gu, ""); | ||
| 13 | |||
| 14 | search.addEventListener("input", () => { | ||
| 15 | const searchText = search.value.toLowerCase().trim(); | ||
| 16 | const searchTerms = normalize(searchText).split(" "); | ||
| 17 | const hasFilter = searchText.length > 0; | ||
| 18 | songlist.classList.toggle("list-searched", hasFilter); | ||
| 19 | |||
| 20 | songs.forEach(song => { | ||
| 21 | const songText = normalize(`${song.textContent} ${song.dataset.alias}`).toLowerCase(); | ||
| 22 | const isMatch = searchTerms.every(term => songText.includes(term)); | ||
| 23 | song.hidden = !isMatch; | ||
| 24 | }); | ||
| 25 | }); | ||
| 26 | |||
| 27 | const clearSearch = document.getElementById("clear-search"); | ||
| 28 | clearSearch.addEventListener("click", () => { | ||
| 29 | search.value = ""; | ||
| 30 | songs.forEach(song => { | ||
| 31 | song.hidden = false; | ||
| 32 | }); | ||
| 33 | songlist.classList.remove("list-searched"); | ||
| 34 | }); | ||
| 35 | }); | ||
| 36 | </script> | ||
| 37 | |||
| 38 | <div class="list-container"> | ||
| 39 | {{ .Content }} | ||
| 40 | |||
| 41 | {{ $tagsPage := eq .Title "Tags"}} | ||
| 42 | |||
| 43 | <h2 class="center">What do you want to sing?</h2> | ||
| 44 | <div class="search-bar-container"> | ||
| 45 | <div class="search-wrapper"> | ||
| 46 | <input type="text" id="search" class="search-bar" placeholder="Search ALL Songs..."> | ||
| 47 | <button id="clear-search" class="js-only"> | ||
| 48 | <svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"> | ||
| 49 | <title>Backspace</title> | ||
| 50 | <path | ||
| 51 | d="M135.19 390.14a28.79 28.79 0 0021.68 9.86h246.26A29 29 0 00432 371.13V140.87A29 29 0 00403.13 112H156.87a28.84 28.84 0 00-21.67 9.84v0L46.33 256l88.86 134.11z" | ||
| 52 | fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32"></path> | ||
| 53 | <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" | ||
| 54 | d="M336.67 192.33L206.66 322.34M336.67 322.34L206.66 192.33M336.67 192.33L206.66 322.34M336.67 322.34L206.66 192.33"> | ||
| 55 | </path> | ||
| 56 | </svg></button> | ||
| 57 | </div> | ||
| 58 | </div> | ||
| 59 | |||
| 60 | <ul id="song-list"> | ||
| 61 | {{ range .Pages }} | ||
| 62 | <li data-alias={{ if .Params.alias }}{{ .Params.alias }}{{ else }}{{ .Params.title }}{{ end }}> | ||
| 63 | <a href="{{ .RelPermalink }}"> | ||
| 64 | {{ .Title }} | ||
| 65 | </a> | ||
| 66 | </li> | ||
| 67 | {{ end }} | ||
| 68 | </ul> | ||
| 69 | |||
| 70 | </div> | ||
| 71 | {{ end }} \ No newline at end of file | ||
diff --git a/layouts/_default/single.html b/layouts/_default/single.html new file mode 100644 index 0000000..38cf763 --- /dev/null +++ b/layouts/_default/single.html | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | {{ define "main" }} | ||
| 2 | |||
| 3 | {{/* Breadcrumbs */}} | ||
| 4 | |||
| 5 | <div {{ if .Param "autonumber" }} class="autonumber" {{ end }}> | ||
| 6 | |||
| 7 | <div class="single-intro-container"> | ||
| 8 | |||
| 9 | {{/* Title and Summary */}} | ||
| 10 | |||
| 11 | <h2 class="single-title">{{ .Title }}</h2> | ||
| 12 | {{ if .Param "author" }} | ||
| 13 | <p>By: {{ .Param "author" }}</p> | ||
| 14 | {{ end }} | ||
| 15 | |||
| 16 | <div class="controls"> | ||
| 17 | <div> | ||
| 18 | <label class="chord-toggle"> | ||
| 19 | <input type="checkbox" id="toggle-chords" checked> | ||
| 20 | <span class="slider"></span> | ||
| 21 | <span class="label-text">Show Chords</span> | ||
| 22 | </label> | ||
| 23 | </div> | ||
| 24 | |||
| 25 | <div > | ||
| 26 | <label for="key-selector">Key:</label> | ||
| 27 | <select id="key-selector" data-original-key="{{ with .Params.key }}{{ . }}{{ else }}C{{ end }}"> | ||
| 28 | {{ $allKeys := slice "C" "C#/Db" "D" "D#/Eb" "E" "F" "F#/Gb" "G" "G#/Ab" "A" "A#/Bb" "B" }} | ||
| 29 | {{ $currentKey := .Params.key | default "C" }} | ||
| 30 | {{ range $k := $allKeys }} | ||
| 31 | {{ $cleanKey := (index (split $k "/") 0) }} | ||
| 32 | <option value="{{ $cleanKey }}" {{ if in $currentKey $k }}selected{{ end }}> | ||
| 33 | {{ $k }} | ||
| 34 | </option> | ||
| 35 | {{ end }} | ||
| 36 | </select> | ||
| 37 | </div> | ||
| 38 | </div> | ||
| 39 | |||
| 40 | </div> | ||
| 41 | |||
| 42 | {{ if .Param "showTags" }} | ||
| 43 | |||
| 44 | {{ $taxonomy := "tags" }} | ||
| 45 | {{ with .Param $taxonomy }} | ||
| 46 | |||
| 47 | <div class="single-tags"> | ||
| 48 | {{ range $index, $tag := . }} | ||
| 49 | {{ with $.Site.GetPage (printf "/%s/%s" $taxonomy $tag) -}} | ||
| 50 | <span> | ||
| 51 | <a href="{{ .Permalink }}">#{{ .LinkTitle }}</a> | ||
| 52 | </span> | ||
| 53 | {{ end }} | ||
| 54 | {{ end }} | ||
| 55 | </div> | ||
| 56 | |||
| 57 | {{ end }} | ||
| 58 | {{ end }} | ||
| 59 | |||
| 60 | {{/* Page content */}} | ||
| 61 | |||
| 62 | <div class="single-content"> | ||
| 63 | {{ .Content }} | ||
| 64 | </div> | ||
| 65 | |||
| 66 | {{/* Back to top */}} | ||
| 67 | <div class="back-to-top"> | ||
| 68 | <a href="#top"> | ||
| 69 | back to top | ||
| 70 | </a> | ||
| 71 | </div> | ||
| 72 | </div> | ||
| 73 | |||
| 74 | {{ end }} \ No newline at end of file | ||
