diff --git a/assets/scss/custom.scss b/assets/scss/custom.scss
index 29d826e4551..ec641f506d2 100644
--- a/assets/scss/custom.scss
+++ b/assets/scss/custom.scss
@@ -2045,6 +2045,34 @@ h1.clusters-site-heading.clusters-hub-page__title {
.fr-specificity-toggle input {
margin: 0;
}
+.fr-clear-btn {
+ margin-left: auto;
+ align-self: center;
+ font-family: inherit;
+ font-size: 0.62rem;
+ font-weight: 600;
+ padding: 0.18rem 0.5rem;
+ border-radius: 3px;
+ border: 1px solid rgba(0,64,85,0.25);
+ background: transparent;
+ color: #004055;
+ cursor: pointer;
+ white-space: nowrap;
+ transition: background 0.15s, color 0.15s, border-color 0.15s;
+}
+.fr-clear-btn::before {
+ content: "\00d7";
+ margin-right: 0.3rem;
+ font-weight: 700;
+}
+.fr-clear-btn:hover {
+ background: #004055;
+ color: #fff;
+ border-color: #004055;
+}
+.fr-clear-btn[hidden] {
+ display: none;
+}
/* ---- Search input ---- */
.fr-search-wrap {
diff --git a/layouts/partials/clusters/clusters_controls.html b/layouts/partials/clusters/clusters_controls.html
index a8bd2db81d4..673037fa735 100644
--- a/layouts/partials/clusters/clusters_controls.html
+++ b/layouts/partials/clusters/clusters_controls.html
@@ -32,6 +32,8 @@
Show specialist resources
+
{{/* Inline search results panel (kept for cluster/subcluster name search) */}}
diff --git a/static/js/featured-resources.js b/static/js/featured-resources.js
index 7aff1938817..6c92d14fc16 100644
--- a/static/js/featured-resources.js
+++ b/static/js/featured-resources.js
@@ -705,33 +705,45 @@
var typeGroup = document.getElementById('fr-global-type-tags');
var specCheckbox = document.getElementById('fr-global-specificity-checkbox');
var searchInput = document.getElementById('clusters-inline-search-input');
+ var clearBtn = document.getElementById('fr-global-clear');
// --- Pre-cache card data so filtering never queries the DOM for text ---
var sectionCache = [];
document.querySelectorAll('.acc-section').forEach(function (section) {
+ var header = section.querySelector('.acc-header');
+ var body = section.querySelector('.acc-body');
+
+ /* Own text (heading + description) - a query matching only this, not any
+ card, still means the section is relevant and shouldn't disable/collapse. */
+ var labelEl = header ? header.querySelector('.acc-label') : null;
+ var descEl = section.querySelector('.sc-description');
+ var ownText = ((labelEl ? labelEl.textContent : '') + ' ' + (descEl ? descEl.textContent : '')).toLowerCase();
+
var container = section.querySelector('.fr-cards-list');
- if (!container) return;
- var cardEls = container.querySelectorAll('.fc-card, .fr-card');
var cards = [];
- cardEls.forEach(function (card) {
- var title = (card.querySelector('.fc-title') || card.querySelector('.fr-title') || {}).textContent || '';
- var summary = (card.querySelector('.fc-summary') || card.querySelector('.fr-summary') || {}).textContent || '';
- cards.push({
- el: card,
- focus: card.getAttribute('data-focus'),
- type: card.getAttribute('data-type'),
- spec: card.getAttribute('data-specificity'),
- text: (title + ' ' + summary).toLowerCase()
+ if (container) {
+ container.querySelectorAll('.fc-card, .fr-card').forEach(function (card) {
+ var title = (card.querySelector('.fc-title') || card.querySelector('.fr-title') || {}).textContent || '';
+ var summary = (card.querySelector('.fc-summary') || card.querySelector('.fr-summary') || {}).textContent || '';
+ cards.push({
+ el: card,
+ focus: card.getAttribute('data-focus'),
+ type: card.getAttribute('data-type'),
+ spec: card.getAttribute('data-specificity'),
+ text: (title + ' ' + summary).toLowerCase()
+ });
});
- });
- var header = section.querySelector('.acc-header');
- var body = section.querySelector('.acc-body');
+ }
+
+ if (!container && !ownText.trim()) return;
+
sectionCache.push({
el: section,
countEl: section.querySelector('.acc-count-match'),
header: header,
body: body,
chevron: header ? header.querySelector('.acc-chevron') : null,
+ ownText: ownText,
cards: cards
});
});
@@ -750,6 +762,24 @@
if (specCheckbox) specCheckbox.addEventListener('change', applyGlobalFilters);
+ if (clearBtn) {
+ clearBtn.addEventListener('click', function () {
+ if (searchInput) searchInput.value = '';
+ resetGroupToAll(focusGroup);
+ resetGroupToAll(typeGroup);
+ if (specCheckbox) specCheckbox.checked = false;
+ applyGlobalFilters();
+ if (searchInput) searchInput.focus();
+ });
+ }
+
+ function resetGroupToAll(group) {
+ if (!group) return;
+ group.querySelectorAll('.fr-tag-btn').forEach(function (b) {
+ b.classList.toggle('active', b.getAttribute('data-value') === 'all');
+ });
+ }
+
if (searchInput) {
var debounce;
searchInput.addEventListener('input', function () {
@@ -773,6 +803,7 @@
var query = searchInput ? searchInput.value.toLowerCase().trim() : '';
var tokens = query ? query.split(/\s+/) : [];
var isFiltered = activeFocus !== 'all' || activeType !== 'all' || tokens.length > 0;
+ if (clearBtn) clearBtn.hidden = !isFiltered;
// --- Pass 1: compute visibility from cached data (no DOM reads) ---
var sectionResults = [];
@@ -793,7 +824,19 @@
cardVis[c] = show;
if (show) matchCount++;
}
- sectionResults.push({ matchCount: matchCount, cardVis: cardVis });
+
+ /* A query matching only the section's own heading/description (no card)
+ still means it's relevant - keep it open rather than disabling it.
+ Only applies with no active facet, since facets are card-specific. */
+ var ownTextMatches = false;
+ if (tokens.length > 0 && activeFocus === 'all' && activeType === 'all' && sec.ownText) {
+ ownTextMatches = true;
+ for (var t2 = 0; t2 < tokens.length; t2++) {
+ if (sec.ownText.indexOf(tokens[t2]) === -1) { ownTextMatches = false; break; }
+ }
+ }
+
+ sectionResults.push({ matchCount: matchCount, cardVis: cardVis, ownTextMatches: ownTextMatches });
}
// --- Pass 2: batch all DOM writes ---
@@ -812,7 +855,7 @@
var chevron = sec.chevron;
if (!header || !body) continue;
- if (res.matchCount === 0) {
+ if (res.matchCount === 0 && !res.ownTextMatches) {
if (!header.classList.contains('acc-disabled')) {
header.dataset.wasOpen = body.classList.contains('acc-collapsed') ? '0' : '1';
}