style: 演唱會搜尋說明

This commit is contained in:
2026-06-03 16:04:13 +08:00
parent 4163cce3fa
commit f54dfda5f4
2 changed files with 112 additions and 26 deletions
+31 -3
View File
@@ -103,6 +103,17 @@
margin: 0; margin: 0;
} }
.concert-search-hint {
width: 100%;
max-width: 720px;
margin: 0 0 12px;
padding: 0 6px;
text-align: left;
font-size: 0.82rem;
line-height: 1.6;
color: rgba(255, 255, 255, 0.78);
}
.concert-search-summary { .concert-search-summary {
margin: 0 0 12px; margin: 0 0 12px;
text-align: center; text-align: center;
@@ -130,6 +141,18 @@
line-height: 1.5; line-height: 1.5;
} }
.concert-search-hits li.concert-search-hit-jump {
cursor: pointer;
transition: background 0.2s ease, border-color 0.2s ease;
}
.concert-search-hits li.concert-search-hit-jump:hover,
.concert-search-hits li.concert-search-hit-jump:focus-visible {
background: rgba(255, 204, 0, 0.15);
border-color: #ffcc00;
outline: none;
}
.concert-search-hits .hit-track { .concert-search-hits .hit-track {
display: block; display: block;
font-weight: 700; font-weight: 700;
@@ -171,12 +194,17 @@
<div class="search-section"> <div class="search-section">
<div class="concert-search-box"> <div class="concert-search-box">
<div class="concert-search-row"> <div class="concert-search-row">
<input type="text" class="search-input" id="concert-query" value="" placeholder="搜尋曲目或場次"> <input type="text" class="search-input" id="concert-query" value="" placeholder="搜尋曲目或場次"
<input type="text" class="search-input" id="concert-member" value="" placeholder="成員或段落(可空白)"> aria-describedby="concert-search-hint">
<input type="text" class="search-input" id="concert-member" value=""
placeholder="成員或 ENCORESolo…(可空白)" aria-describedby="concert-search-hint">
<button type="button" class="concert-search-btn" id="concert-search-btn">查詢</button> <button type="button" class="concert-search-btn" id="concert-search-btn">查詢</button>
</div> </div>
<p class="concert-search-hint" id="concert-search-hint">
<strong>曲目或場次</strong>:曲名、巡演名或場地(例:COUPLE、釜山、YELLOW NOTE)。<strong>成員或段落</strong>:可留白;要縮小範圍時可填成員名,或歌單裡的段落標記(例:殷志源、ENCORE、BLACKKIES、Solo)。兩格都有填時,需同時符合。按查詢或 Enter;點結果可跳到下方歌單。
</p>
<div id="concert-search-result" class="concert-search-result" aria-live="polite"> <div id="concert-search-result" class="concert-search-result" aria-live="polite">
<p class="concert-search-summary">輸入後按「查詢」,會在下方列出比對到的曲目</p> <p class="concert-search-summary">尚未查詢</p>
</div> </div>
</div> </div>
</div> </div>
+81 -23
View File
@@ -43,6 +43,36 @@ function clearSearchHighlights() {
}); });
} }
function expandVenueForTrackItem(trackItem) {
const venueBlock = trackItem?.closest(".track-list")?.parentElement;
const expand = venueBlock?.querySelector(".expand-trigger");
if (expand) {
expand.checked = true;
}
}
function scrollToSearchResults() {
if (!myResult) return;
myResult.scrollIntoView({ behavior: "smooth", block: "start" });
}
function jumpToTrackHit(hit) {
if (!hit?.el) return;
clearSearchHighlights();
expandVenueForTrackItem(hit.el);
hit.el.classList.add("is-search-hit");
hit.el.scrollIntoView({ behavior: "smooth", block: "center" });
}
function setActiveSearchListItem(list, activeLi) {
if (!list) return;
list.querySelectorAll(".concert-search-hit-jump").forEach(function (item) {
item.classList.remove("is-active");
});
if (activeLi) activeLi.classList.add("is-active");
}
function renderSearchResults(getQuery, getMember, hits) { function renderSearchResults(getQuery, getMember, hits) {
if (!myResult) return; if (!myResult) return;
@@ -52,15 +82,15 @@ function renderSearchResults(getQuery, getMember, hits) {
summary.className = "concert-search-summary"; summary.className = "concert-search-summary";
if (!hits.length) { if (!hits.length) {
summary.textContent = `關鍵字${getQuery || "(未填)"}${getMember.trim() ? `、成員「${getMember}` : ""}:沒有比對到曲目,請換字再試。`; summary.textContent = `曲目或場次${getQuery || "(未填)"}${getMember.trim() ? `、成員「${getMember}` : ""}:沒有比對到曲目,請換字再試。`;
myResult.replaceChildren(summary); myResult.replaceChildren(summary);
return; return;
} }
if (getMember.trim()) { if (getMember.trim()) {
summary.textContent = `關鍵字${getQuery || "(未填)"}」、成員「${getMember}」:共 ${hits.length},以下列出前 ${Math.min(hits.length, SEARCH_LIST_MAX)}`; summary.textContent = `曲目或場次${getQuery || "(未填)"}」、成員「${getMember}」:共 ${hits.length}。點任一列可跳到歌單`;
} else { } else {
summary.textContent = `關鍵字${getQuery}」:共 ${hits.length},以下列出前 ${Math.min(hits.length, SEARCH_LIST_MAX)}`; summary.textContent = `曲目或場次${getQuery}」:共 ${hits.length}。點任一列可跳到歌單`;
} }
const list = document.createElement("ul"); const list = document.createElement("ul");
@@ -70,6 +100,11 @@ function renderSearchResults(getQuery, getMember, hits) {
if (hit.el) hit.el.classList.add("is-search-hit"); if (hit.el) hit.el.classList.add("is-search-hit");
const li = document.createElement("li"); const li = document.createElement("li");
li.className = "concert-search-hit-jump";
li.setAttribute("role", "button");
li.setAttribute("tabindex", "0");
li.setAttribute("aria-label", `跳到曲目:${hit.trackName}`);
const trackLine = document.createElement("span"); const trackLine = document.createElement("span");
trackLine.className = "hit-track"; trackLine.className = "hit-track";
trackLine.textContent = hit.trackName; trackLine.textContent = hit.trackName;
@@ -80,41 +115,64 @@ function renderSearchResults(getQuery, getMember, hits) {
li.appendChild(trackLine); li.appendChild(trackLine);
if (metaLine.textContent) li.appendChild(metaLine); if (metaLine.textContent) li.appendChild(metaLine);
li.addEventListener("click", function () {
jumpToTrackHit(hit);
});
li.addEventListener("keydown", function (event) {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
jumpToTrackHit(hit);
}
});
list.appendChild(li); list.appendChild(li);
}); });
if (hits.length > SEARCH_LIST_MAX) { if (hits.length > SEARCH_LIST_MAX) {
const more = document.createElement("li"); const more = document.createElement("li");
more.className = "hit-more"; more.className = "hit-more";
more.textContent = `另有 ${hits.length - SEARCH_LIST_MAX} 筆未顯示,請縮小關鍵字`; more.textContent = `另有 ${hits.length - SEARCH_LIST_MAX} 筆未顯示,請縮小曲目或場次`;
list.appendChild(more); list.appendChild(more);
} }
myResult.replaceChildren(summary, list); myResult.replaceChildren(summary, list);
const firstHit = hits[0]?.el; jumpToTrackHit(hits[0]);
if (firstHit) { }
firstHit.scrollIntoView({ behavior: "smooth", block: "nearest" });
function runSearch() {
let getQuery = myQuery ? myQuery.value : "";
let getMember = myMember ? myMember.value : "";
if (!getQuery.trim() && !getMember.trim()) {
myResult.replaceChildren();
const summary = document.createElement("p");
summary.className = "concert-search-summary";
summary.textContent = "請至少輸入曲目/場次,或成員其中一項。";
myResult.appendChild(summary);
clearSearchHighlights();
return;
} }
const hits = findMatchingTracks(getQuery, getMember);
renderSearchResults(getQuery, getMember, hits);
}
function handleSearchEnter(event) {
if (event.key !== "Enter") return;
event.preventDefault();
runSearch();
} }
// 2.action // 2.action
if (myBtn && myResult) { if (myBtn && myResult) {
myBtn.addEventListener("click", function () { myBtn.addEventListener("click", runSearch);
let getQuery = myQuery ? myQuery.value : "";
let getMember = myMember ? myMember.value : "";
if (!getQuery.trim() && !getMember.trim()) { if (myQuery) {
myResult.replaceChildren(); myQuery.addEventListener("keydown", handleSearchEnter);
const summary = document.createElement("p"); }
summary.className = "concert-search-summary"; if (myMember) {
summary.textContent = "請至少輸入曲目/場次,或成員其中一項。"; myMember.addEventListener("keydown", handleSearchEnter);
myResult.appendChild(summary); }
clearSearchHighlights();
return;
}
const hits = findMatchingTracks(getQuery, getMember);
renderSearchResults(getQuery, getMember, hits);
});
} }