/* DAREBench project page: shared helpers. Classic script (works over file://). Every number rendered on the page is read from window.DARE (assets/data/data.js). */ (function () { 'use strict'; var D = window.DARE; var U = (window.U = {}); U.D = D; U.IMG = 'assets/'; // data.js image paths (img/...) live under assets/ var SVGNS = 'http://www.w3.org/2000/svg'; U.$ = function (s, r) { return (r || document).querySelector(s); }; U.$$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); }; /* Element builder. attrs: class, text, attrs{}, data{}, on{}, style{}; children: nodes/strings. */ U.h = function (tag, o) { var el = document.createElement(tag); o = o || {}; if (o.class) el.className = o.class; if (o.text != null) el.textContent = o.text; if (o.attrs) for (var k in o.attrs) if (o.attrs[k] != null && o.attrs[k] !== false) el.setAttribute(k, o.attrs[k] === true ? '' : o.attrs[k]); if (o.data) for (var d in o.data) el.dataset[d] = o.data[d]; if (o.style) for (var st in o.style) el.style.setProperty(st, o.style[st]); if (o.on) for (var ev in o.on) el.addEventListener(ev, o.on[ev]); for (var i = 2; i < arguments.length; i++) U.append(el, arguments[i]); return el; }; U.append = function (el, c) { if (c == null || c === false) return el; if (Array.isArray(c)) { c.forEach(function (x) { U.append(el, x); }); return el; } el.appendChild(typeof c === 'string' || typeof c === 'number' ? document.createTextNode(String(c)) : c); return el; }; U.s = function (tag, attrs, parent) { var el = document.createElementNS(SVGNS, tag); if (attrs) for (var k in attrs) if (attrs[k] != null) el.setAttribute(k, attrs[k]); if (parent) parent.appendChild(el); return el; }; /* icon */ U.icon = function (id, cls) { var svg = document.createElementNS(SVGNS, 'svg'); svg.setAttribute('aria-hidden', 'true'); svg.setAttribute('focusable', 'false'); if (cls) svg.setAttribute('class', cls); var use = document.createElementNS(SVGNS, 'use'); use.setAttribute('href', '#' + id); svg.appendChild(use); return svg; }; /* ---- data access ---- */ U.get = function (path) { return String(path).split('.').reduce(function (o, k) { return o == null ? o : o[k]; }, D); }; var byName = {}; D.models.forEach(function (m) { byName[m.name] = m; byName[m.shortName] = m; }); U.model = function (n) { return byName[n]; }; U.group = function (k) { return D.groups.filter(function (g) { return g.key === k; })[0]; }; U.source = function (k) { return D.sources.filter(function (s) { return s.key === k; })[0]; }; U.GROUPS = D.groupOrder.map(U.group); U.TEXT_GROUPS = U.GROUPS.filter(function (g) { return g.modality === 'text'; }); U.MM_GROUPS = U.GROUPS.filter(function (g) { return g.modality !== 'text'; }); U.wlVar = function (k) { return 'var(--wl-' + k + ')'; }; U.css = function (name) { return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); }; /* ---- formatting (display only; never recomputes paper values) ---- */ U.int = function (n) { return Number(n).toLocaleString('en-US'); }; U.pct = function (n) { return n == null ? '—' : Number(n).toFixed(1); }; U.tok = function (k) { return k == null ? '—' : k + 'k'; }; U.usd = function (txt) { return txt == null ? '—' : (txt.charAt(0) === '<' ? '<$' + txt.slice(1) : '$' + txt); }; U.modalityCount = function () { var m = {}, f = {}; D.groups.forEach(function (g) { m[g.modality] = 1; f[g.form] = 1; }); return [Object.keys(m).length, Object.keys(f).length]; }; U.matrixLabel = function () { var mc = U.modalityCount(); return mc[0] + '×' + mc[1]; }; U.scoringLabel = { auto: 'Auto', judge: 'Judge', hybrid: 'Hybrid' }; U.SCORING_ORDER = ['auto', 'hybrid', 'judge']; // ordinal: increasing reliance on the LLM judge U.scoringVar = function (k) { return 'var(--score-' + k + ')'; }; /* Competition ranking (1,2,2,4) by value descending */ U.rank = function (items, val) { var sorted = items.slice().sort(function (a, b) { return val(b) - val(a); }); var ranks = new Map(); sorted.forEach(function (it, i) { if (i > 0 && val(it) === val(sorted[i - 1])) ranks.set(it, ranks.get(sorted[i - 1])); else ranks.set(it, i + 1); }); return ranks; }; /* Wrap numbers in a sentence with (text built with DOM nodes, not innerHTML) */ U.emphNums = function (text) { /* Bold standalone numbers only (not digits inside names such as GPT-5.4 or Qwen3.6-27B). */ var frag = document.createDocumentFragment(); var re = /(^|[\s(~+\u2248\u2192:;,])((?:\$|<\$?|[+\u2212])?\d[\d,]*(?:\.\d+)?(?:k|%|\u00d7)?)(?![\w\-.]*\w)/g, last = 0, m; while ((m = re.exec(text))) { var start = m.index + m[1].length; if (start > last) frag.appendChild(document.createTextNode(text.slice(last, start))); frag.appendChild(U.h('b', { text: m[2] })); last = start + m[2].length; } if (last < text.length) frag.appendChild(document.createTextNode(text.slice(last))); return frag; }; /* Render `code` spans from README markdown */ U.mdCode = function (text) { var frag = document.createDocumentFragment(); String(text).split(/(`[^`]+`)/g).forEach(function (part) { if (!part) return; if (part.charAt(0) === '`' && part.charAt(part.length - 1) === '`') frag.appendChild(U.h('code', { text: part.slice(1, -1) })); else frag.appendChild(document.createTextNode(part)); }); return frag; }; /* ---- bindings ---- */ U.bindAll = function (root) { U.$$('[data-bind]', root).forEach(function (el) { var v = U.get(el.getAttribute('data-bind')); if (v != null) el.textContent = v; }); U.$$('[data-href]', root).forEach(function (el) { var v = U.get(el.getAttribute('data-href')); if (v) { el.setAttribute('href', v); el.setAttribute('rel', 'noopener'); } }); }; /* ---- tooltip ---- */ var tip, tipOwner = null; U.tip = { el: function () { return tip || (tip = document.getElementById('tip')); }, /* spec: {title, rows:[{label, value, color}], note} */ show: function (spec, x, y, owner) { var t = U.tip.el(); t.textContent = ''; if (spec.title) t.appendChild(U.h('div', { class: 'tip__title', text: spec.title })); (spec.rows || []).forEach(function (r) { var key = U.h('span', { class: 'tip__key' }); if (r.color) key.style.borderTopColor = r.color; else key.style.borderTopColor = 'transparent'; t.appendChild(U.h('div', { class: 'tip__row' }, key, U.h('span', { class: 'tip__lab', text: r.label }), U.h('span', { class: 'tip__val', text: r.value }))); }); if (spec.note) t.appendChild(U.h('div', { class: 'tip__note', text: spec.note })); t.hidden = false; tipOwner = owner || null; var w = t.offsetWidth, hgt = t.offsetHeight, vw = window.innerWidth, vh = window.innerHeight; var left = x + 16, top = y + 16; if (left + w > vw - 8) left = Math.max(8, x - w - 16); if (top + hgt > vh - 8) top = Math.max(8, y - hgt - 16); t.style.left = left + 'px'; t.style.top = top + 'px'; }, showAt: function (spec, el) { var r = el.getBoundingClientRect(); U.tip.show(spec, r.left + r.width / 2, r.top + r.height / 2, el); }, hide: function (owner) { if (owner && tipOwner && owner !== tipOwner) return; var t = U.tip.el(); if (t) t.hidden = true; tipOwner = null; }, /* keyboard focus: show next to the element and remember it, so the tooltip can be re-anchored after a scroll instead of floating detached from its owner */ focus: function (specFn, el) { focusOwner = { el: el, spec: specFn }; U.tip.showAt(specFn(), el); }, blur: function (el) { if (focusOwner && focusOwner.el === el) focusOwner = null; U.tip.hide(el); } }; var focusOwner = null, scrollTimer = null; /* Attach hover + focus tooltip to an element; specFn returns the spec */ U.hoverTip = function (el, specFn) { el.addEventListener('pointermove', function (e) { U.tip.show(specFn(), e.clientX, e.clientY, el); }); el.addEventListener('pointerleave', function () { U.tip.hide(el); }); el.addEventListener('focus', function () { U.tip.focus(specFn, el); }); el.addEventListener('blur', function () { U.tip.blur(el); }); }; window.addEventListener('scroll', function () { U.tip.hide(); clearTimeout(scrollTimer); scrollTimer = setTimeout(function () { if (!focusOwner || document.activeElement !== focusOwner.el) { focusOwner = null; return; } var r = focusOwner.el.getBoundingClientRect(); if (r.bottom > 0 && r.top < window.innerHeight) U.tip.showAt(focusOwner.spec(), focusOwner.el); }, 160); }, { passive: true }); /* Escape dismisses the tooltip for good: forget the focus owner so a later scroll does not re-open it */ document.addEventListener('keydown', function (e) { if (e.key === 'Escape') { focusOwner = null; U.tip.hide(); } }); /* ---- chart <-> table toggles ---- */ U.initViewToggles = function () { U.$$('.view-toggle').forEach(function (btn) { var t = btn.getAttribute('data-target'); var chart = document.getElementById(t + '-chart'), table = document.getElementById(t + '-table'); if (!chart || !table) return; /* the visible label says what the button will show next ("View as table" / "View as chart"), so no aria-pressed: a changing name plus a pressed state would double-signal */ btn.removeAttribute('aria-pressed'); btn.setAttribute('aria-controls', t + '-chart ' + t + '-table'); btn.addEventListener('click', function () { var showTable = table.hidden; btn.classList.toggle('is-on', showTable); chart.hidden = showTable; table.hidden = !showTable; btn.querySelector('span').textContent = showTable ? 'View as chart' : 'View as table'; var use = btn.querySelector('use'); if (use) use.setAttribute('href', showTable ? '#i-chart' : '#i-table'); U.tip.hide(); document.dispatchEvent(new CustomEvent('viewtoggle', { detail: { target: t, table: showTable } })); }); }); }; /* Build a simple data table: cols [{label, num?, cls?, get(row)->string|node}] */ U.table = function (cols, rows, caption) { var t = U.h('table', { class: 'data-table' }); if (caption) t.appendChild(U.h('caption', { text: caption })); var thead = U.h('thead'), tr = U.h('tr'); cols.forEach(function (c) { tr.appendChild(U.h('th', { class: c.num ? 'num' : (c.cls || ''), text: c.label, attrs: { scope: 'col' } })); }); thead.appendChild(tr); t.appendChild(thead); var tb = U.h('tbody'); rows.forEach(function (r) { var row = U.h('tr'); cols.forEach(function (c, i) { var v = c.get(r); var cell = U.h(i === 0 && c.rowHeader !== false ? 'th' : 'td', { class: c.num ? 'num' : (c.cls || '') }); if (i === 0 && c.rowHeader !== false) cell.setAttribute('scope', 'row'); U.append(cell, v == null ? '—' : v); row.appendChild(cell); }); tb.appendChild(row); }); t.appendChild(tb); return t; }; /* Put a U.table into a chart's table view (host = the .view--table element), replacing what was there. The caption moves above the scroller so it never scrolls away with the columns; the scroller is a named, focusable region (keyboard users can scroll it with the arrow keys), and while it overflows a "swipe" hint shows and the right edge fades until the last column is reached. */ U.tableView = function (host, table) { host.textContent = ''; host.classList.add('tview'); var capEl = table.querySelector('caption'), capId = host.id + '-cap'; var cap = U.h('p', { class: 'tview__cap', text: capEl ? capEl.textContent : '', attrs: { id: capId } }); if (capEl) capEl.parentNode.removeChild(capEl); table.setAttribute('aria-labelledby', capId); var sc = U.h('div', { class: 'table-scroll', attrs: { tabindex: '0', role: 'region', 'aria-labelledby': capId } }, table); sc.addEventListener('scroll', function () { tviewState(host, sc); }, { passive: true }); host.appendChild(cap); host.appendChild(U.h('p', { class: 'tview__swipe', text: 'swipe the table sideways →', attrs: { 'aria-hidden': 'true' } })); host.appendChild(sc); tviewState(host, sc); return sc; }; function tviewState(host, sc) { var over = sc.scrollWidth > sc.clientWidth + 1; host.classList.toggle('is-over', over); sc.classList.toggle('at-end', !over || sc.scrollLeft + sc.clientWidth >= sc.scrollWidth - 2); } function tviewAll() { U.$$('.tview').forEach(function (host) { var sc = U.$('.table-scroll', host); if (sc) tviewState(host, sc); }); } var tviewTimer; window.addEventListener('resize', function () { clearTimeout(tviewTimer); tviewTimer = setTimeout(tviewAll, 80); }); document.addEventListener('viewtoggle', tviewAll); if (document.fonts && document.fonts.ready) document.fonts.ready.then(tviewAll); /* ---- segmented radio control ---- */ U.seg = function (container, options, value, onChange) { container.textContent = ''; var btns = options.map(function (o) { var b = U.h('button', { attrs: { type: 'button', role: 'radio', 'aria-checked': String(o.value === value), tabindex: o.value === value ? '0' : '-1' } }); if (o.dot) b.appendChild(U.h('span', { class: 'dot', style: { background: o.dot } })); b.appendChild(document.createTextNode(o.label)); if (o.title) b.title = o.title; b.addEventListener('click', function () { select(o.value, true); }); container.appendChild(b); return b; }); function select(v, fire) { options.forEach(function (o, i) { var on = o.value === v; btns[i].setAttribute('aria-checked', String(on)); btns[i].tabIndex = on ? 0 : -1; }); if (fire) onChange(v); } container.addEventListener('keydown', function (e) { var idx = btns.indexOf(document.activeElement); if (idx < 0) return; var n = null; if (e.key === 'ArrowRight' || e.key === 'ArrowDown') n = (idx + 1) % btns.length; if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') n = (idx - 1 + btns.length) % btns.length; if (n != null) { e.preventDefault(); btns[n].focus(); btns[n].click(); } }); return { select: select }; }; /* ---- copy to clipboard ---- */ U.copy = function (text, btn) { function done(ok) { var span = btn.querySelector('span'); var prev = span.textContent; span.textContent = ok ? 'Copied' : 'Press Ctrl+C'; btn.classList.toggle('is-done', ok); var use = btn.querySelector('use'); if (use && ok) use.setAttribute('href', '#i-check'); var live = document.getElementById('live'); if (live) live.textContent = ok ? 'Copied to clipboard' : 'Copy failed'; setTimeout(function () { span.textContent = prev === 'Copied' ? 'Copy' : prev; btn.classList.remove('is-done'); if (use) use.setAttribute('href', '#i-copy'); }, 1600); } function fallback() { var ta = U.h('textarea', { attrs: { readonly: true, 'aria-hidden': 'true' }, style: { position: 'fixed', left: '-9999px', top: '0' } }); ta.value = text; document.body.appendChild(ta); ta.select(); var ok = false; try { ok = document.execCommand('copy'); } catch (e) { ok = false; } document.body.removeChild(ta); done(ok); } if (navigator.clipboard && window.isSecureContext) navigator.clipboard.writeText(text).then(function () { done(true); }, fallback); else fallback(); }; U.copyButton = function (getText, label) { var b = U.h('button', { class: 'copy-btn', attrs: { type: 'button', 'aria-label': label || 'Copy to clipboard' } }, U.icon('i-copy'), U.h('span', { text: 'Copy', attrs: { 'aria-hidden': 'true' } })); b.addEventListener('click', function () { U.copy(getText(), b); }); return b; }; /* ---- lightbox ---- */ U.lightbox = function (src, alt, caption) { var dlg = document.getElementById('lightbox'); var img = document.getElementById('lightbox-img'); /* on phones, show the figure near its natural size inside a pannable box */ var pan = window.matchMedia && window.matchMedia('(max-width: 720px)').matches; dlg.classList.toggle('is-pan', !!pan); img.style.width = ''; img.onload = function () { if (pan) img.style.width = Math.min(img.naturalWidth, 1400) + 'px'; }; img.src = src; img.alt = alt || ''; if (pan && img.complete && img.naturalWidth) img.style.width = Math.min(img.naturalWidth, 1400) + 'px'; document.getElementById('lightbox-cap').textContent = caption || ''; if (typeof dlg.showModal === 'function') dlg.showModal(); else dlg.setAttribute('open', ''); }; U.initLightbox = function () { var dlg = document.getElementById('lightbox'); document.getElementById('lightbox-close').addEventListener('click', function () { dlg.close ? dlg.close() : dlg.removeAttribute('open'); }); dlg.addEventListener('click', function (e) { if (e.target === dlg) dlg.close(); }); }; U.fig = function (key) { return D.caseStudy.figures.filter(function (f) { return f.key === key; })[0]; }; /* zoomable figure button */ U.zoomable = function (src, alt, caption, label, opts) { opts = opts || {}; var img = U.h('img', { attrs: { src: src, alt: alt || '', loading: opts.eager ? null : 'lazy', decoding: 'async' } }); var b = U.h('button', { class: 'zoomable', attrs: { type: 'button', 'aria-label': 'Enlarge ' + (label || 'figure') } }, img, U.h('span', { class: 'zoomable__hint' }, U.icon('i-zoom'), 'Enlarge')); b.addEventListener('click', function () { U.lightbox(src, alt, caption); }); return b; }; /* ---- resize ---- */ U.onResize = function (el, fn) { var last = 0, t; var ro = new ResizeObserver(function (entries) { var w = Math.round(entries[0].contentRect.width); if (w === last || w === 0) return; last = w; clearTimeout(t); t = setTimeout(function () { fn(w); }, 60); }); ro.observe(el); }; U.reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; /* Nice linear ticks */ U.ticks = function (lo, hi, n) { var span = hi - lo, step = Math.pow(10, Math.floor(Math.log10(span / n))), err = span / n / step; if (err >= 7.5) step *= 10; else if (err >= 3.5) step *= 5; else if (err >= 1.5) step *= 2; var out = [], v = Math.ceil(lo / step) * step; for (; v <= hi + 1e-9; v += step) out.push(Math.round(v * 1e6) / 1e6); return out; }; /* Log-axis tick labels (1-2-5 per decade) without collisions: 1s first, then 5s, then 2s; a label is kept only if it clears every kept label by `gap` px and stays inside [x0, x1]. ticks: [{v, k, x}] -> returns the subset whose labels should be drawn. */ U.fitTicks = function (ticks, fmt, x0, x1, font, gap) { gap = gap == null ? 8 : gap; var kept = []; [1, 5, 2].forEach(function (k) { ticks.filter(function (t) { return t.k === k; }).forEach(function (t) { var w = U.textWidth(fmt(t.v), font || '400 12.5px Nunito'); var a = t.x - w / 2, b = t.x + w / 2; if (a < x0 - 1 || b > x1 + 1) return; for (var i = 0; i < kept.length; i++) if (a < kept[i].b + gap && b > kept[i].a - gap) return; kept.push({ a: a, b: b, t: t }); }); }); return kept.map(function (x) { return x.t; }); }; U.usdTick = function (v) { var dec = Math.max(0, -Math.floor(Math.log10(v) + 1e-9)); return '$' + v.toFixed(dec); }; /* ---- roving focus: one Tab stop for a set of marks, arrow keys move between them ---- items: array of focusable elements (in navigation order). onFocus(i) is called when an item receives focus. Returns {set(i)} to move the tab stop programmatically. */ U.roving = function (items, onFocus, opts) { opts = opts || {}; var cur = Math.max(0, Math.min(items.length - 1, opts.start || 0)); items.forEach(function (el, i) { el.setAttribute('tabindex', i === cur ? '0' : '-1'); el.addEventListener('focus', function () { cur = i; items.forEach(function (x, j) { x.setAttribute('tabindex', j === i ? '0' : '-1'); }); if (onFocus) onFocus(i); }); el.addEventListener('keydown', function (e) { var n = null; if (e.key === 'ArrowRight' || e.key === 'ArrowDown') n = Math.min(items.length - 1, i + 1); else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') n = Math.max(0, i - 1); else if (e.key === 'Home') n = 0; else if (e.key === 'End') n = items.length - 1; if (n != null) { e.preventDefault(); items[n].focus(); } }); }); return { set: function (i) { cur = i; items.forEach(function (x, j) { x.setAttribute('tabindex', j === i ? '0' : '-1'); }); } }; }; /* ---- comic starburst accent (decorative; the number it sits next to is real text) ---- */ U.burst = function (text, variant) { return U.h('span', { class: 'burst' + (variant ? ' burst--' + variant : ''), attrs: { 'aria-hidden': 'true' } }, U.h('span', { class: 'burst__t', text: text })); }; /* Text width measurement for SVG label layout */ var measureCtx; U.textWidth = function (text, font) { measureCtx = measureCtx || document.createElement('canvas').getContext('2d'); measureCtx.font = font || '800 12.5px Nunito'; return measureCtx.measureText(text).width; }; })();