heroRoot = {
const intervals = [];
const track = (id) => { intervals.push(id); return id; };
const root = document.createElement('div');
root.className = 'irw-hero';
root.innerHTML = `
<div class="page">
<div class="header">
<div class="pill-badge">ITEM RESPONSE WAREHOUSE</div>
<h1 class="headline">
Every question ever answered<sup>*</sup>, in
<span class="highlight">one place
<svg viewBox="0 0 200 20" preserveAspectRatio="none">
<path d="M2,14 C30,4 50,18 75,10 C100,2 120,16 145,8 C165,2 180,14 198,8"
fill="none" stroke="var(--yellow)" stroke-width="5" stroke-linecap="round"/>
</svg>
</span>
</h1>
<p class="footnote">*Well, nearly every question!</p>
<p class="tagline"><strong>${CONFIG.responses}</strong> responses. True/false, five-star, multiple choice, Likert — all reshaped into one format.</p>
<a class="cta" href="data.qmd">Explore the warehouse →</a>
</div>
<div class="collage" id="irw-hero-collage">
<div class="zone-box" id="irw-hero-zone-tables">
<div class="box-tab">Tables</div>
<div class="zone-inner">
<div class="stamp bob" style="animation-delay:0s;">
<div class="stamp-number">${CONFIG.tables}+</div>
<div class="stamp-label">TABLES</div>
</div>
<div class="tag-cloud" id="irw-hero-table-domains"></div>
</div>
</div>
<div class="zone-box" id="irw-hero-zone-people">
<div class="box-tab">People</div>
<div class="zone-inner">
<div class="stamp bob" style="animation-delay:.5s;">
<div class="stamp-number">${CONFIG.people}</div>
<div class="stamp-label">PEOPLE</div>
</div>
<div class="tag-cloud" id="irw-hero-people-traits"></div>
</div>
</div>
<div class="zone-box" id="irw-hero-zone-items">
<div class="box-tab">Items</div>
<svg class="connectors" id="irw-hero-connectors"></svg>
<div class="zone-inner" id="irw-hero-items-inner">
<div class="stamp bob" id="irw-hero-stamp-items" style="animation-delay:.3s;">
<div class="stamp-number">${CONFIG.items}+</div>
<div class="stamp-label">ITEMS</div>
</div>
<div class="card bob" id="irw-hero-card-choice" style="animation-delay:.15s;"></div>
<div class="card bob" id="irw-hero-card-likert" style="animation-delay:0s;"></div>
<div class="card bob" id="irw-hero-card-slider" style="animation-delay:.45s;"></div>
</div>
</div>
<div class="zone-box" id="irw-hero-zone-responses">
<div class="box-tab">Responses</div>
<div class="zone-inner">
<div class="stamp bob" style="animation-delay:.2s;">
<div class="stamp-number">${CONFIG.responsesAbbrev}</div>
<div class="stamp-label">RESPONSES</div>
</div>
<div class="tag-cloud" id="irw-hero-response-attributes"></div>
</div>
</div>
</div>
<p class="caption">All numbers shown are approximate and metadata is available in limited cases.</p>
</div>
`;
const $ = (sel) => root.querySelector(sel);
function buildTagCloud(id, labels) {
const el = $(id);
el.innerHTML = labels.map((label, i) =>
`<span class="bubble"><span class="dot" style="background:${PALETTE[i % PALETTE.length]};"></span>${label}</span>`
).join('');
}
buildTagCloud('#irw-hero-table-domains', CONFIG.tableDomains);
buildTagCloud('#irw-hero-people-traits', CONFIG.peopleTraits);
buildTagCloud('#irw-hero-response-attributes', CONFIG.responseAttributes);
// Item-type card content.
const cardDefs = [
{ id:'#irw-hero-card-choice', badgeKey:'choice', dot:'var(--purple)', label:'Choose an answer',
body:`<div class="mini-single" data-single-group><div class="radio active">A</div><div class="radio">B</div><div class="radio">C</div><div class="radio">D</div></div>` },
{ id:'#irw-hero-card-likert', badgeKey:'likert', dot:'var(--pink)', label:'Strongly agree?',
body:`<div class="mini-likert" data-likert-group><span class="likert-edge">DIS</span><div class="likert-dot"></div><div class="likert-dot"></div><div class="likert-dot"></div><div class="likert-dot"></div><div class="likert-dot"></div><span class="likert-edge">AGR</span></div>` },
{ id:'#irw-hero-card-slider', badgeKey:'slider', dot:'var(--orange)', label:'How confident?',
body:`<div class="mini-slider"><div class="slider-track"><div class="slider-knob"></div></div><div class="slider-nums"><span>0</span><span>100</span></div></div>` }
];
cardDefs.forEach(def => {
const el = $(def.id);
const badge = CONFIG.badges[def.badgeKey];
el.innerHTML = `${badge ? `<div class="badge">${badge}</div>` : ''}
<div class="card-label"><span class="dot" style="background:${def.dot};"></span>${def.label}</div>
${def.body}`;
});
// Mini-demo animations (slider knob is pure CSS animation, see @keyframes
// irwHeroSlideKnob above).
root.querySelectorAll('[data-single-group]').forEach(group => {
const radios = group.querySelectorAll('.radio');
let idx = 0;
const render = () => radios.forEach((r, i) => r.classList.toggle('active', i === idx));
render();
track(setInterval(() => { idx = (idx + 1) % radios.length; render(); }, 900));
});
root.querySelectorAll('[data-likert-group]').forEach(group => {
const dots = group.querySelectorAll('.likert-dot');
let idx = 0, dir = 1;
const render = () => dots.forEach((d, i) => d.classList.toggle('active', i === idx));
render();
track(setInterval(() => {
idx += dir;
if (idx === dots.length - 1 || idx === 0) dir *= -1;
render();
}, 480));
});
// Dashed connector lines: each ring card -> center of ITEMS stamp.
// Scoped to the ITEMS box only; only meaningful once the >=960px ring
// layout is active; recomputed on resize (debounced) and via
// ResizeObserver so it stays correct across the 760px/960px breakpoints.
const svg = $('#irw-hero-connectors');
const inner = $('#irw-hero-items-inner');
const stampItems = $('#irw-hero-stamp-items');
const cardIds = ['#irw-hero-card-choice', '#irw-hero-card-likert', '#irw-hero-card-slider'];
const drawConnectors = () => {
if (window.innerWidth < 960) { svg.innerHTML = ''; return; }
const innerRect = inner.getBoundingClientRect();
if (innerRect.width === 0) return;
const items = stampItems.getBoundingClientRect();
const itemsCenter = {
x: items.left + items.width / 2 - innerRect.left,
y: items.top + items.height / 2 - innerRect.top
};
let html = '';
cardIds.forEach(id => {
const r = $(id).getBoundingClientRect();
const cx = r.left + r.width / 2 - innerRect.left;
const cy = r.bottom - innerRect.top;
html += `<line x1="${cx}" y1="${cy}" x2="${itemsCenter.x}" y2="${itemsCenter.y}"
stroke="#1c1c22" stroke-width="2" stroke-dasharray="6 6" stroke-opacity="0.35" />`;
});
svg.innerHTML = html;
};
requestAnimationFrame(() => requestAnimationFrame(drawConnectors));
let resizeTimer;
const onResize = () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(drawConnectors, 120); };
window.addEventListener('resize', onResize);
const resizeObserver = new ResizeObserver(() => drawConnectors());
resizeObserver.observe(inner);
invalidation.then(() => {
intervals.forEach(id => clearInterval(id));
clearTimeout(resizeTimer);
window.removeEventListener('resize', onResize);
resizeObserver.disconnect();
});
return root;
}