diff --git a/benchmarks/templates/benchmarks/base.html b/benchmarks/templates/benchmarks/base.html index 646e3ea12..88714d92d 100644 --- a/benchmarks/templates/benchmarks/base.html +++ b/benchmarks/templates/benchmarks/base.html @@ -5,14 +5,14 @@ - - + + diff --git a/benchmarks/templates/benchmarks/model.html b/benchmarks/templates/benchmarks/model.html index 064a29e51..4abcc10fb 100644 --- a/benchmarks/templates/benchmarks/model.html +++ b/benchmarks/templates/benchmarks/model.html @@ -263,10 +263,43 @@

Code examples + {% comment "BERG link + tooltip disabled in favor of the BERG info box below. To re-enable, remove comment tags and rename data-tooltip-html-disabled back to data-tooltip-html" %} +
+ + Brain Encoding Response Generator + +
+ {% endcomment %} + {# BERG info box #} +
+

+ Brain Encoding Response Generator (BERG) +

+

+ Through the + BERG + you can easily generate neural responses to + {% if model.domain == 'vision' %}images of your choice using any Brain-Score vision model.{% else %}text sentences of your choice using any Brain-Score language model.{% endif %} +

+

+ For more information on how to use BERG, see the + documentation + and + tutorial. +

+
+ {# Benchmarks bibtex #}

@@ -282,4 +315,13 @@

+ + + {% endblock %} diff --git a/static/benchmarks/css/components/tooltip.sass b/static/benchmarks/css/components/tooltip.sass index a1b2cc216..985fb65c9 100644 --- a/static/benchmarks/css/components/tooltip.sass +++ b/static/benchmarks/css/components/tooltip.sass @@ -64,6 +64,22 @@ &::after border-top-color: #333 + // Multi-line tooltip (for HTML content) + &.tooltip-multiline + white-space: normal + max-width: 400px + text-align: left + line-height: 1.5 + padding: 12px 16px + pointer-events: auto + + a + color: #90cdf4 + text-decoration: underline + + &:hover + color: white + // Tooltip fade-in animation @keyframes tooltipFadeIn from diff --git a/static/benchmarks/js/components/tooltip.js b/static/benchmarks/js/components/tooltip.js index 6240faf16..363dfae93 100644 --- a/static/benchmarks/js/components/tooltip.js +++ b/static/benchmarks/js/components/tooltip.js @@ -17,6 +17,7 @@ * @param {string} options.position - Tooltip position: 'top', 'bottom', 'left', 'right' (default: 'top') * @param {number} options.duration - Auto-hide duration in ms (default: 2500) * @param {number} options.offset - Distance from element in pixels (default: 10) + * @param {boolean} options.html - If true, render message as HTML instead of plain text (default: false) * @returns {Object|null} Object with `element` and `remove()` method, or null if element is invalid */ function createTooltip(element, message, options = {}) { @@ -24,7 +25,8 @@ function createTooltip(element, message, options = {}) { type = 'info', position = 'top', duration = 2500, - offset = 10 + offset = 10, + html = false } = options; if (!element) return null; @@ -41,7 +43,12 @@ function createTooltip(element, message, options = {}) { if (position !== 'top') { tooltip.classList.add(`tooltip-${position}`); } - tooltip.textContent = message; + if (html) { + tooltip.innerHTML = message; + tooltip.classList.add('tooltip-multiline'); + } else { + tooltip.textContent = message; + } // Position tooltip const rect = element.getBoundingClientRect(); @@ -109,22 +116,40 @@ function showTooltip(elementId, message, type = 'info') { */ function addHoverTooltip(element, message, options = {}) { if (!element) return; - + let currentTooltip = null; - - element.addEventListener('mouseenter', () => { - currentTooltip = createTooltip(element, message, { - ...options, - duration: 999999 // Don't auto-hide on hover - }); - }); - - element.addEventListener('mouseleave', () => { - if (currentTooltip) { - currentTooltip.remove(); - currentTooltip = null; + let hideTimeout = null; + + function show() { + if (hideTimeout) { clearTimeout(hideTimeout); hideTimeout = null; } + if (!currentTooltip) { + currentTooltip = createTooltip(element, message, { + ...options, + duration: 999999 + }); + if (currentTooltip && options.html) { + currentTooltip.element.addEventListener('mouseenter', function() { + if (hideTimeout) { clearTimeout(hideTimeout); hideTimeout = null; } + }); + currentTooltip.element.addEventListener('mouseleave', function() { + hide(); + }); + } } - }); + } + + function hide() { + var delay = options.html ? 200 : 0; + hideTimeout = setTimeout(function() { + if (currentTooltip) { + currentTooltip.remove(); + currentTooltip = null; + } + }, delay); + } + + element.addEventListener('mouseenter', show); + element.addEventListener('mouseleave', hide); } /** @@ -136,9 +161,16 @@ function initializeDataTooltips() { const message = element.getAttribute('data-tooltip'); const type = element.getAttribute('data-tooltip-type') || 'info'; const position = element.getAttribute('data-tooltip-position') || 'top'; - + addHoverTooltip(element, message, { type, position }); }); + document.querySelectorAll('[data-tooltip-html]').forEach(element => { + const message = element.getAttribute('data-tooltip-html'); + const type = element.getAttribute('data-tooltip-type') || 'info'; + const position = element.getAttribute('data-tooltip-position') || 'bottom'; + + addHoverTooltip(element, message, { type, position, html: true }); + }); } // Auto-initialize data tooltips when DOM is ready diff --git a/static/benchmarks/js/link-tracking.js b/static/benchmarks/js/link-tracking.js new file mode 100644 index 000000000..5d52fa271 --- /dev/null +++ b/static/benchmarks/js/link-tracking.js @@ -0,0 +1,90 @@ +/** + * Generic link click tracking for Google Analytics (gtag). + * + * Usage: + * trackLinks({ + * // Track a single element by ID + * ids: { + * 'berg-link': { category: 'BERG', label: 'main_link' } + * }, + * // Track elements matching a CSS selector, reading the label from a data attribute + * selectors: { + * '.berg-box-link': { category: 'BERG', labelAttr: 'data-berg-label' } + * }, + * // Delegated tracking: clicks inside a container, mapped by URL substring + * delegated: { + * '.tooltip-multiline a': { + * category: 'BERG', + * urlMap: { + * 'gifale95.github.io/BERG': 'tooltip_berg', + * 'readthedocs': 'tooltip_documentation', + * 'drive.google.com': 'tooltip_tutorial' + * }, + * fallbackLabel: 'unknown' + * } + * } + * }); + */ +function trackLinks(config) { + if (typeof gtag === 'undefined') return; + + // Track by element ID + if (config.ids) { + Object.keys(config.ids).forEach(function(id) { + var el = document.getElementById(id); + if (!el) return; + var opts = config.ids[id]; + el.addEventListener('click', function() { + gtag('event', 'click', { + event_category: opts.category, + event_label: opts.label, + transport_type: 'beacon' + }); + }); + }); + } + + // Track by CSS selector, with label from a data attribute or a fixed string + if (config.selectors) { + Object.keys(config.selectors).forEach(function(selector) { + var opts = config.selectors[selector]; + document.querySelectorAll(selector).forEach(function(el) { + el.addEventListener('click', function() { + var label = opts.labelAttr + ? this.getAttribute(opts.labelAttr) + : opts.label; + gtag('event', 'click', { + event_category: opts.category, + event_label: label, + transport_type: 'beacon' + }); + }); + }); + }); + } + + // Delegated click tracking: match clicked links by URL substring + if (config.delegated) { + Object.keys(config.delegated).forEach(function(selector) { + var opts = config.delegated[selector]; + document.addEventListener('click', function(e) { + var link = e.target.closest(selector); + if (!link) return; + var href = link.getAttribute('href') || ''; + var label = opts.fallbackLabel || 'unknown'; + var urls = Object.keys(opts.urlMap); + for (var i = 0; i < urls.length; i++) { + if (href.indexOf(urls[i]) !== -1) { + label = opts.urlMap[urls[i]]; + break; + } + } + gtag('event', 'click', { + event_category: opts.category, + event_label: label, + transport_type: 'beacon' + }); + }); + }); + } +}