vadugwi / app.js
deucebucket's picture
fix: drop stale tier breakdown groups; projective framing
0b3afa9 verified
Raw
History Blame Contribute Delete
27.2 kB
(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();
}
})();