Add controlled tag taxonomy and filters
Deploy production / deploy (push) Successful in 1s

This commit is contained in:
2026-08-11 22:01:22 -07:00
parent bdcad1321f
commit 2b4eb9f0f6
26 changed files with 438 additions and 36 deletions
+60 -8
View File
@@ -59,6 +59,63 @@ h1 { max-width: 650px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
.card-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 1px; background: var(--rule); border: 1px solid var(--rule); }
.entry-card { min-height: 245px; padding: 28px; background: var(--bg); }
.entry-tags { display: flex; flex-wrap: wrap; gap: 7px; margin: 16px 0; }
.entry-tags a,
.post-tags a,
.post-tags span {
border: 1px solid var(--teal);
padding: 3px 7px;
color: var(--teal);
font: 500 9px var(--mono);
letter-spacing: .08em;
text-transform: uppercase;
}
.entry-tags a:hover,
.entry-tags a:focus-visible,
.post-tags a:hover,
.post-tags a:focus-visible { border-color: var(--ink); color: var(--ink); }
.tag-filter { display: none; flex-wrap: wrap; gap: 8px; padding: 22px 0; }
.js .tag-filter { display: flex; }
.tag-filter-button {
border: 1px solid var(--rule);
background: transparent;
padding: 7px 10px;
color: var(--muted);
cursor: pointer;
font: 500 9px var(--mono);
letter-spacing: .08em;
text-transform: uppercase;
}
.tag-filter-button:hover,
.tag-filter-button:focus-visible,
.tag-filter-button.is-active { border-color: var(--teal); color: var(--teal); }
.filter-status {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
.filter-empty,
.empty-state { margin: 28px 0 0; color: var(--muted); }
.tag-directory { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; border: 1px solid var(--rule); background: var(--rule); }
.tag-directory-card {
display: grid;
grid-template-columns: 1fr auto;
gap: 10px 24px;
min-height: 170px;
padding: 28px;
background: var(--bg);
}
.tag-directory-card:hover,
.tag-directory-card:focus-visible { background: var(--panel); }
.tag-directory-name { font: 600 21px/1.3 var(--serif); }
.tag-directory-count { color: var(--teal); font: 500 9px var(--mono); letter-spacing: .1em; text-transform: uppercase; }
.tag-directory-summary { grid-column: 1 / -1; color: var(--muted); }
.content-section { padding: 55px 0 80px; }
.prose { max-width: 720px; }
.prose p { color: #c8c5bc; }
@@ -109,14 +166,6 @@ h1 { max-width: 650px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
}
.post-info dd { margin: 0; color: var(--ink); font-size: 14px; }
.post-tags { display: flex; flex-wrap: wrap; gap: 7px; }
.post-tags span {
border: 1px solid var(--teal);
padding: 3px 7px;
color: var(--teal);
font: 500 9px var(--mono);
letter-spacing: .08em;
text-transform: uppercase;
}
.article-info .post-info-title { color: var(--teal); }
.publication-links { display: grid; gap: 9px; margin: 0; padding: 0; list-style: none; }
.publication-links a {
@@ -143,6 +192,9 @@ h1 { max-width: 650px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
.hero, .hero.compact { padding-block: 44px; }
h1 { font-size: 30px; }
.card-grid { grid-template-columns: 1fr; }
.tag-directory { grid-template-columns: 1fr; }
.tag-filter { padding-block: 18px; }
.tag-directory-card { min-height: 0; }
.blog-post-layout,
.article-post-layout {
grid-template-columns: 1fr;
+57
View File
@@ -0,0 +1,57 @@
(() => {
document.documentElement.classList.add("js");
for (const filter of document.querySelectorAll("[data-tag-filter]")) {
const section = filter.closest(".activity-section");
const cards = [...section.querySelectorAll("[data-filter-item]")];
const buttons = [...filter.querySelectorAll("[data-tag-value]")];
const count = section.querySelector("[data-entry-count]");
const status = section.querySelector("[data-filter-status]");
const empty = section.querySelector("[data-filter-empty]");
const availableTags = new Set(buttons.map((button) => button.dataset.tagValue));
const applyFilter = (tag, updateUrl) => {
let visibleCount = 0;
for (const card of cards) {
const tags = new Set((card.dataset.tags || "").split(" ").filter(Boolean));
const visible = !tag || tags.has(tag);
card.hidden = !visible;
if (visible) visibleCount += 1;
}
for (const button of buttons) {
const active = button.dataset.tagValue === tag;
button.classList.toggle("is-active", active);
button.setAttribute("aria-pressed", String(active));
}
const entryLabel = visibleCount === 1 ? "entry" : "entries";
count.textContent = `${visibleCount} ${entryLabel}`;
status.textContent = tag
? `Showing ${visibleCount} ${entryLabel} tagged ${buttons.find((button) => button.dataset.tagValue === tag)?.textContent.trim()}.`
: `Showing all ${visibleCount} ${entryLabel}.`;
empty.hidden = visibleCount !== 0;
if (updateUrl) {
const url = new URL(window.location.href);
if (tag) url.searchParams.set("tag", tag);
else url.searchParams.delete("tag");
window.history.replaceState({}, "", url);
}
};
for (const button of buttons) {
button.addEventListener("click", () => applyFilter(button.dataset.tagValue, true));
button.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
applyFilter(button.dataset.tagValue, true);
}
});
}
const requestedTag = new URL(window.location.href).searchParams.get("tag") || "";
applyFilter(availableTags.has(requestedTag) ? requestedTag : "", false);
}
})();