diff --git a/_includes/appendix-filter.html b/_includes/appendix-filter.html
new file mode 100644
index 000000000..9b031bb53
--- /dev/null
+++ b/_includes/appendix-filter.html
@@ -0,0 +1,29 @@
+
+
Explore by rules — select attributes, then choose how non-matching licenses appear:
+
+ Hide non-matching
+ Prioritize matches (dim others)
+
+
+ {% assign types = "permissions|conditions|limitations" | split: "|" %}
+ {% assign seen_tags = '' %}
+ {% for type in types %}
+
+ {{ type | capitalize }}
+ {% assign rules = site.data.rules[type] | sort: "label" %}
+ {% for rule_obj in rules %}
+ {% if seen_tags contains rule_obj.tag or rule_obj.tag contains '--' %}
+ {% continue %}
+ {% endif %}
+ {% capture seen_tags %}{{ seen_tags | append:rule_obj.tag }}{% endcapture %}
+
+
+ {{ rule_obj.label }}
+
+ {% endfor %}
+
+ {% endfor %}
+
+
Clear selection
+
+
diff --git a/_includes/css/responsive.css b/_includes/css/responsive.css
index 64524f36b..40d9a2e31 100644
--- a/_includes/css/responsive.css
+++ b/_includes/css/responsive.css
@@ -170,3 +170,68 @@
padding: 0 10px;
}
}
+
+.appendix-table-wrapper {
+ overflow-x: auto;
+ max-width: 100%;
+ margin-bottom: 2rem;
+}
+
+.appendix-filter {
+ margin: 1.5rem 0;
+ padding: 1rem;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ background: color-mix(in srgb, var(--backgroundColor) 92%, #149ad4 8%);
+}
+
+.appendix-filter-mode {
+ display: flex;
+ gap: 1.25rem;
+ margin: 0.75rem 0;
+ font-size: 0.9rem;
+}
+
+.appendix-filter-mode label {
+ font-weight: normal;
+}
+
+.appendix-filter-groups {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+}
+
+.appendix-filter-group {
+ border: none;
+ margin: 0;
+ padding: 0;
+ min-width: 12rem;
+}
+
+.appendix-filter-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ margin: 0.15rem 0.5rem 0.15rem 0;
+ font-size: 0.85rem;
+ font-weight: normal;
+}
+
+.appendix-filter-clear {
+ margin-top: 0.75rem;
+}
+
+.appendix-filter-status {
+ margin: 0.5rem 0 0;
+ font-size: 0.85rem;
+}
+
+.appendix-row-dimmed {
+ opacity: 0.35;
+}
+
+.appendix-row-match th,
+.appendix-row-match td {
+ background: color-mix(in srgb, var(--backgroundColor) 80%, #3dc637 20%);
+}
diff --git a/appendix.md b/appendix.md
index 62c119d98..fea52f37c 100644
--- a/appendix.md
+++ b/appendix.md
@@ -9,8 +9,12 @@ For reference, here is a table of every license described in the [choosealicense
If you're here to choose a license, **[start from the home page](/)** to see a few licenses that will work for most cases.
-
+{% include appendix-filter.html %}
+
+
+
{% assign types = "permissions|conditions|limitations" | split: "|" %}
+
License
{% assign seen_tags = '' %}
@@ -25,9 +29,12 @@ If you're here to choose a license, **[start from the home page](/)** to see a f
{% endfor %}
{% endfor %}
+
+
{% assign licenses = site.licenses | sort: "path" %}
{% for license in licenses %}
- {{ license.title }}
+ {% capture rule_tags %}{% for t in types %}{% for r in license[t] %}{{ r }},{% endfor %}{% endfor %}{% endcapture %}
+ {{ license.title }}
{% assign seen_tags = '' %}
{% for type in types %}
{% assign rules = site.data.rules[type] | sort: "label" %}
@@ -62,7 +69,9 @@ If you're here to choose a license, **[start from the home page](/)** to see a f
{% endfor %}
{% endfor %}
+
+
## Legend
@@ -93,3 +102,5 @@ If you're here to choose a license, **[start from the home page](/)** to see a f
{% endfor %}
{% endfor %}
+
+
diff --git a/assets/js/appendix-explorer.js b/assets/js/appendix-explorer.js
new file mode 100644
index 000000000..e0b67b325
--- /dev/null
+++ b/assets/js/appendix-explorer.js
@@ -0,0 +1,80 @@
+class AppendixExplorer {
+ constructor(root) {
+ this.root = root;
+ this.table = document.querySelector('.appendix-table');
+ this.tbody = this.table?.querySelector('tbody');
+ this.rows = Array.from(this.tbody?.querySelectorAll('tr') || []);
+ this.checkboxes = Array.from(root.querySelectorAll('input[type="checkbox"]'));
+ this.modeInputs = Array.from(root.querySelectorAll('input[name="appendix-filter-mode"]'));
+ this.statusEl = document.querySelector('#appendix-filter-status');
+ this.clearBtn = document.querySelector('#appendix-filter-clear');
+
+ this.checkboxes.forEach((cb) => cb.addEventListener('change', () => this.apply()));
+ this.modeInputs.forEach((input) => input.addEventListener('change', () => this.apply()));
+ this.clearBtn?.addEventListener('click', () => {
+ this.checkboxes.forEach((cb) => { cb.checked = false; });
+ this.apply();
+ });
+
+ this.apply();
+ }
+
+ selectedRules() {
+ return this.checkboxes.filter((cb) => cb.checked).map((cb) => cb.value);
+ }
+
+ mode() {
+ return this.modeInputs.find((input) => input.checked)?.value || 'hide';
+ }
+
+ rowMatches(row, rules) {
+ if (!rules.length) return true;
+ const rowTags = (row.dataset.rules || '').split(',').filter(Boolean);
+ return rules.every((rule) => rowTags.includes(rule));
+ }
+
+ apply() {
+ const rules = this.selectedRules();
+ const mode = this.mode();
+ let matches = 0;
+
+ this.rows.forEach((row) => {
+ row.classList.remove('appendix-row-dimmed', 'appendix-row-match');
+ row.hidden = false;
+ });
+
+ if (!rules.length) {
+ if (this.statusEl) this.statusEl.textContent = `Showing all ${this.rows.length} licenses.`;
+ return;
+ }
+
+ const matching = [];
+ const nonMatching = [];
+
+ this.rows.forEach((row) => {
+ if (this.rowMatches(row, rules)) {
+ matching.push(row);
+ row.classList.add('appendix-row-match');
+ matches += 1;
+ } else {
+ nonMatching.push(row);
+ }
+ });
+
+ if (mode === 'hide') {
+ nonMatching.forEach((row) => { row.hidden = true; });
+ } else {
+ [...matching, ...nonMatching].forEach((row) => this.tbody.appendChild(row));
+ nonMatching.forEach((row) => row.classList.add('appendix-row-dimmed'));
+ }
+
+ if (this.statusEl) {
+ this.statusEl.textContent = `${matches} license(s) match; ${nonMatching.length} do not (${mode === 'hide' ? 'hidden' : 'dimmed'}).`;
+ }
+ }
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ const root = document.querySelector('#appendix-filter');
+ if (root) new AppendixExplorer(root);
+});