diff --git a/make-pages/GalleryPage.hs b/make-pages/GalleryPage.hs
index 5dde804..a76f883 100644
--- a/make-pages/GalleryPage.hs
+++ b/make-pages/GalleryPage.hs
@@ -34,6 +34,8 @@ make' title prefix nsfw infos = [b|@0
+
+
$*title
diff --git a/script/gallery.js b/script/gallery.js
new file mode 100644
index 0000000..a5a6f14
--- /dev/null
+++ b/script/gallery.js
@@ -0,0 +1,84 @@
+(function() {
+'use strict';
+
+let items = Array.from(document.querySelectorAll('.item.post'));
+
+let reqBoxes = Array.from(document.querySelectorAll('#require input'));
+let excBoxes = Array.from(document.querySelectorAll('#exclude input'));
+let allBoxes = [...reqBoxes, ...excBoxes];
+
+let tags = new Map(items.map(item => [item, item.dataset.tags.split(';')]));
+
+
+function fillSets() {
+ let checkedValues = boxes =>
+ new Set(boxes.filter(b => b.checked).map(b => b.value));
+
+ return [checkedValues(reqBoxes), checkedValues(excBoxes)];
+}
+
+function update() {
+ let [reqTags, excTags] = fillSets();
+ let anyReq = reqTags.size > 0;
+
+ for (let item of items) {
+ let req = tags.get(item).some(x => reqTags.has(x));
+ let exc = tags.get(item).some(x => excTags.has(x));
+
+ if ((req || !anyReq) && !exc) {
+ item.classList.remove('hidden');
+ } else {
+ item.classList.add('hidden');
+ }
+ }
+}
+
+function converseId(id) {
+ if (id.match(/^require/)) {
+ return id.replace('require', 'exclude');
+ } else {
+ return id.replace('exclude', 'require');
+ }
+}
+
+function toggle(checkbox, thisSet, thatSet) {
+ if (checkbox.checked)
+ document.getElementById(converseId(checkbox.id)).checked = false;
+
+ update();
+}
+
+
+function clearForm() {
+ allBoxes.forEach(b => b.checked = false);
+}
+
+function clear(e) {
+ clearForm();
+ update();
+ if (e) e.preventDefault();
+}
+
+function resetForm() {
+ document.getElementById('filters').reset();
+}
+
+function reset(e) {
+ resetForm();
+ update();
+ if (e) e.preventDefault();
+}
+
+
+function setup() {
+ allBoxes.forEach(b => b.addEventListener('change', () => toggle(b)));
+
+ document.getElementById('clear').addEventListener('click', clear);
+ document.getElementById('reset').addEventListener('click', reset);
+
+ update();
+}
+
+window.addEventListener('load', setup);
+
+})();
diff --git a/style/shiny/gallery.css b/style/shiny/gallery.css
index d1c1c26..845674c 100644
--- a/style/shiny/gallery.css
+++ b/style/shiny/gallery.css
@@ -98,6 +98,10 @@ body {
background: hsl(340, 45%, 65%);
}
+.item.hidden {
+ display: none;
+}
+
.item:focus-within {
box-shadow: var(--focus-box);
}