diff --git a/layouts/partials/custom_js.html b/layouts/partials/custom_js.html
new file mode 100644
index 0000000000..6483386262
--- /dev/null
+++ b/layouts/partials/custom_js.html
@@ -0,0 +1,6 @@
+{{/* Custom JavaScript for FORRT site */}}
+
+{{/* Include clusters search functionality on clusters page */}}
+{{ if eq .RelPermalink "/clusters/" }}
+
+{{ end }}
diff --git a/static/js/clusters-search.js b/static/js/clusters-search.js
new file mode 100644
index 0000000000..291be7ab28
--- /dev/null
+++ b/static/js/clusters-search.js
@@ -0,0 +1,354 @@
+/**
+ * Clusters Page Search Functionality
+ * Enables searching within Bootstrap tab content that would otherwise be hidden from Ctrl-F
+ */
+(function() {
+ 'use strict';
+
+ // Wait for DOM to be ready
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+
+ function init() {
+ // Only run on clusters page
+ if (!window.location.pathname.includes('/clusters')) {
+ return;
+ }
+
+ createSearchInterface();
+ setupSearchHandlers();
+ }
+
+ function createSearchInterface() {
+ // Find the intro section to insert search box after it
+ const introSection = document.querySelector('.wg-blank');
+ if (!introSection) return;
+
+ // Create search container
+ const searchContainer = document.createElement('div');
+ searchContainer.className = 'cluster-search-container';
+ searchContainer.innerHTML = `
+
+ `;
+
+ // Insert after intro section
+ introSection.parentNode.insertBefore(searchContainer, introSection.nextSibling);
+ }
+
+ function setupSearchHandlers() {
+ const searchInput = document.getElementById('clusterSearchInput');
+ const searchBtn = document.getElementById('clusterSearchBtn');
+ const clearBtn = document.getElementById('clusterClearBtn');
+ const resultsDiv = document.getElementById('clusterSearchResults');
+
+ if (!searchInput || !searchBtn || !clearBtn) return;
+
+ // Search on button click
+ searchBtn.addEventListener('click', performSearch);
+
+ // Search on Enter key
+ searchInput.addEventListener('keypress', function(e) {
+ if (e.key === 'Enter') {
+ performSearch();
+ }
+ });
+
+ // Clear search
+ clearBtn.addEventListener('click', function() {
+ searchInput.value = '';
+ resultsDiv.innerHTML = '';
+ clearBtn.style.display = 'none';
+ removeAllHighlights();
+ collapseAllTabs();
+ });
+ }
+
+ function performSearch() {
+ const searchInput = document.getElementById('clusterSearchInput');
+ const clearBtn = document.getElementById('clusterClearBtn');
+ const resultsDiv = document.getElementById('clusterSearchResults');
+ const query = searchInput.value.trim();
+
+ if (!query || query.length < 2) {
+ resultsDiv.innerHTML = 'Please enter at least 2 characters to search.
';
+ return;
+ }
+
+ // Remove previous highlights
+ removeAllHighlights();
+
+ // Search through all tab content
+ const results = searchAllTabs(query);
+
+ // Display results
+ displayResults(results, query);
+
+ // Show clear button
+ clearBtn.style.display = 'inline-block';
+ }
+
+ function searchAllTabs(query) {
+ const results = [];
+ const queryLower = query.toLowerCase();
+
+ // Find all cluster sections
+ const clusterSections = document.querySelectorAll('section[id^="cluster"]');
+
+ clusterSections.forEach(function(section) {
+ const clusterTitle = section.querySelector('h3, h2, .home-section-title');
+ const clusterName = clusterTitle ? clusterTitle.textContent.trim() : 'Unknown Cluster';
+
+ // Find all tab panes in this cluster
+ const tabPanes = section.querySelectorAll('.tab-pane');
+
+ tabPanes.forEach(function(tabPane) {
+ const tabId = tabPane.id;
+ const content = tabPane.textContent || tabPane.innerText;
+ const contentLower = content.toLowerCase();
+
+ // Check if query is in content
+ if (contentLower.includes(queryLower)) {
+ // Count occurrences
+ const matches = countMatches(contentLower, queryLower);
+
+ // Get tab label
+ const tabLink = section.querySelector(`a[href="#${tabId}"]`);
+ const tabLabel = tabLink ? tabLink.textContent.trim() : tabId;
+
+ // Get a snippet of context
+ const snippet = getContextSnippet(content, query);
+
+ results.push({
+ cluster: clusterName,
+ tab: tabLabel,
+ tabId: tabId,
+ matches: matches,
+ snippet: snippet,
+ section: section,
+ tabPane: tabPane,
+ tabLink: tabLink
+ });
+ }
+ });
+ });
+
+ return results;
+ }
+
+ function countMatches(text, query) {
+ const regex = new RegExp(query, 'gi');
+ const matches = text.match(regex);
+ return matches ? matches.length : 0;
+ }
+
+ function getContextSnippet(text, query) {
+ const queryLower = query.toLowerCase();
+ const textLower = text.toLowerCase();
+ const index = textLower.indexOf(queryLower);
+
+ if (index === -1) return '';
+
+ const start = Math.max(0, index - 50);
+ const end = Math.min(text.length, index + query.length + 100);
+ let snippet = text.substring(start, end);
+
+ if (start > 0) snippet = '...' + snippet;
+ if (end < text.length) snippet = snippet + '...';
+
+ // Highlight the query in snippet
+ const regex = new RegExp(`(${escapeRegExp(query)})`, 'gi');
+ snippet = snippet.replace(regex, '$1');
+
+ return snippet;
+ }
+
+ function escapeRegExp(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ }
+
+ function displayResults(results, query) {
+ const resultsDiv = document.getElementById('clusterSearchResults');
+
+ if (results.length === 0) {
+ resultsDiv.innerHTML = `No results found for "${escapeHtml(query)}".
`;
+ return;
+ }
+
+ let html = `Found ${results.length} tab(s) containing "${escapeHtml(query)}" (${results.reduce((sum, r) => sum + r.matches, 0)} total matches). Click on a result to view it:
`;
+ html += '';
+ resultsDiv.innerHTML = html;
+
+ // Add click handlers to results
+ const resultLinks = resultsDiv.querySelectorAll('.cluster-search-result');
+ resultLinks.forEach(function(link) {
+ link.addEventListener('click', function(e) {
+ e.preventDefault();
+ const tabId = this.getAttribute('data-tab-id');
+ const result = results.find(r => r.tabId === tabId);
+ if (result) {
+ activateTab(result);
+ highlightMatches(result.tabPane, query);
+ // Scroll to the section
+ result.section.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ }
+ });
+ });
+
+ // Auto-expand first result
+ if (results.length > 0) {
+ activateTab(results[0]);
+ highlightMatches(results[0].tabPane, query);
+ }
+ }
+
+ function escapeHtml(text) {
+ const div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+ }
+
+ function activateTab(result) {
+ // Collapse all tabs first
+ collapseAllTabs();
+
+ // Activate the target tab
+ if (result.tabLink) {
+ result.tabLink.click();
+ }
+ }
+
+ function collapseAllTabs() {
+ document.querySelectorAll('.tab-pane.show.active').forEach(function(pane) {
+ pane.classList.remove('show', 'active');
+ });
+ document.querySelectorAll('.nav-link.active').forEach(function(link) {
+ link.classList.remove('active');
+ });
+ }
+
+ function highlightMatches(tabPane, query) {
+ if (!tabPane) return;
+
+ // Remove existing highlights first
+ removeHighlightsInElement(tabPane);
+
+ // Use mark.js-like approach to highlight matches
+ const regex = new RegExp(`(${escapeRegExp(query)})`, 'gi');
+
+ // Get all text nodes
+ const walker = document.createTreeWalker(
+ tabPane,
+ NodeFilter.SHOW_TEXT,
+ null,
+ false
+ );
+
+ const textNodes = [];
+ let node;
+ while ((node = walker.nextNode())) {
+ // Skip if parent is already a mark
+ if (node.parentElement.tagName !== 'MARK') {
+ textNodes.push(node);
+ }
+ }
+
+ textNodes.forEach(function(textNode) {
+ const text = textNode.textContent;
+ const testRegex = new RegExp(`(${escapeRegExp(query)})`, 'gi');
+ if (testRegex.test(text)) {
+ const fragment = document.createDocumentFragment();
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = text.replace(regex, '$1');
+ while (tempDiv.firstChild) {
+ fragment.appendChild(tempDiv.firstChild);
+ }
+ textNode.parentNode.replaceChild(fragment, textNode);
+ }
+ });
+ }
+
+ function removeAllHighlights() {
+ document.querySelectorAll('.cluster-highlight').forEach(function(mark) {
+ const parent = mark.parentNode;
+ parent.replaceChild(document.createTextNode(mark.textContent), mark);
+ parent.normalize();
+ });
+ }
+
+ function removeHighlightsInElement(element) {
+ element.querySelectorAll('.cluster-highlight').forEach(function(mark) {
+ const parent = mark.parentNode;
+ parent.replaceChild(document.createTextNode(mark.textContent), mark);
+ parent.normalize();
+ });
+ }
+
+ // Add CSS for highlighting
+ const style = document.createElement('style');
+ style.textContent = `
+ .cluster-highlight {
+ background-color: #ffeb3b;
+ padding: 2px 0;
+ font-weight: bold;
+ }
+
+ .cluster-search-container {
+ position: sticky;
+ top: 70px;
+ background: white;
+ z-index: 100;
+ padding: 20px 0 10px 0;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+ }
+
+ .cluster-search-result:hover {
+ cursor: pointer;
+ }
+
+ .cluster-search-result mark {
+ background-color: #ffeb3b;
+ padding: 1px 2px;
+ }
+ `;
+ document.head.appendChild(style);
+
+})();