Skip to content

Commit d7841cf

Browse files
committed
Update theme
1 parent 64d5f2c commit d7841cf

8 files changed

Lines changed: 197 additions & 44 deletions

File tree

app.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,22 @@ async function init() {
132132
const container = document.getElementById(targetId);
133133
if (!container || !container._allValues) return;
134134
const q = e.target.value.toLowerCase();
135-
const filtered = container._allValues.filter((v) => {
136-
const label = container._labelFn ? container._labelFn(v) : v;
137-
return label.toLowerCase().includes(q) || v.toLowerCase().includes(q);
138-
});
139-
buildPills(container, filtered, container._filterKey, container._labelFn);
135+
let visible;
136+
if (q === '') {
137+
// Restore top-N plus any currently-selected values not in the top set
138+
const topValues = container._topValues || [];
139+
const selected = state.filters[container._filterKey] || [];
140+
const topSet = new Set(topValues);
141+
const extras = selected.filter((v) => !topSet.has(v) && container._allValues.includes(v));
142+
visible = [...topValues, ...extras];
143+
} else {
144+
// Show all values matching the query
145+
visible = container._allValues.filter((v) => {
146+
const label = container._labelFn ? container._labelFn(v) : v;
147+
return label.toLowerCase().includes(q) || v.toLowerCase().includes(q);
148+
});
149+
}
150+
buildPills(container, visible, container._filterKey, container._labelFn);
140151
});
141152
});
142153

index.html

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@
4848
</script>
4949

5050
<!-- Pico CSS classless -->
51-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css" />
51+
<link rel="stylesheet" href="pico.classless.min.css" />
5252
<link rel="stylesheet" href="style.css" />
5353
</head>
5454
<body>
5555
<header>
5656
<div id="header-inner">
5757
<div id="header-branding">
5858
<h1><a class="h1-prefix" href="/">Lodes & Lanterns</a><span class="h1-title">Torchfinder 🔥</span></h1>
59-
<p>Find official and third-party <a href="https://www.thearcanelibrary.com" target="_blank" rel="noopener">Shadowdark</a> content.</p>
59+
<p>Find <a href="https://www.thearcanelibrary.com" target="_blank" rel="noopener">Shadowdark</a> content, official and third-party.</p>
6060
</div>
6161
<div id="header-controls">
6262
<button id="theme-toggle" aria-label="Toggle light/dark mode"></button>
@@ -85,14 +85,25 @@ <h1><a class="h1-prefix" href="/">Lodes & Lanterns</a><span class="h1-title">Tor
8585
<section class="filter-group">
8686
<button class="filter-group-toggle" aria-expanded="false">Category <span class="filter-group-chevron" aria-hidden="true"></span></button>
8787
<div class="filter-group-content" hidden>
88+
<input type="search" class="filter-search-within" data-target="filter-category" placeholder="Search categories…" aria-label="Search categories" disabled />
8889
<div id="filter-category" class="pill-group" role="group" aria-label="Filter by category"></div>
8990
</div>
9091
</section>
9192

93+
<!-- Format -->
94+
<section class="filter-group">
95+
<button class="filter-group-toggle" aria-expanded="false">Format <span class="filter-group-chevron" aria-hidden="true"></span></button>
96+
<div class="filter-group-content" hidden>
97+
<input type="search" class="filter-search-within" data-target="filter-format" placeholder="Search formats…" aria-label="Search formats" disabled />
98+
<div id="filter-format" class="pill-group" role="group" aria-label="Filter by format"></div>
99+
</div>
100+
</section>
101+
92102
<!-- System -->
93103
<section class="filter-group">
94104
<button class="filter-group-toggle" aria-expanded="false">System <span class="filter-group-chevron" aria-hidden="true"></span></button>
95105
<div class="filter-group-content" hidden>
106+
<input type="search" class="filter-search-within" data-target="filter-system" placeholder="Search systems…" aria-label="Search systems" disabled />
96107
<div id="filter-system" class="pill-group" role="group" aria-label="Filter by system"></div>
97108
</div>
98109
</section>
@@ -101,6 +112,7 @@ <h1><a class="h1-prefix" href="/">Lodes & Lanterns</a><span class="h1-title">Tor
101112
<section class="filter-group">
102113
<button class="filter-group-toggle" aria-expanded="false">Setting <span class="filter-group-chevron" aria-hidden="true"></span></button>
103114
<div class="filter-group-content" hidden>
115+
<input type="search" class="filter-search-within" data-target="filter-setting" placeholder="Search settings…" aria-label="Search settings" disabled />
104116
<div id="filter-setting" class="pill-group" role="group" aria-label="Filter by setting"></div>
105117
</div>
106118
</section>
@@ -109,6 +121,7 @@ <h1><a class="h1-prefix" href="/">Lodes & Lanterns</a><span class="h1-title">Tor
109121
<section class="filter-group">
110122
<button class="filter-group-toggle" aria-expanded="false">Environment <span class="filter-group-chevron" aria-hidden="true"></span></button>
111123
<div class="filter-group-content" hidden>
124+
<input type="search" class="filter-search-within" data-target="filter-environment" placeholder="Search environments…" aria-label="Search environments" disabled />
112125
<div id="filter-environment" class="pill-group" role="group" aria-label="Filter by environment"></div>
113126
</div>
114127
</section>
@@ -117,26 +130,11 @@ <h1><a class="h1-prefix" href="/">Lodes & Lanterns</a><span class="h1-title">Tor
117130
<section class="filter-group">
118131
<button class="filter-group-toggle" aria-expanded="false">Themes <span class="filter-group-chevron" aria-hidden="true"></span></button>
119132
<div class="filter-group-content" hidden>
133+
<input type="search" class="filter-search-within" data-target="filter-themes" placeholder="Search themes…" aria-label="Search themes" disabled />
120134
<div id="filter-themes" class="pill-group" role="group" aria-label="Filter by theme"></div>
121135
</div>
122136
</section>
123137

124-
<!-- Format -->
125-
<section class="filter-group">
126-
<button class="filter-group-toggle" aria-expanded="false">Format <span class="filter-group-chevron" aria-hidden="true"></span></button>
127-
<div class="filter-group-content" hidden>
128-
<div id="filter-format" class="pill-group" role="group" aria-label="Filter by format"></div>
129-
</div>
130-
</section>
131-
132-
<!-- Language -->
133-
<section class="filter-group">
134-
<button class="filter-group-toggle" aria-expanded="false">Language <span class="filter-group-chevron" aria-hidden="true"></span></button>
135-
<div class="filter-group-content" hidden>
136-
<div id="filter-languages" class="pill-group" role="group" aria-label="Filter by language"></div>
137-
</div>
138-
</section>
139-
140138
<!-- Level range -->
141139
<section class="filter-group">
142140
<button class="filter-group-toggle" aria-expanded="false">Level range <span class="filter-group-chevron" aria-hidden="true"></span></button>
@@ -180,7 +178,7 @@ <h1><a class="h1-prefix" href="/">Lodes & Lanterns</a><span class="h1-title">Tor
180178
<button class="filter-group-toggle" aria-expanded="false">Publisher <span class="filter-group-chevron" aria-hidden="true"></span></button>
181179
<div class="filter-group-content" hidden>
182180
<input type="search" class="filter-search-within" data-target="filter-publisher" placeholder="Search publishers…" aria-label="Search publishers" disabled />
183-
<div id="filter-publisher" class="pill-group pill-group-scrollable" role="group" aria-label="Filter by publisher"></div>
181+
<div id="filter-publisher" class="pill-group" role="group" aria-label="Filter by publisher"></div>
184182
</div>
185183
</section>
186184

@@ -189,7 +187,16 @@ <h1><a class="h1-prefix" href="/">Lodes & Lanterns</a><span class="h1-title">Tor
189187
<button class="filter-group-toggle" aria-expanded="false">Authors <span class="filter-group-chevron" aria-hidden="true"></span></button>
190188
<div class="filter-group-content" hidden>
191189
<input type="search" class="filter-search-within" data-target="filter-authors" placeholder="Search authors…" aria-label="Search authors" disabled />
192-
<div id="filter-authors" class="pill-group pill-group-scrollable" role="group" aria-label="Filter by author"></div>
190+
<div id="filter-authors" class="pill-group" role="group" aria-label="Filter by author"></div>
191+
</div>
192+
</section>
193+
194+
<!-- Language -->
195+
<section class="filter-group">
196+
<button class="filter-group-toggle" aria-expanded="false">Language <span class="filter-group-chevron" aria-hidden="true"></span></button>
197+
<div class="filter-group-content" hidden>
198+
<input type="search" class="filter-search-within" data-target="filter-languages" placeholder="Search languages…" aria-label="Search languages" disabled />
199+
<div id="filter-languages" class="pill-group" role="group" aria-label="Filter by language"></div>
193200
</div>
194201
</section>
195202

@@ -260,18 +267,18 @@ <h1><a class="h1-prefix" href="/">Lodes & Lanterns</a><span class="h1-title">Tor
260267
</p>
261268
<p>
262269
A <a href="https://lodesandlanterns.com" target="_blank" rel="noopener">Lodes &amp; Lanterns</a> project.
263-
<a href="https://github.com/Lodes-and-Lanterns/torchfinder/issues/new" target="_blank" rel="noopener">Found a bug?</a>
270+
<a href="https://github.com/Lodes-and-Lanterns/torchfinder/issues/new" target="_blank" rel="noopener">Found an issue?</a>
264271
</p>
265272
<p>
266-
<a href="https://github.com/Lodes-and-Lanterns/torchfinder" target="_blank" rel="noopener">GitHub</a>
267-
&middot;
268273
<a href="/dist/feed.xml">RSS Feed</a>
269274
&middot;
275+
<a href="https://github.com/Lodes-and-Lanterns/torchfinder" target="_blank" rel="noopener">GitHub</a>
276+
&middot;
270277
<a href="https://github.com/Lodes-and-Lanterns/torchfinder/blob/main/LICENSE" target="_blank" rel="noopener">MIT License</a>
271278
</p>
272279
</div>
273280
<div id="footer-disclaimer">
274-
Torchfinder and Lodes &amp; Lanterns are not affiliated with The Arcane Library, LLC. Shadowdark RPG &copy; 2023 The Arcane Library, LLC.
281+
Lodes &amp; Lanterns is not affiliated with The Arcane Library, LLC. Shadowdark RPG &copy; 2023 The Arcane Library, LLC.
275282
</div>
276283
</footer>
277284

pico.classless.min.css

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

robots.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
User-agent: *
2+
Allow: /

scripts/handlers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ export function onClearFilters() {
7777
input.value = '';
7878
const targetId = input.dataset.target;
7979
const container = document.getElementById(targetId);
80-
if (container && container._allValues) {
81-
buildPills(container, container._allValues, container._filterKey, container._labelFn);
80+
if (container && container._topValues) {
81+
buildPills(container, container._topValues, container._filterKey, container._labelFn);
8282
}
8383
});
8484

scripts/render.js

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { updateUrl } from './url.js';
1414

1515
// ---- Filter Sidebar ---------------------------------------------------------
1616

17+
export const FILTER_TOP_N = 8;
18+
1719
export function collectDistinctValues(field, isArray) {
1820
const values = new Set();
1921
for (const entry of state.data) {
@@ -26,6 +28,35 @@ export function collectDistinctValues(field, isArray) {
2628
return [...values].sort();
2729
}
2830

31+
// Returns the top n values for a field sorted by descending frequency across
32+
// all data entries. Ties are broken alphabetically.
33+
export function collectTopValues(field, isArray, n) {
34+
const counts = new Map();
35+
for (const entry of state.data) {
36+
const vals = isArray
37+
? entry[field] || []
38+
: entry[field] != null && entry[field] !== ''
39+
? [entry[field]]
40+
: [];
41+
for (const v of vals) {
42+
counts.set(v, (counts.get(v) || 0) + 1);
43+
}
44+
}
45+
return [...counts.entries()]
46+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
47+
.slice(0, n)
48+
.map(([v]) => v);
49+
}
50+
51+
// Returns topValues with any currently-selected values that didn't make the
52+
// top-N appended at the end, so active filters are always visible.
53+
function defaultVisibleValues(topValues, allValues, filterKey) {
54+
const selected = state.filters[filterKey] || [];
55+
const topSet = new Set(topValues);
56+
const extras = selected.filter((v) => !topSet.has(v) && allValues.includes(v));
57+
return [...topValues, ...extras];
58+
}
59+
2960
export function buildPills(container, values, filterKey, labelFn) {
3061
container.innerHTML = '';
3162
const selected = state.filters[filterKey] || [];
@@ -55,34 +86,49 @@ export function renderFilterSidebar() {
5586
for (const { id, key, isArray, fn, pinTop } of fields) {
5687
const container = document.getElementById(id);
5788
if (!container) continue;
58-
let values = collectDistinctValues(key, isArray);
89+
let allValues = collectDistinctValues(key, isArray);
90+
if (pinTop) {
91+
allValues = [...pinTop.filter((v) => allValues.includes(v)), ...allValues.filter((v) => !pinTop.includes(v))];
92+
}
93+
let topValues = collectTopValues(key, isArray, FILTER_TOP_N);
5994
if (pinTop) {
60-
values = [...pinTop.filter((v) => values.includes(v)), ...values.filter((v) => !pinTop.includes(v))];
95+
topValues = [...pinTop.filter((v) => topValues.includes(v)), ...topValues.filter((v) => !pinTop.includes(v))];
6196
}
62-
buildPills(container, values, key, fn);
63-
container._allValues = values;
97+
buildPills(container, defaultVisibleValues(topValues, allValues, key), key, fn);
98+
container._allValues = allValues;
99+
container._topValues = topValues;
64100
container._filterKey = key;
65101
container._labelFn = fn;
102+
const searchInput = document.querySelector(`.filter-search-within[data-target="${id}"]`);
103+
if (searchInput) searchInput.hidden = allValues.length <= FILTER_TOP_N;
66104
}
67105

68106
// Searchable groups
69107
const publisherVals = collectDistinctValues('publisher', false);
108+
const publisherTop = collectTopValues('publisher', false, FILTER_TOP_N);
70109
const authorVals = collectDistinctValues('authors', true);
110+
const authorTop = collectTopValues('authors', true, FILTER_TOP_N);
71111

72112
const pubContainer = document.getElementById('filter-publisher');
73113
if (pubContainer) {
74-
buildPills(pubContainer, publisherVals, 'publisher', (v) => v);
114+
buildPills(pubContainer, defaultVisibleValues(publisherTop, publisherVals, 'publisher'), 'publisher', (v) => v);
75115
pubContainer._allValues = publisherVals;
116+
pubContainer._topValues = publisherTop;
76117
pubContainer._filterKey = 'publisher';
77118
pubContainer._labelFn = (v) => v;
119+
const pubSearch = document.querySelector('.filter-search-within[data-target="filter-publisher"]');
120+
if (pubSearch) pubSearch.hidden = publisherVals.length <= FILTER_TOP_N;
78121
}
79122

80123
const authorContainer = document.getElementById('filter-authors');
81124
if (authorContainer) {
82-
buildPills(authorContainer, authorVals, 'authors', (v) => v);
125+
buildPills(authorContainer, defaultVisibleValues(authorTop, authorVals, 'authors'), 'authors', (v) => v);
83126
authorContainer._allValues = authorVals;
127+
authorContainer._topValues = authorTop;
84128
authorContainer._filterKey = 'authors';
85129
authorContainer._labelFn = (v) => v;
130+
const authorSearch = document.querySelector('.filter-search-within[data-target="filter-authors"]');
131+
if (authorSearch) authorSearch.hidden = authorVals.length <= FILTER_TOP_N;
86132
}
87133

88134
syncFilterControlStates();
@@ -124,12 +170,14 @@ export function renderResults() {
124170
paginationEl.innerHTML = '';
125171

126172
if (entry) {
173+
document.title = `${entry.title} — Torchfinder`;
127174
summary.textContent = entry.title;
128175
list.innerHTML = renderCardHtml(entry, true);
129176
attachCardListeners(list);
130177
const heading = list.querySelector('.card-title');
131178
if (heading) requestAnimationFrame(() => heading.focus());
132179
} else {
180+
document.title = 'Torchfinder';
133181
summary.textContent = '';
134182
list.innerHTML =
135183
'<div class="empty-state"><p>Entry not found.</p></div>';
@@ -138,6 +186,7 @@ export function renderResults() {
138186
return;
139187
}
140188

189+
document.title = 'Torchfinder';
141190
backBtn.hidden = true;
142191

143192
if (!state.data) return;

style.css

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -337,13 +337,6 @@ header {
337337
gap: 0.35rem;
338338
}
339339

340-
.pill-group-scrollable {
341-
max-height: 140px;
342-
overflow-y: auto;
343-
scrollbar-width: thin;
344-
padding: 0.25rem 0;
345-
}
346-
347340
.pill {
348341
display: inline-flex;
349342
align-items: center;

0 commit comments

Comments
 (0)