(function () { 'use strict'; // ---------- state ---------- var state = { phase: 'intro', // intro | test | results probes: [], // the 35 drawn scenarios items: [], // 50-item interleaved sequence: {type:'scenario'|'word', ...} itemAnswers: [], // ratings for the 50 items, index-aligned with items answers: [], // derived scenario-only ratings (for compute/interpret/breakdown) currentQ: 0, cardVisible: true, theme: 'paper', poolSize: 0, nonce: null, profile: null, interp: null, wordGaps: null, barsReady: false }; var pool = null; var poolTries = 0; var drag = false; var timers = { t1: null, t2: null, tf: null, tb: null }; // ---------- dom ---------- var root = document.getElementById('app-root'); var el = { themeToggle: document.getElementById('theme-toggle'), phaseIntro: document.getElementById('phase-intro'), phaseTest: document.getElementById('phase-test'), phaseResults: document.getElementById('phase-results'), introGradient: document.getElementById('intro-gradient'), poolLine: document.getElementById('pool-line'), beginBtn: document.getElementById('begin-btn'), progressFill: document.getElementById('progress-fill'), progressText: document.getElementById('progress-text'), qIndex: document.getElementById('q-index'), wordPrompt: document.getElementById('word-prompt'), probeText: document.getElementById('probe-text'), valueDisplay: document.getElementById('value-display'), valueWord: document.getElementById('value-word'), rail: document.getElementById('rail'), railGradient: document.getElementById('rail-gradient'), railTicks: document.getElementById('rail-ticks'), railThumb: document.getElementById('rail-thumb'), card: document.getElementById('question-card'), prevBtn: document.getElementById('prev-btn'), nextBtn: document.getElementById('next-btn'), headlineCard: document.getElementById('headline-card'), headlineTitle: document.getElementById('headline-title'), headlineBody: document.getElementById('headline-body'), dimRows: document.getElementById('dim-rows'), insightList: document.getElementById('insight-list'), tierPanel: document.getElementById('tier-panel'), tierRead: document.getElementById('tier-read'), callbackPanel: document.getElementById('callback-panel'), callbackList: document.getElementById('callback-list'), wordgapPanel: document.getElementById('wordgap-panel'), wordgapList: document.getElementById('wordgap-list'), breakdownList: document.getElementById('breakdown-list'), restartBtn: document.getElementById('restart-btn') }; // ---------- constants ---------- var SCALE_MAP = { '-6':0,'-5':0,'-4':51,'-3':102,'-2':128,'-1':140,'0':153,'1':166,'2':179,'3':204,'4':230,'5':255,'6':255 }; var WORDS = ['Hits hard','Hits hard','Hits hard','Stings','Stings','Eases off','Doesn’t affect me','Lifts a little','Feels good','Feels good','Feels great','Feels great','Feels great']; var DIM_META = [ { key:'V', name:'Valence', lo:'negative bias', hi:'positive bias' }, { key:'A', name:'Arousal', lo:'calm / flat', hi:'reactive' }, { key:'D', name:'Dominance', lo:'powerless', hi:'in control' }, { key:'U', name:'Urgency', lo:'no pressure', hi:'everything urgent' }, { key:'G', name:'Gravity', lo:'feels heavy', hi:'feels light' }, { key:'W', name:'Self-Worth', lo:'self-critical', hi:'self-assured' }, { key:'I', name:'Intent', lo:'withdrawing', hi:'connecting' } ]; var TIER_META = [ { key:'N', name:'Every scene was neutral', desc:'Each one was written to be ambiguous — the meaning came from you. This is what you projected onto blank situations.' } ]; // ---------- palette / color ---------- function pal() { if (state.theme === 'dusk') return { clay:[220,114,83], sand:[150,139,118], green:[98,179,129], dims:{ V:'#6E9BD6', A:'#DB8154', D:'#A98AD2', U:'#DC6F5E', G:'#DFB35F', W:'#62BB8E', I:'#CDA552' }, rate:{ low:'#DC7253', high:'#62B381', mid:'#8E8369' }, ins:{ warn:'#D78B57', good:'#62B381', neutral:'#AC9F87' }, tier:{ N:'#B79BCB', NEG:'#DC7253', POS:'#62B381' } }; return { clay:[154,59,37], sand:[199,183,155], green:[47,107,69], dims:{ V:'#3F6FB0', A:'#C0623A', D:'#7A5BA6', U:'#B0453A', G:'#C2913E', W:'#3E8A6B', I:'#A8832F' }, rate:{ low:'#9A3B25', high:'#2F6B45', mid:'#A89B85' }, ins:{ warn:'#B26A3A', good:'#3E8A6B', neutral:'#9A8E79' }, tier:{ N:'#8A6B9E', NEG:'#B0453A', POS:'#3E8A6B' } }; } function colorFor(v) { var p = pal(); if (v === null || v === undefined) { var s = p.sand; return 'rgb(' + s[0] + ',' + s[1] + ',' + s[2] + ')'; } var a, b, t; if (v <= 0) { a = p.clay; b = p.sand; t = (v + 6) / 6; } else { a = p.sand; b = p.green; t = v / 6; } var c = a.map(function (x, i) { return Math.round(x + (b[i] - x) * t); }); return 'rgb(' + c[0] + ',' + c[1] + ',' + c[2] + ')'; } function gradientCss() { return 'linear-gradient(90deg,' + colorFor(-6) + ',' + colorFor(0) + ' 50%,' + colorFor(6) + ')'; } // ---------- theme ---------- function applyBodyBg(t) { try { var c = t === 'dusk' ? '#100E09' : '#ECE4D4'; document.body.style.background = c; document.documentElement.style.background = c; } catch (_) {} } function setTheme(t) { state.theme = t; root.setAttribute('data-theme', t); el.themeToggle.innerHTML = t === 'dusk' ? '☀' : '☾'; applyBodyBg(t); refreshGradients(); if (state.phase === 'test') renderTest(); if (state.phase === 'results') renderResults(); } function toggleTheme() { setTheme(state.theme === 'dusk' ? 'paper' : 'dusk'); } function refreshGradients() { var g = gradientCss(); el.introGradient.style.background = g; el.railGradient.style.background = g; el.progressFill.style.background = g; } // ---------- probe selection (ported from rtr_logic.js) ---------- function shuffle(arr) { for (var i = arr.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var t = arr[i]; arr[i] = arr[j]; arr[j] = t; } return arr; } function selectProbes(p) { var cats = ['V','A','D','U','G','W','I'], tiers = ['N','NEG','POS'], groups = {}; for (var ci = 0; ci < cats.length; ci++) for (var ti = 0; ti < tiers.length; ti++) groups[cats[ci] + '_' + tiers[ti]] = []; for (var i = 0; i < p.length; i++) { var k = p[i].cat + '_' + p[i].tier; if (groups[k]) groups[k].push(p[i]); } for (var key in groups) shuffle(groups[key]); var selected = []; for (var c2 = 0; c2 < cats.length; c2++) { var c = cats[c2], catProbes = []; var nArr = groups[c + '_N'], negArr = groups[c + '_NEG'], posArr = groups[c + '_POS']; if (nArr.length) catProbes.push(nArr.pop()); if (negArr.length) catProbes.push(negArr.pop()); if (posArr.length) catProbes.push(posArr.pop()); var remaining = []; for (var j = 0; j < nArr.length; j++) remaining.push(nArr[j]); for (var j2 = 0; j2 < negArr.length; j2++) remaining.push(negArr[j2]); for (var j3 = 0; j3 < posArr.length; j3++) remaining.push(posArr[j3]); shuffle(remaining); var need = 5 - catProbes.length; for (var j4 = 0; j4 < need && j4 < remaining.length; j4++) catProbes.push(remaining[j4]); for (var j5 = 0; j5 < catProbes.length; j5++) selected.push(catProbes[j5]); } shuffle(selected); return selected; } // ---------- words fetch + 50-item sequence ---------- function fetchWords() { // fail-soft: any error or empty -> resolve to [] (just the 35 scenarios) return fetch('/api/words?n=15', { headers: { 'Accept': 'application/json' } }) .then(function (r) { return r.ok ? r.json() : null; }) .then(function (j) { var ws = (j && Array.isArray(j.words)) ? j.words : []; return ws.filter(function (w) { return w && w.word; }).slice(0, 15); }) .catch(function () { return []; }); } // Build a single 50-item sequence (35 scenarios + up to 15 words), // interleaved so it doesn't feel like two blocks. function buildSequence(scenarios, words) { var items = []; for (var i = 0; i < scenarios.length; i++) { items.push({ type: 'scenario', probe: scenarios[i] }); } for (var j = 0; j < words.length; j++) { items.push({ type: 'word', word: words[j] }); } shuffle(items); return items; } function scenarioProbes() { var out = []; for (var i = 0; i < state.items.length; i++) { if (state.items[i].type === 'scenario') out.push(state.items[i].probe); } return out; } function scenarioAnswers() { var out = []; for (var i = 0; i < state.items.length; i++) { if (state.items[i].type === 'scenario') out.push(state.itemAnswers[i]); } return out; } // ---------- pool load (retry, like original _loadPool) ---------- function loadPool() { var p = (typeof window !== 'undefined' && window.RTR_POOL) ? window.RTR_POOL : null; if (p && p.length) { pool = p; state.probes = selectProbes(pool); state.poolSize = pool.length; el.poolLine.textContent = '50 questions — ' + state.probes.length + ' situations drawn from ' + state.poolSize + ', plus words — every run differs'; } else { poolTries += 1; if (poolTries < 120) setTimeout(loadPool, 40); } } // ---------- flow ---------- function clearTimers() { clearTimeout(timers.t1); clearTimeout(timers.t2); clearTimeout(timers.tf); clearTimeout(timers.tb); } function showPhase(phase) { state.phase = phase; el.phaseIntro.hidden = phase !== 'intro'; el.phaseTest.hidden = phase !== 'test'; el.phaseResults.hidden = phase !== 'results'; } function begin() { if (!state.probes.length) return; el.beginBtn.disabled = true; fetchWords().then(function (words) { // fail-soft: words may be [] (then sequence is just the 35 scenarios) state.items = buildSequence(state.probes, words); state.itemAnswers = new Array(state.items.length).fill(null); state.nonce = newNonce(); state.currentQ = 0; state.cardVisible = true; el.card.classList.remove('rtr-hidden'); el.beginBtn.disabled = false; showPhase('test'); renderTest(); }); } function newNonce() { try { if (window.crypto && typeof window.crypto.randomUUID === 'function') return window.crypto.randomUUID(); } catch (_) {} return 'n-' + Date.now() + '-' + Math.random().toString(16).slice(2); } function setProvisional(value) { state.itemAnswers[state.currentQ] = value; renderTest(); } function valueFromEvent(e) { var r = el.rail.getBoundingClientRect(); var ratio = (e.clientX - r.left) / r.width; ratio = Math.max(0, Math.min(1, ratio)); return Math.round(ratio * 12) - 6; } function railDown(e) { drag = true; try { el.rail.setPointerCapture(e.pointerId); } catch (_) {} setProvisional(valueFromEvent(e)); } function railMove(e) { if (drag) setProvisional(valueFromEvent(e)); } function railUp() { if (!drag) return; drag = false; if (state.itemAnswers[state.currentQ] === null) return; scheduleAdvance(); } function railKey(e) { var cur = state.itemAnswers[state.currentQ]; if (cur === null) cur = 0; if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') { e.preventDefault(); setProvisional(Math.max(-6, cur - 1)); } else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') { e.preventDefault(); setProvisional(Math.min(6, cur + 1)); } else if (e.key === 'Enter') { if (state.itemAnswers[state.currentQ] !== null) next(); } } function scheduleAdvance() { clearTimers(); timers.t1 = setTimeout(function () { state.cardVisible = false; el.card.classList.add('rtr-hidden'); timers.t2 = setTimeout(function () { var nx = state.currentQ + 1; if (nx < state.items.length) { state.currentQ = nx; state.cardVisible = true; el.card.classList.remove('rtr-hidden'); renderTest(); } else { goToResults(); } }, 260); }, 300); } function fadeTo(idx) { clearTimers(); state.cardVisible = false; el.card.classList.add('rtr-hidden'); timers.tf = setTimeout(function () { state.currentQ = idx; state.cardVisible = true; el.card.classList.remove('rtr-hidden'); renderTest(); }, 260); } function next() { if (state.itemAnswers[state.currentQ] === null) return; var nx = state.currentQ + 1; if (nx < state.items.length) fadeTo(nx); else goToResults(); } function prev() { if (state.currentQ > 0) fadeTo(state.currentQ - 1); } function goToResults() { clearTimers(); // Scenario answers feed the 7-D profile EXACTLY as before; word answers do not. state.probes = scenarioProbes(); state.answers = scenarioAnswers(); state.profile = window.RTR_COMPUTE.computeProfile(state.probes, state.answers); state.interp = window.RTR_INTERPRET.interpret(state.profile, state.probes, state.answers); state.wordGaps = null; state.barsReady = false; showPhase('results'); renderResults(); try { window.scrollTo(0, 0); } catch (_) {} timers.tb = setTimeout(function () { state.barsReady = true; renderDimRows(); }, 220); submitRun(); } function submitRun() { var scenarios = []; var words = []; for (var i = 0; i < state.items.length; i++) { var it = state.items[i], rating = state.itemAnswers[i]; if (rating === null) continue; if (it.type === 'scenario') { scenarios.push({ id: it.probe.id, cat: it.probe.cat, tier: it.probe.tier, rating: rating }); } else { words.push({ id: it.word.id, word: it.word.word, source: it.word.source, rating: rating }); } } // fail-soft: if /api/submit errors, results stay; just no word-gap section. fetch('/api/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nonce: state.nonce, scenarios: scenarios, words: words }) }) .then(function (r) { return r.ok ? r.json() : null; }) .then(function (j) { var gaps = (j && Array.isArray(j.wordGaps)) ? j.wordGaps : null; if (state.phase !== 'results') return; state.wordGaps = gaps; renderWordGaps(); }) .catch(function () { /* fail-soft: leave word-gap section hidden */ }); } function restart() { clearTimers(); state.probes = selectProbes(pool || []); state.items = []; state.itemAnswers = []; state.answers = []; state.nonce = null; state.currentQ = 0; state.profile = null; state.interp = null; state.wordGaps = null; state.barsReady = false; state.cardVisible = true; el.card.classList.remove('rtr-hidden'); showPhase('intro'); try { window.scrollTo(0, 0); } catch (_) {} } // ---------- render: test ---------- function pad(n) { return (n < 10 ? '0' : '') + n; } function buildTicks() { el.railTicks.innerHTML = ''; for (var i = 0; i < 13; i++) { var d = document.createElement('div'); d.className = 'rtr-tick'; d.style.left = (i / 12 * 100) + '%'; el.railTicks.appendChild(d); } } function renderTest() { var items = state.items, idx = state.currentQ, total = items.length; var item = items[idx]; var cur = state.itemAnswers[idx]; var done = state.itemAnswers.filter(function (a) { return a !== null; }).length; var hasVal = cur !== null; var dusk = state.theme === 'dusk'; var isWord = item && item.type === 'word'; if (isWord) { el.qIndex.textContent = 'Word ' + pad(idx + 1) + ' / ' + total; el.wordPrompt.hidden = false; el.wordPrompt.textContent = 'How does this word feel?'; el.probeText.className = 'rtr-probe-text rtr-is-word'; el.probeText.textContent = item.word.word; } else { el.qIndex.textContent = 'Situation ' + pad(idx + 1) + ' / ' + total; el.wordPrompt.hidden = true; el.probeText.className = 'rtr-probe-text'; el.probeText.textContent = '“' + (item && item.probe ? item.probe.text : '') + '”'; } el.progressFill.style.width = (done / total * 100) + '%'; el.progressText.textContent = done + ' / ' + total; el.valueDisplay.textContent = hasVal ? ((cur > 0 ? '+' : '') + cur) : '–'; el.valueDisplay.style.color = colorFor(cur); el.valueWord.textContent = hasVal ? WORDS[cur + 6] : 'Drag or tap the line'; // thumb if (hasVal) { el.railThumb.className = 'rtr-rail-thumb has-value'; el.railThumb.style.left = ((((cur) + 6) / 12) * 100) + '%'; el.railThumb.style.borderColor = colorFor(cur); } else { el.railThumb.className = 'rtr-rail-thumb no-value'; el.railThumb.style.left = '50%'; el.railThumb.style.borderColor = ''; } // prev el.prevBtn.disabled = idx === 0; el.prevBtn.style.color = idx === 0 ? (dusk ? '#544B3B' : '#CFC4B0') : (dusk ? '#AC9F87' : '#8C806C'); // next var last = idx === total - 1; el.nextBtn.disabled = !hasVal; el.nextBtn.textContent = last ? 'See results →' : 'Next →'; if (hasVal) { el.nextBtn.style.background = dusk ? '#F2EAD9' : '#241F18'; el.nextBtn.style.borderColor = dusk ? '#F2EAD9' : '#241F18'; el.nextBtn.style.color = dusk ? '#1A160F' : '#F4EEE2'; } else { el.nextBtn.style.background = 'transparent'; el.nextBtn.style.borderColor = dusk ? '#322C20' : '#D8CDB8'; el.nextBtn.style.color = dusk ? '#544B3B' : '#C4B89F'; } } // ---------- render: results ---------- function insAccent(kind) { var p = pal(); return kind === 'warn' ? p.ins.warn : kind === 'good' ? p.ins.good : p.ins.neutral; } function renderDimRows() { var pf = state.profile, p = pal(), dims = (state.interp && state.interp.dimensions) || []; var byKey = {}; for (var i = 0; i < dims.length; i++) byKey[dims[i].key] = dims[i]; el.dimRows.innerHTML = ''; DIM_META.forEach(function (d, di) { var idim = byKey[d.key] || {}; var val = (typeof idim.score === 'number') ? idim.score : pf[d.key]; var mid = 128, color = p.dims[d.key]; var span = Math.abs(val - mid) / mid; var half = (state.barsReady ? span : 0) * 50; var right = val >= mid; var reading = idim.reading || getReading(val, d.lo, d.hi); var line = idim.line || ''; var row = document.createElement('div'); row.className = 'rtr-dim-row'; var head = document.createElement('div'); head.className = 'rtr-dim-head'; var nm = document.createElement('span'); nm.className = 'rtr-dim-name'; nm.textContent = idim.name || d.name; var rd = document.createElement('span'); rd.className = 'rtr-dim-reading'; rd.style.color = color; rd.textContent = reading; head.appendChild(nm); head.appendChild(rd); var track = document.createElement('div'); track.className = 'rtr-dim-track'; var center = document.createElement('div'); center.className = 'rtr-dim-center'; var fill = document.createElement('div'); fill.className = 'rtr-dim-fill'; fill.style.left = right ? '50%' : (50 - half) + '%'; fill.style.width = half + '%'; fill.style.background = color; fill.style.borderRadius = right ? '0 7px 7px 0' : '7px 0 0 7px'; fill.style.transitionDelay = (di * 0.07) + 's'; track.appendChild(center); track.appendChild(fill); var foot = document.createElement('div'); foot.className = 'rtr-dim-foot'; var lo = document.createElement('span'); lo.textContent = d.lo; var hi = document.createElement('span'); hi.textContent = d.hi; foot.appendChild(lo); foot.appendChild(hi); row.appendChild(head); row.appendChild(track); row.appendChild(foot); if (line) { var ln = document.createElement('p'); ln.className = 'rtr-dim-line'; ln.textContent = line; row.appendChild(ln); } el.dimRows.appendChild(row); }); } function getReading(val, lo, hi) { if (val < 80) return lo; if (val < 118) return 'slightly ' + lo; if (val > 180) return hi; if (val > 138) return 'slightly ' + hi; return 'balanced'; } function renderHeadline() { var h = state.interp && state.interp.headline; if (h && (h.title || h.body)) { el.headlineCard.hidden = false; el.headlineTitle.textContent = h.title || ''; el.headlineBody.textContent = h.body || ''; el.headlineCard.style.borderLeftColor = 'var(--accent,#A8754E)'; } else { el.headlineCard.hidden = true; } } function renderInsights() { var list = (state.interp && state.interp.insights) || []; el.insightList.innerHTML = ''; if (!list.length) { el.insightList.parentElement.hidden = true; return; } el.insightList.parentElement.hidden = false; list.forEach(function (ins) { var d = document.createElement('div'); d.className = 'rtr-insight'; d.style.borderLeftColor = insAccent(ins.kind); var t = document.createElement('div'); t.className = 'rtr-insight-title'; t.textContent = ins.title || ''; var b = document.createElement('div'); b.className = 'rtr-insight-body'; b.textContent = ins.body || ''; d.appendChild(t); d.appendChild(b); el.insightList.appendChild(d); }); } function renderTierRead() { var tr = state.interp && state.interp.tierRead; if (!tr) { el.tierPanel.hidden = true; return; } el.tierPanel.hidden = false; el.tierRead.innerHTML = ''; var lines = [ { label: 'Floor', text: tr.floorLine }, { label: 'Ceiling', text: tr.ceilingLine }, { label: 'Baseline', text: tr.baselineLine } ]; lines.forEach(function (l) { if (!l.text) return; var row = document.createElement('div'); row.className = 'rtr-tier-line'; var lab = document.createElement('div'); lab.className = 'rtr-tier-label'; lab.textContent = l.label; var txt = document.createElement('div'); txt.className = 'rtr-tier-text'; txt.textContent = l.text; row.appendChild(lab); row.appendChild(txt); el.tierRead.appendChild(row); }); if (tr.summary) { var s = document.createElement('div'); s.className = 'rtr-tier-summary'; s.textContent = tr.summary; el.tierRead.appendChild(s); } } function renderCallbacks() { var list = (state.interp && state.interp.callbacks) || []; if (!list.length) { el.callbackPanel.hidden = true; return; } el.callbackPanel.hidden = false; el.callbackList.innerHTML = ''; list.forEach(function (cb) { var d = document.createElement('div'); d.className = 'rtr-callback'; var head = document.createElement('div'); head.className = 'rtr-callback-head'; var txt = document.createElement('span'); txt.className = 'rtr-callback-text'; txt.textContent = '“' + (cb.text || '') + '”'; var rate = document.createElement('span'); rate.className = 'rtr-callback-rating'; rate.textContent = cb.ratingStr || ''; head.appendChild(txt); head.appendChild(rate); d.appendChild(head); if (cb.note) { var note = document.createElement('div'); note.className = 'rtr-callback-note'; note.textContent = cb.note; d.appendChild(note); } el.callbackList.appendChild(d); }); } function renderBreakdown() { var P = state.probes, A = state.answers, p = pal(); el.breakdownList.innerHTML = ''; TIER_META.forEach(function (td) { var grp = document.createElement('div'); grp.className = 'rtr-grp'; var name = document.createElement('div'); name.className = 'rtr-grp-name'; name.style.color = p.tier[td.key]; name.textContent = td.name; var desc = document.createElement('p'); desc.className = 'rtr-grp-desc'; desc.textContent = td.desc; grp.appendChild(name); grp.appendChild(desc); for (var pi = 0; pi < P.length; pi++) { if (P[pi].tier !== td.key) continue; var raw = A[pi]; var rs = (raw > 0 ? '+' : '') + raw; var rc = raw <= -3 ? p.rate.low : raw >= 3 ? p.rate.high : p.rate.mid; var item = document.createElement('div'); item.className = 'rtr-grp-item'; var it = document.createElement('span'); it.className = 'rtr-grp-item-text'; it.textContent = '“' + P[pi].text + '”'; var ir = document.createElement('span'); ir.className = 'rtr-grp-item-rating'; ir.style.color = rc; ir.textContent = rs; item.appendChild(it); item.appendChild(ir); grp.appendChild(item); } el.breakdownList.appendChild(grp); }); } function renderWordGaps() { var list = state.wordGaps; if (!list || !list.length) { el.wordgapPanel.hidden = true; return; } el.wordgapPanel.hidden = false; el.wordgapList.innerHTML = ''; var p = pal(); list.forEach(function (wg) { var word = wg.word || ''; var unknown = wg.source === 'unknown'; var d = document.createElement('div'); d.className = 'rtr-insight'; var title = document.createElement('div'); title.className = 'rtr-insight-title'; title.textContent = '‘' + word + '’'; var body = document.createElement('div'); body.className = 'rtr-insight-body'; if (unknown) { d.style.borderLeftColor = p.ins.neutral; body.textContent = wg.note || ('the engine didn’t know this; your rating helps teach it.'); } else { var gap = (typeof wg.gap === 'number') ? wg.gap : null; d.style.borderLeftColor = gap === null ? p.ins.neutral : (gap < 0 ? p.ins.warn : p.ins.good); body.textContent = wg.note || ('you read it ' + (gap === null ? 'differently' : (gap < 0 ? 'colder' : 'warmer')) + ' than the language does'); } d.appendChild(title); d.appendChild(body); el.wordgapList.appendChild(d); }); } function renderResults() { renderHeadline(); renderDimRows(); renderInsights(); renderTierRead(); renderCallbacks(); renderWordGaps(); renderBreakdown(); } // ---------- wire up ---------- function init() { setTheme('paper'); refreshGradients(); buildTicks(); el.themeToggle.addEventListener('click', toggleTheme); el.beginBtn.addEventListener('click', begin); el.restartBtn.addEventListener('click', restart); el.prevBtn.addEventListener('click', prev); el.nextBtn.addEventListener('click', next); el.rail.addEventListener('pointerdown', railDown); el.rail.addEventListener('pointermove', railMove); el.rail.addEventListener('pointerup', railUp); el.rail.addEventListener('keydown', railKey); loadPool(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();