Toolkit: ship the rest of the ladder (Spark templates + handout, Circuit Video Maker + split tester, Nexus Leader Ops, grants, co-brand, partner code)

- Spark: one-tap banner/text campaign templates aimed at the member's link (POST /api/my/toolkit/template); printable QR handout at /handout/<username>
- Circuit: videomaker.js renders every promo video/short with the member's end card + QR via ffmpeg (Dockerfile adds ffmpeg + ttf-dejavu), hosted in Spaces; split tester compares join angles
- Nexus: Leader Ops (three-level triage, stalled flags, one-click nudges), AI team broadcast kind + Send to my team, credit grants from the leader's earned pool, co-branded join page (sponsor bio for level-3 sponsors), member-funded partner code (promo_codes.funder; redemption charges the funder) + /partners?ref=<username>
- toolkit ladder all live; chatbot updated; my.js v20260914e, site.css v20260914c, join.js/partners.js v20260914a

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-14 07:15:43 -05:00
parent 0f030a4943
commit 9316dfc0db
14 changed files with 436 additions and 27 deletions
+1
View File
@@ -94,6 +94,7 @@
sponsorName = sp.name;
$('jnSponName').textContent = sp.name + (sp.own ? ' (this is your own invite page; visitors see your name here)' : '');
if (sp.avatarUrl) { $('jnSponImg').src = sp.avatarUrl; $('jnSponImg').hidden = false; }
if (sp.cobrand && sp.bio && $('jnSponBio')) { $('jnSponBio').textContent = sp.bio; $('jnSponBio').hidden = false; $('jnSpon').classList.add('cobrand'); }
$('jnSpon').hidden = false;
}
}).catch(() => {});
+76 -2
View File
@@ -1487,21 +1487,95 @@
$('tkUsage').textContent = r.freeLeft + ' free this month · then ' + r.cost + ' credits each · you have ' + r.available.toLocaleString() + ' credits';
$('tkCostLine').textContent = r.freeLeft > 0 ? 'This one is free (' + r.freeLeft + ' left this month).' : 'This one costs ' + r.cost + ' credits from your earned pool.';
if (!$('tkKind').options.length) { $('tkKind').innerHTML = r.kinds.map(k => '<option value="' + k.key + '">' + esc(k.label) + '</option>').join(''); $('tkAngle').innerHTML = r.angles.map(a => '<option value="' + a + '">' + a + '</option>').join(''); }
renderTkTools(r);
$('tkHistory').innerHTML = r.history.length ? '<p class="small muted" style="margin:0 0 4px">Recent</p>' + r.history.map(h => '<div class="tk-hist"><span class="muted">' + esc(h.kind) + ' · ' + new Date(h.ts).toLocaleString() + (h.charged ? ' · ' + h.charged + ' credits' : ' · free') + '</span><br>' + esc(h.text).replace(/\n/g, '<br>') + '</div>').join('') : '';
} catch (e) {}
}
// the rest of the ladder: Spark templates + handout, Circuit Video Maker + split tester, Nexus Leader Ops
const tkEsc = t => String(t == null ? '' : t).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
const tkRank = { free: 0, spark: 1, surge: 2, circuit: 3, nexus: 4 };
async function tkPost(url, body) { const r = await (await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) })).json(); if (r.error) { IAP.status(r.error, 'bad'); return null; } return r; }
function renderTkTools(r) {
const at = tkRank[r.tier] || 0;
// Spark
const spark = at >= 1; $('tkSparkLocked').hidden = spark; $('tkSparkBody').hidden = !spark;
if (!spark) $('tkSparkLocked').textContent = 'Unlocks at Spark: switch on payouts in the Wallet tab. Then one tap builds a banner or text campaign aimed at your link, and you get a printable handout with your QR.';
else { $('tkHandoutLine').hidden = !r.handoutUrl; if (r.handoutUrl) $('tkHandout').href = r.handoutUrl; }
// Circuit
const circ = at >= 3; $('tkCircuitLocked').hidden = circ; $('tkCircuitBody').hidden = !circ;
if (!circ) $('tkCircuitLocked').textContent = 'Unlocks at Circuit: two qualifying buyers in your line. Then every promo video gets your own end card and QR, and the split tester shows which join angle pulls for you.';
else { loadTkVideos(r); loadTkSplit(); }
// Nexus
const nex = at >= 4; $('tkNexusLocked').hidden = nex; $('tkNexusBody').hidden = !nex;
if (!nex) $('tkNexusLocked').textContent = 'Unlocks at Nexus: five qualifying buyers. Then you get team triage across three levels with one-click nudges, AI-drafted team broadcasts, credit grants to your people, a co-branded join page and your own partner code.';
else { loadTkTeam(); renderTkPartner(r); }
}
async function loadTkVideos(r) {
const box = $('tkVideos');
if (!r.videoMaker) { box.innerHTML = '<p class="muted small">The Video Maker is warming up on the server. Check back shortly.</p>'; return; }
if (!r.username) { box.innerHTML = '<p class="muted small">Pick a username first (Profile); it goes on the end card.</p>'; return; }
try {
const v = await (await fetch('/api/my/toolkit/videos')).json(); if (v.error) return;
box.innerHTML = v.list.map(x => '<div class="tk-vid"><b>' + tkEsc(x.title) + '</b>' + (x.status === 'done' ? '<a class="btn small" href="' + tkEsc(x.url) + '" target="_blank" rel="noopener" download>Download</a> <button type="button" class="btn small sec" data-copyvid="' + tkEsc(x.url) + '">Copy link</button>' : x.status === 'queued' || x.status === 'working' ? '<span class="st">Rendering, about a minute. It appears here when done.</span>' : '<button type="button" class="btn small sec" data-mkvid="' + tkEsc(x.slug) + '">Make mine</button>') + '</div>').join('');
if (v.list.some(x => x.status === 'queued' || x.status === 'working')) setTimeout(() => loadTkVideos(r), 15000);
} catch (e) {}
}
async function loadTkSplit() {
try {
const sp = await (await fetch('/api/my/toolkit/split')).json(); if (sp.error) return;
const rows = sp.rows.filter(x => x.views || x.joins);
$('tkSplit').innerHTML = !rows.length ? '<p class="muted small">No link views yet. Share two different angle links this week and the winner shows up here.</p>'
: '<div style="overflow-x:auto"><table class="tk-table"><tr><th>Angle</th><th class="n">Views</th><th class="n">Last 30d</th><th class="n">Joins</th><th class="n">Buyers</th><th class="n">Join rate</th></tr>' + rows.map(x => '<tr' + (x.angle === sp.best ? ' class="best"' : '') + '><td>' + tkEsc(x.angle) + (x.angle === sp.best ? ' (winner so far)' : '') + '<br><span class="muted" style="font-size:11px">' + tkEsc(x.link) + '</span></td><td class="n">' + x.views + '</td><td class="n">' + x.views30 + '</td><td class="n">' + x.joins + '</td><td class="n">' + x.buyers + '</td><td class="n">' + x.rate + '%</td></tr>').join('') + '</table></div><p class="muted small" style="margin:6px 0 0">Rate = joins per 100 views. A winner needs at least 10 views. Send more traffic to the winner, keep testing the rest.</p>';
} catch (e) {}
}
let tkTeamData = null;
async function loadTkTeam() {
try {
const t = await (await fetch('/api/my/toolkit/team')).json(); if (t.error || !t.unlocked) return; tkTeamData = t;
$('tkTeamSub').textContent = t.total + ' in three levels (' + t.byLevel.join(' / ') + ') · ' + t.recent + ' new this week · ' + t.stalled.length + ' stalled';
const list = t.stalled.length ? t.stalled : t.members.slice(0, 12);
$('tkTeam').innerHTML = (t.stalled.length ? '<p class="small" style="margin:0 0 6px">Stalled first: quiet for a while and not yet past the next rung. One click sends them the right message from you.</p>' : '<p class="small muted" style="margin:0 0 6px">Nobody stalled right now. Your newest people:</p>')
+ list.map(m => '<div class="tk-team' + (m.stalled ? ' stalled' : '') + '"><div class="who"><b>' + tkEsc(m.name) + '</b> <span>L' + m.level + ' · ' + tkEsc(m.label) + (m.quietDays ? ' · quiet ' + m.quietDays + 'd' : '') + '</span><br><span>Next: ' + tkEsc(m.next) + '</span></div><button type="button" class="btn small sec" data-nudge="' + tkEsc(m.email) + '" data-say="' + tkEsc(m.say) + '">Nudge</button></div>').join('');
$('tkGrantTo').innerHTML = t.members.map(m => '<option value="' + tkEsc(m.email) + '">' + tkEsc(m.name) + ' (L' + m.level + ')</option>').join('');
} catch (e) {}
}
function renderTkPartner(r) {
const pk = r.partner;
$('tkPartner').innerHTML = pk
? '<p class="small" style="margin:0 0 6px">Code <b>' + tkEsc(pk.code) + '</b> gives ' + pk.credits + ' welcome credits, funded from your earned pool at each redemption. Redeemed ' + pk.uses + ' times.</p><p class="small" style="margin:0 0 4px">Invite link with the code: <code>' + tkEsc(pk.link) + '</code> <button type="button" class="btn small sec" data-copy="' + tkEsc(pk.link) + '">Copy</button></p><p class="small" style="margin:0 0 8px">Your partner kit page (for list owners and site owners you talk to): <code>' + tkEsc(pk.kit) + '</code> <button type="button" class="btn small sec" data-copy="' + tkEsc(pk.kit) + '">Copy</button></p><div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center"><label class="small">Welcome credits <input type="number" id="tkPcN" value="' + pk.credits + '" min="50" max="500" style="width:80px"></label><button type="button" class="btn small sec" id="tkPcGo" data-code="' + tkEsc(pk.code) + '">Update</button></div>'
: '<p class="small muted" style="margin:0 0 6px">Give your recruits a welcome bonus under your own code. Each redemption moves the credits from your earned pool to theirs, so it only costs you when it works. Then hand partners the kit page, which sends their deals under you.</p><div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center"><input id="tkPcCode" placeholder="CODE (letters, numbers)" maxlength="24" style="width:170px;text-transform:uppercase"><label class="small">Welcome credits <input type="number" id="tkPcN" value="100" min="50" max="500" style="width:80px"></label><button type="button" class="btn small" id="tkPcGo">Create my code</button></div>';
}
document.addEventListener('click', async ev => {
const b = ev.target.closest('[data-tpl],[data-mkvid],[data-copyvid],[data-nudge],[data-copy],#tkPcGo,#tkGrantGo,#tkDraftBc,[data-sendnudge]'); if (!b) return;
if (b.dataset.tpl) { b.disabled = true; const r = await tkPost('/api/my/toolkit/template', { kind: b.dataset.tpl, budget: Number($('tkTplBudget').value) }); b.disabled = false; if (r) { IAP.status('Campaign "' + r.name + '" created with ' + r.budget + ' credits. It is under Campaigns.', 'ok'); loadDashboard(); } return; }
if (b.dataset.mkvid) { b.disabled = true; const r = await tkPost('/api/my/toolkit/video', { slug: b.dataset.mkvid }); if (r) { IAP.status(r.status === 'done' ? 'Already made.' : 'Rendering. It appears here in about a minute.', 'ok'); loadTkVideos(tkState); } else b.disabled = false; return; }
if (b.dataset.copyvid || b.dataset.copy) { try { await navigator.clipboard.writeText(b.dataset.copyvid || b.dataset.copy); IAP.status('Copied.', 'ok'); } catch (e) { IAP.status('Copy failed; select it by hand.', 'bad'); } return; }
if (b.dataset.nudge) { const row = b.closest('.tk-team'); const old = row.querySelector('.tk-nudge'); if (old) { old.remove(); return; } const box = document.createElement('div'); box.className = 'tk-nudge'; box.innerHTML = '<textarea rows="3" style="width:100%" maxlength="600"></textarea><div style="display:flex;gap:8px;margin-top:6px"><button type="button" class="btn small" data-sendnudge="' + tkEsc(b.dataset.nudge) + '">Send from me</button><span class="muted small">Lands in their inbox and their email.</span></div>'; box.querySelector('textarea').value = b.dataset.say; row.appendChild(box); return; }
if (b.dataset.sendnudge) { const box = b.closest('.tk-nudge'); b.disabled = true; const r = await tkPost('/api/my/toolkit/nudge', { email: b.dataset.sendnudge, text: box.querySelector('textarea').value }); b.disabled = false; if (r) { IAP.status('Sent.', 'ok'); box.remove(); } return; }
if (b.id === 'tkGrantGo') { b.disabled = true; const r = await tkPost('/api/my/toolkit/grant', { email: $('tkGrantTo').value, credits: Number($('tkGrantN').value) }); b.disabled = false; if (r) { IAP.status(r.credits + ' credits moved to ' + r.toName + '.', 'ok'); loadToolkit(); loadDashboard(); } return; }
if (b.id === 'tkPcGo') { b.disabled = true; const r = await tkPost('/api/my/toolkit/promo', { code: b.dataset.code || ($('tkPcCode') ? $('tkPcCode').value : ''), credits: Number($('tkPcN').value) }); b.disabled = false; if (r) { IAP.status('Code ' + r.code + ' is live: ' + r.credits + ' welcome credits.', 'ok'); loadToolkit(); } return; }
if (b.id === 'tkDraftBc') { if (!tkState || !tkState.unlocked) return; $('tkKind').value = 'broadcast'; $('tkBrief').placeholder = 'What should the team focus on this week?'; $('tkEngine').scrollIntoView({ behavior: 'smooth', block: 'start' }); $('tkBrief').focus(); IAP.status('Pick a focus, hit Write it, then Send to my team.', 'ok'); return; }
});
$('tkKind') && $('tkKind').addEventListener('change', () => { if ($('tkSendBc')) $('tkSendBc').hidden = $('tkKind').value !== 'broadcast'; });
async function tkSendBroadcast() {
const txt = $('tkText').value || ''; const m = /^\s*Subject:\s*(.+)\n+([\s\S]+)$/.exec(txt);
const subject = m ? m[1].trim() : 'A note from your sponsor', body = (m ? m[2] : txt).trim();
if (!(await IAP.confirmBox('Send this to everyone in your three levels? One broadcast a day.', { title: 'Team broadcast', ok: 'Send' }))) return;
const r = await tkPost('/api/my/broadcast', { subject, body: '<p>' + tkEsc(body).replace(/\n{2,}/g, '</p><p>').replace(/\n/g, '<br>') + '</p>', scope: 'all' });
if (r) IAP.status('Sent to ' + (r.sent || r.count || r.recipients || 'your team') + '.', 'ok');
}
async function tkGenerate() {
const btn = $('tkGo'); if (btn.disabled) return; btn.disabled = true; btn.textContent = 'Writing…';
try {
const r = await (await fetch('/api/my/toolkit/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ kind: $('tkKind').value, angle: $('tkAngle').value, brief: $('tkBrief').value }) })).json();
if (r.error) { IAP.status(r.error, 'bad'); return; }
$('tkText').value = r.text; $('tkOut').hidden = false;
$('tkText').value = r.text; $('tkOut').hidden = false; $('tkSendBc').hidden = $('tkKind').value !== 'broadcast';
IAP.status(r.charged ? r.charged + ' credits used.' : 'Written. ' + r.freeLeft + ' free left this month.', 'ok');
loadToolkit(); loadDashboard();
} catch (e) { IAP.status('The engine did not answer. Try again.', 'bad'); }
finally { btn.disabled = false; btn.textContent = 'Write it'; }
}
if ($('tkGo')) { $('tkGo').addEventListener('click', tkGenerate); $('tkAgain').addEventListener('click', tkGenerate); $('tkCopy').addEventListener('click', async () => { try { await navigator.clipboard.writeText($('tkText').value); IAP.status('Copied.', 'ok'); } catch (e) { $('tkText').select(); } }); }
if ($('tkGo')) { $('tkGo').addEventListener('click', tkGenerate); $('tkAgain').addEventListener('click', tkGenerate); $('tkCopy').addEventListener('click', async () => { try { await navigator.clipboard.writeText($('tkText').value); IAP.status('Copied.', 'ok'); } catch (e) { $('tkText').select(); } }); $('tkSendBc').addEventListener('click', tkSendBroadcast); }
// promo tools: pill menu switches between posts / swipes / banners / wall / videos
function setPromoSub(name) {
const ids = ['toolkit', 'posts', 'text', 'swipe', 'banners', 'wall', 'objections', 'videos'];
+2 -1
View File
@@ -5,12 +5,13 @@
const q = new URLSearchParams(location.search);
const name = String(q.get('name') || '').replace(/[^A-Za-zÀ-ɏ' -]/g, '').trim().slice(0, 30);
const promo = String(q.get('promo') || '').toUpperCase().replace(/[^A-Z0-9_-]/g, '').slice(0, 24);
const ref = String(q.get('ref') || '').toLowerCase().replace(/[^a-z0-9_]/g, '').slice(0, 20); // a member's own partner kit (Nexus): the deal lands under them
const hello = document.getElementById('pkHello');
if (hello && name) { hello.textContent = name + ', this page is for you. Your spot directly under the company at the top is reserved; activate with the $20 package to claim it.'; hello.style.display = 'block'; }
const cta = document.getElementById('pkCta');
if (cta) {
const p = new URLSearchParams(); if (name) p.set('name', name); if (promo) p.set('promo', promo);
cta.href = '/join/company' + (p.toString() ? '?' + p.toString() : '');
cta.href = (ref ? '/join/' + ref : '/join/company') + (p.toString() ? '?' + p.toString() : '');
if (name) cta.textContent = 'Claim my spot, ' + name;
}
const ex = document.getElementById('pkExample');
+5
View File
@@ -699,3 +699,8 @@ img{max-width:100%}
.tk-tier ul{margin:0;padding-left:16px} .tk-tier li{margin:3px 0} .tk-tier li.soon{opacity:.65} .tk-tier li.soon::after{content:' (coming)';font-size:11px;color:var(--muted)}
.tk-tier .lock{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:#ffd15c}
.tk-hist{border-top:1px solid var(--line);padding:8px 0;font-size:13px} .tk-hist .muted{font-size:12px}
.tk-sec{margin-top:18px;padding-top:14px;border-top:1px solid var(--line)} .tk-grid{display:flex;gap:8px;flex-wrap:wrap}
.tk-vids{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:8px} .tk-vid{border:1px solid var(--line);border-radius:10px;padding:10px 12px;font-size:13px} .tk-vid b{display:block;font-weight:600;margin-bottom:4px} .tk-vid .st{font-size:12px;color:var(--muted)}
.tk-table{width:100%;border-collapse:collapse;font-size:13px} .tk-table th,.tk-table td{text-align:left;padding:6px 8px;border-bottom:1px solid var(--line)} .tk-table th{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)} .tk-table td.n{font-variant-numeric:tabular-nums;text-align:right} .tk-table th.n{text-align:right} .tk-table tr.best td{color:var(--mint)}
.tk-team{border:1px solid var(--line);border-radius:10px;padding:8px 12px;margin:6px 0;font-size:13px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:space-between} .tk-team .who b{font-weight:600} .tk-team .who span{color:var(--muted);font-size:12px} .tk-team.stalled{border-color:rgba(255,209,92,.45)}
.tk-nudge{width:100%;margin:8px 0 0;padding:8px 12px;border:1px dashed var(--line);border-radius:10px;font-size:13px}
+3 -2
View File
@@ -69,6 +69,7 @@
.jn-pk .n{color:var(--muted);font-size:11.5px;text-transform:uppercase;letter-spacing:.1em}
.jn-foot{color:var(--muted);font-size:13px;border-top:1px solid var(--line);margin-top:40px;padding:22px 0 50px}
.jn-err{color:var(--bad)}
.jn-spon.cobrand{border:1px solid rgba(67,232,195,.4);border-radius:14px;padding:10px 14px;margin-bottom:12px}.jn-spon.cobrand img{width:56px;height:56px;border-radius:50%}.jn-bio{display:block;margin-top:4px;color:var(--ink);font-size:14px;max-width:60ch}
</style>
</head>
<body>
@@ -79,7 +80,7 @@
</div>
<section class="jn-hero">
<div class="jn-spon" id="jnSpon" hidden><img id="jnSponImg" alt="" hidden><span class="small muted">Personal invitation from <b id="jnSponName" style="color:var(--mint)"></b></span></div>
<div class="jn-spon" id="jnSpon" hidden><img id="jnSponImg" alt="" hidden><span class="small muted">Personal invitation from <b id="jnSponName" style="color:var(--mint)"></b><br><span id="jnSponBio" class="jn-bio" hidden></span></span></div>
<p class="jn-brand">Instant<em>AdPay</em> <span id="jnEyebrow">· Advertise and earn on Polygon</span></p>
<p class="jn-hello" id="jnHello" hidden style="margin:8px 0 12px;padding:10px 14px;border:1px solid var(--mint);border-radius:12px;color:var(--mint);font-weight:700"></p>
<h1 id="jnHead">Advertise and earn.<br><em>Paid on-chain, instantly.</em></h1>
@@ -157,6 +158,6 @@
</div>
</div>
<script src="/assets/common.js?v=20260914a"></script>
<script src="/assets/join.js?v=20260913a"></script>
<script src="/assets/join.js?v=20260914a"></script>
</body>
</html>
+42 -3
View File
@@ -5,7 +5,7 @@
<title>Member area | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260914b">
<link rel="stylesheet" href="/assets/site.css?v=20260914c">
</head>
<body class="bo-body">
@@ -703,10 +703,49 @@
<label class="small">Brief (who it is for, what they said, what you want to say; or leave it blank)<textarea id="tkBrief" rows="3" style="width:100%" maxlength="800" placeholder="e.g. a friend who runs a safelist and says he never gets paid on time"></textarea></label>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin:8px 0"><button type="button" class="btn small" id="tkGo">Write it</button><span class="muted small" id="tkCostLine"></span></div>
<div id="tkOut" hidden><textarea id="tkText" rows="8" style="width:100%"></textarea>
<div style="display:flex;gap:8px;flex-wrap:wrap;margin:8px 0 0"><button type="button" class="btn small sec" id="tkCopy">Copy</button><button type="button" class="btn small sec" id="tkAgain">Another take</button></div></div>
<div style="display:flex;gap:8px;flex-wrap:wrap;margin:8px 0 0"><button type="button" class="btn small sec" id="tkCopy">Copy</button><button type="button" class="btn small sec" id="tkAgain">Another take</button><button type="button" class="btn small" id="tkSendBc" hidden>Send to my team</button></div></div>
<div id="tkHistory" style="margin-top:14px"></div>
</div>
</div>
<div id="tkSpark" class="tk-sec">
<div class="card-head"><h3>Spark: campaign templates and handout</h3></div>
<div id="tkSparkLocked" class="muted small" hidden></div>
<div id="tkSparkBody" hidden>
<p class="muted small" style="margin:0 0 8px">One tap builds a campaign aimed at your invite link, funded from the credits you already hold. Edit or pause it any time under Campaigns.</p>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin:0 0 8px"><label class="small">Budget <input type="number" id="tkTplBudget" value="200" min="50" max="5000" style="width:90px"></label><span class="muted small">credits</span></div>
<div class="tk-grid">
<button type="button" class="btn small sec" data-tpl="banner">Banner 300x250</button>
<button type="button" class="btn small sec" data-tpl="leaderboard">Banner 728x90</button>
<button type="button" class="btn small sec" data-tpl="text">Text ad</button>
<button type="button" class="btn small sec" data-tpl="text2">Text ad, free angle</button>
</div>
<p class="small" style="margin:10px 0 0" id="tkHandoutLine"><a id="tkHandout" target="_blank" rel="noopener">Print your handout</a> <span class="muted">two per sheet, your QR and your link on each</span></p>
</div>
</div>
<div id="tkCircuit" class="tk-sec">
<div class="card-head"><h3>Circuit: Video Maker and split tester</h3></div>
<div id="tkCircuitLocked" class="muted small" hidden></div>
<div id="tkCircuitBody" hidden>
<p class="muted small" style="margin:0 0 8px">Every promo video, re-rendered with your own end card: your name, your link and your QR on the last four seconds. Made once, hosted for you, ready to download or post.</p>
<div id="tkVideos" class="tk-vids"></div>
<h4 style="margin:16px 0 6px">Which angle is pulling for you</h4>
<div id="tkSplit"></div>
</div>
</div>
<div id="tkNexus" class="tk-sec">
<div class="card-head"><h3>Nexus: Leader Ops</h3><span class="sub" id="tkTeamSub"></span></div>
<div id="tkNexusLocked" class="muted small" hidden></div>
<div id="tkNexusBody" hidden>
<div id="tkTeam"></div>
<div style="display:flex;gap:8px;flex-wrap:wrap;margin:10px 0 0"><button type="button" class="btn small sec" id="tkDraftBc">Draft a team broadcast with the AI</button></div>
<h4 style="margin:16px 0 6px">Team credit grant</h4>
<p class="muted small" style="margin:0 0 6px">Move ad credits from your earned pool to anyone in your three levels. They get a message from you with it.</p>
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center"><select id="tkGrantTo" style="max-width:240px"></select><input type="number" id="tkGrantN" value="100" min="10" max="2000" style="width:90px"><button type="button" class="btn small" id="tkGrantGo">Send credits</button></div>
<h4 style="margin:16px 0 6px">Your partner code and kit</h4>
<div id="tkPartner"></div>
<p class="muted small" style="margin:8px 0 0">Co-branded join page: your bio (Profile) shows under your name on your invite page, so a visitor sees who is inviting them.</p>
</div>
</div>
</div>
<div class="card promo-sec" id="promo-posts">
<h3>Social posts, personalized with your link</h3>
@@ -945,7 +984,7 @@
<script src="/assets/common.js?v=20260914a"></script>
<script src="/assets/wallet.js?v=20260911a"></script>
<script src="/assets/promo.js?v=20260911a"></script>
<script src="/assets/my.js?v=20260914d"></script>
<script src="/assets/my.js?v=20260914e"></script>
<script src="/assets/chat.js?v=20260907l"></script>
</body>
</html>
+1 -1
View File
@@ -129,6 +129,6 @@
</footer>
</div>
<script src="/assets/common.js?v=20260914a"></script>
<script src="/assets/partners.js?v=20260912a"></script>
<script src="/assets/partners.js?v=20260914a"></script>
</body>
</html>