Add a Circuit filter to the filter bar

Buttons are generated from CONFIG.circuits (not hand-written in
index.html), so adding a circuit to the config still only takes one
line — its filter button appears automatically. Uses accent color to
match the circuit badges on the cards, same active/dimmed styling as
Format/Scope. An event matches the circuit filter if it belongs to any
active circuit (OR within the group), not all of them, since an event
can legitimately carry more than one circuit tag.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
This commit is contained in:
2026-09-04 17:10:20 -05:00
co-authored by Claude Sonnet 5
parent 4bb5dc8ed8
commit 345f952970
2 changed files with 28 additions and 9 deletions
+2
View File
@@ -26,6 +26,8 @@
<span class="text-xs uppercase tracking-widest opacity-50 ml-3 mr-1">Scope</span>
<button type="button" class="btn btn-sm btn-info" data-filter-group="scope" data-filter-value="RTT">RTT</button>
<button type="button" class="btn btn-sm btn-info" data-filter-group="scope" data-filter-value="GT">GT</button>
<span class="text-xs uppercase tracking-widest opacity-50 ml-3 mr-1">Circuit</span>
<span id="circuit-filters" class="contents"></span>
</div>
<!-- Loading skeleton -->
+26 -9
View File
@@ -215,25 +215,42 @@ function scopeOf(ev) {
/* ---- Filters ---- */
const activeFormats = new Set(["Singles", "Team Event"]);
const activeScopes = new Set(["RTT", "GT"]);
const activeCircuits = new Set(CONFIG.circuits.map(c => c.id));
function passesFilter(ev) {
// An event passes the circuit filter if it belongs to at least one
// active circuit — it's an OR within the group, same as Format/Scope,
// not "must match every circuit it happens to also belong to".
function passesFilter(ev, circuits) {
const scope = scopeOf(ev);
return activeFormats.has(formatOf(ev)) && (scope === null || activeScopes.has(scope));
return activeFormats.has(formatOf(ev))
&& (scope === null || activeScopes.has(scope))
&& circuits.some(c => activeCircuits.has(c.id));
}
// Filter buttons use the same color as the badge they control (warning
// for Format, info for Scope) when active, so it's obvious at a glance
// which is which — and switch to a dimmed outline when off, since
// btn-outline alone reads too similarly to its own active state in dark
// mode to tell the two apart at a glance.
const FILTER_GROUP_COLOR = { format: "btn-warning", scope: "btn-info" };
// for Format, info for Scope, accent for Circuit — matching the badge
// colors on the cards) when active, so it's obvious at a glance which is
// which — and switch to a dimmed outline when off, since btn-outline
// alone reads too similarly to its own active state in dark mode to
// tell the two apart at a glance.
const FILTER_GROUP_COLOR = { format: "btn-warning", scope: "btn-info", circuit: "btn-accent" };
const FILTER_SET = { format: activeFormats, scope: activeScopes, circuit: activeCircuits };
// Circuit buttons are generated from CONFIG rather than hand-written in
// index.html, so adding a circuit there is still a one-line change.
function renderCircuitFilterButtons() {
$("circuit-filters").innerHTML = CONFIG.circuits.map(c =>
`<button type="button" class="btn btn-sm btn-accent" data-filter-group="circuit" data-filter-value="${esc(c.id)}">${esc(c.name)}</button>`
).join("");
}
function setupFilters() {
renderCircuitFilterButtons();
document.querySelectorAll("#filters button[data-filter-group]").forEach(btn => {
const group = btn.dataset.filterGroup;
const onClass = FILTER_GROUP_COLOR[group];
btn.addEventListener("click", () => {
const set = group === "format" ? activeFormats : activeScopes;
const set = FILTER_SET[group];
const nowActive = !btn.classList.contains(onClass);
btn.classList.toggle(onClass, nowActive);
btn.classList.toggle("btn-outline", !nowActive);
@@ -297,7 +314,7 @@ let allResults = [];
function renderEvents() {
const merged = mergeEvents(allResults);
const filtered = merged.filter(({ event }) => passesFilter(event));
const filtered = merged.filter(({ event, circuits }) => passesFilter(event, circuits));
const failed = allResults.filter(r => r.error).map(r => r.circuit.name);
const errorBanner = failed.length