// Split Tester client. Reuses the text-ad writer to produce candidates, then // launches the chosen ones as one test through the Traffic Desk. (function () { 'use strict'; var $ = function (id) { return document.getElementById(id); }; function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); } function n(v) { return Number(v || 0).toLocaleString(); } var state = { angle: 'general', angles: [], variants: [], picked: [], target: 'join', remaining: 0, limit: 0, hasPage: false, minPerArm: 500, maxArms: 3, id: null }; var busy = false; function chip(label, on, fn) { var b = document.createElement('button'); b.type = 'button'; b.className = 's-chip' + (on ? ' on' : ''); b.textContent = label; b.addEventListener('click', fn); return b; } function gate(msg) { $('sGate').style.display = 'block'; $('sGate').innerHTML = msg; $('sCard').style.opacity = '.55'; $('sGo').disabled = true; $('sWrite').disabled = true; } function renderAngles() { var host = $('sAngles'); host.innerHTML = ''; state.angles.forEach(function (a) { host.appendChild(chip(a.label, state.angle === a.key, function () { state.angle = a.key; renderAngles(); })); }); } function renderTargets() { var host = $('sTargets'); host.innerHTML = ''; host.appendChild(chip('My invite page', state.target === 'join', function () { state.target = 'join'; renderTargets(); })); if (state.hasPage) { host.appendChild(chip('My personal page', state.target === 'page', function () { state.target = 'page'; renderTargets(); })); } } function renderVariants() { var host = $('sVariants'); host.innerHTML = ''; if (!state.variants.length) return; var grid = document.createElement('div'); grid.className = 's-ads'; state.variants.forEach(function (v, i) { var pickIdx = state.picked.indexOf(i); var card = document.createElement('div'); card.className = 's-ad' + (pickIdx !== -1 ? ' on' : ''); if (pickIdx !== -1) { var tag = document.createElement('span'); tag.className = 'tag'; tag.textContent = String.fromCharCode(65 + pickIdx); card.appendChild(tag); } var s = document.createElement('div'); s.className = 's'; s.textContent = v.subject; card.appendChild(s); v.lines.forEach(function (l) { if (!l) return; var d = document.createElement('div'); d.className = 'l'; d.textContent = l; card.appendChild(d); }); card.addEventListener('click', function () { var at = state.picked.indexOf(i); if (at !== -1) state.picked.splice(at, 1); else if (state.picked.length < state.maxArms) state.picked.push(i); renderVariants(); splitHint(); }); grid.appendChild(card); }); host.appendChild(grid); var hint = document.createElement('div'); hint.className = 's-hint'; hint.textContent = 'Tap 2 or 3 of these to put them head to head. Tap again to unpick.'; host.appendChild(hint); } function splitHint() { var arms = state.picked.length; var total = Number($('sImp').value) || 0; var el = $('sSplitHint'); if (arms < 2) { el.textContent = 'Pick at least two versions above first.'; return; } var per = Math.floor(total / arms); var need = state.minPerArm * arms; el.innerHTML = arms + ' versions × ' + n(per) + ' impressions each. ' + (per < state.minPerArm ? 'Too thin to mean anything — use at least ' + n(need) + ' total for ' + arms + ' versions.' : 'Comes out of this month\'s allowance; stopping the test returns whatever has not served.'); } async function writeAds(btn) { btn.disabled = true; btn.innerHTML = 'Writing…'; $('sErr').style.display = 'none'; try { var r = await fetch('/api/public/suite-textads', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ angle: state.angle }) }); var d = await r.json(); if (!r.ok) { $('sErr').textContent = d.error || 'The writer could not produce versions just now.'; $('sErr').style.display = 'block'; } else { state.variants = d.variants || []; state.picked = state.variants.length >= 2 ? [0, 1] : []; renderVariants(); splitHint(); if (d.meter) $('sWriteMeter').textContent = d.meter.remaining + ' of ' + d.meter.limit + ' batches left this month'; } } catch (e) { $('sErr').textContent = 'Connection hiccup — try again.'; $('sErr').style.display = 'block'; } btn.disabled = false; btn.innerHTML = state.variants.length ? '✍️ Write different versions' : '✍️ Write me some versions'; } function renderTests(tests) { if (!tests || !tests.length) { $('sTestsWrap').style.display = 'none'; return; } $('sTestsWrap').style.display = 'block'; var host = $('sTests'); host.innerHTML = ''; tests.forEach(function (t) { var box = document.createElement('div'); box.className = 's-test'; var when = String(t.at || '').slice(0, 10); var best = (t.verdict && t.verdict.winner) || null; var rows = (t.armResults || []).map(function (a) { return '
| Version | Served | Clicks | Click rate |
|---|