Member pages: send readers to the proof, not back to the same video

Marty spotted that a member's page played the angle video and then its button
sent readers to /join/<id>?v=<angle> — which is a SQUEEZE step that shows
that same video again before anything else.

His instinct was to jump straight to /join-now. That fixes the replay but
skips the proof layer: the live payout feed, the 2-4-8-16 explainer, the
toolkit line and the sponsor card all live on the full invite page. Going
from a personal story directly to "connect your wallet and send POL" is a big
jump for somebody who arrived cold from a Traffic Desk banner.

So the button now drops the ?v= and lands on the full invite page. The
existing design already gives exactly the wanted behaviour — the squeeze step
is skipped, the proof is kept, and no new parameter was needed.

The QR deliberately keeps the angle: a scanned code often reaches someone who
never saw the page, off a printout, and the hook video is what they need.

Also: payout alerts now name what an upgrade unlocked, on upgrade pass-ups
only. Marty noticed the toolkit was never mentioned — the unlock line was
wired to `upgraded` events, which are rare next to payouts. On a pass-up the
buyer below just climbed, so saying what they unlocked makes the reason for
the payment concrete. Entry rewards stay silent: nobody changed level.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-29 05:26:05 -05:00
parent e6b1151ace
commit 6838ab3845
3 changed files with 63 additions and 4 deletions
+10 -1
View File
@@ -1500,7 +1500,16 @@ chain.startIndexer(evt=>{
let text; let text;
if(evt.type==='registered')text=`📈 TEAM BUILD: new position!\n#${evt.id} registered under #${evt.referrerId} (${evt.tierName}).`; if(evt.type==='registered')text=`📈 TEAM BUILD: new position!\n#${evt.id} registered under #${evt.referrerId} (${evt.tierName}).`;
else if(evt.type==='upgraded'){const tl=suiteTools.unlockText(evt.level,evt.levelName,getConfig());text=`🚀 TEAM BUILD: #${evt.id} upgraded to ${evt.levelName}.${tl?`\n🧰 Unlocked: ${tl}.`:''}`;} else if(evt.type==='upgraded'){const tl=suiteTools.unlockText(evt.level,evt.levelName,getConfig());text=`🚀 TEAM BUILD: #${evt.id} upgraded to ${evt.levelName}.${tl?`\n🧰 Unlocked: ${tl}.`:''}`;}
else text=`💸 TEAM BUILD: #${evt.toId} just got PAID ${evt.pol.toFixed(2)} POL${evt.kind==='upline'?` (${evt.gen?`Gen ${evt.gen} `:''}upgrade pass-up from #${evt.fromId})`:` (referral reward from #${evt.fromId})`}.${contact(evt.toId)}`; else {
// On an upgrade pass-up, say what the BUYER unlocked. This is the exact
// moment "your level is your reach" is provable — someone below climbed,
// and that is why this payment landed here. Upgrades are rare next to
// payouts, so without this the toolkit is almost never mentioned.
// Entry rewards get no tool line: nobody changed level.
const upTools = evt.kind==='upline' ? suiteTools.unlockText(0,evt.levelName,getConfig()) : '';
text=`💸 TEAM BUILD: #${evt.toId} just got PAID ${evt.pol.toFixed(2)} POL${evt.kind==='upline'?` (${evt.gen?`Gen ${evt.gen} `:''}upgrade pass-up from #${evt.fromId})`:` (referral reward from #${evt.fromId})`}.${contact(evt.toId)}`;
if(upTools) text+=`\n🧰 #${evt.fromId} reached ${evt.levelName} and unlocked ${upTools}.`;
}
if(evt.tx)text+=`\nhttps://polygonscan.com/tx/${evt.tx}`; if(evt.tx)text+=`\nhttps://polygonscan.com/tx/${evt.tx}`;
sendTelegram(text); sendTelegram(text);
// recruiting-framed copy of the SAME event to the new-members topic // recruiting-framed copy of the SAME event to the new-members topic
+46 -2
View File
@@ -47,6 +47,36 @@ function list(id) {
return out; return out;
} }
// How many pages a position may HOLD at once, by level (index = level - 1).
// This is a different thing from the monthly BUILD quota in suite-meter: builds
// are how often you may run the writer, this is how many pages you may keep.
// They used to contradict each other — an Ascensus member was allowed 3 builds
// a month but every build overwrote the same page, so they could never have
// more than one. Anyone who can build a page can now keep several, because the
// moment somebody thinks about two different audiences they want two pages.
const PAGE_LIMIT = [0, 2, 3, 5, 8, 15, 25, 50];
function pageLimit(level) {
const lv = Math.max(1, Math.min(8, Number(level) || 1));
return PAGE_LIMIT[lv - 1] || 0;
}
// Slug from the headline the writer produced, so the address describes the page
// without asking the member to invent one. Falls back to the angle, then to a
// short random suffix, and always de-duplicates against what they already hold.
function slugFromTitle(id, title, angle) {
let base = slugify(title || '');
if (base.length < 3) base = slugify(angle || 'page');
if (!base) base = 'page';
const taken = {};
list(id).forEach(function (p) { if (p.slug) taken[p.slug] = 1; });
if (!taken[base]) return base;
for (let i = 2; i < 40; i++) {
const c = slugify(base.slice(0, 20) + '-' + i);
if (!taken[c]) return c;
}
return slugify(base.slice(0, 16) + '-' + Math.floor(Date.now() / 1000).toString(36));
}
// Angles reuse the team's existing hook videos + their landing variant. // Angles reuse the team's existing hook videos + their landing variant.
const ANGLES = { const ANGLES = {
pocket: { label: 'Pocket change', video: '/v/rmc-pocket-change-b403fb2f75.mp4', poster: '/v/rmc-pocket-change-poster.jpg' }, pocket: { label: 'Pocket change', video: '/v/rmc-pocket-change-b403fb2f75.mp4', poster: '/v/rmc-pocket-change-poster.jpg' },
@@ -119,6 +149,20 @@ function esc(s) {
function render(rec) { function render(rec) {
const a = ANGLES[rec.angle] || ANGLES.overview; const a = ANGLES[rec.angle] || ANGLES.overview;
// The button and the QR go to DIFFERENT places on purpose.
//
// `?v=<angle>` is not a normal landing page — it is a SQUEEZE step that hides
// everything except the hook video and one button. A reader of this page has
// just watched that exact video, so sending them there replayed it before
// they could act. Dropping the `?v=` lands them on the full invite page
// instead: payout feed, proof, sponsor card, join button. That is the proof
// layer, and skipping straight past it to the payment screen would be too big
// a jump for someone who arrived cold from an ad.
//
// The QR keeps the angle, because a scanned code often reaches somebody who
// never saw this page — off a printout or a screenshot — and the hook video
// is exactly what they need first.
const joinLink = 'https://rmcircle.team/join/' + rec.id;
const link = 'https://rmcircle.team/join/' + rec.id + (rec.angle && rec.angle !== 'overview' ? '?v=' + rec.angle : ''); const link = 'https://rmcircle.team/join/' + rec.id + (rec.angle && rec.angle !== 'overview' ? '?v=' + rec.angle : '');
const who = esc(rec.name || ('Member #' + rec.id)); const who = esc(rec.name || ('Member #' + rec.id));
const c = rec.copy || {}; const c = rec.copy || {};
@@ -156,7 +200,7 @@ function render(rec) {
story + story +
(bullets ? '<ul>' + bullets + '</ul>' : '') + (bullets ? '<ul>' + bullets + '</ul>' : '') +
'<div class="cta"><p>' + esc(c.closing || 'Take a look and see what you think.') + '</p>' + '<div class="cta"><p>' + esc(c.closing || 'Take a look and see what you think.') + '</p>' +
'<a class="btn btn-primary" href="' + esc(link) + '">See how it works →</a>' + '<a class="btn btn-primary" href="' + esc(joinLink) + '">Join ' + who + ' &rarr;</a>' +
'<div class="qr" id="ppQr" data-link="' + esc(link) + '"></div><div class="micro" style="color:var(--muted)">or scan · ' + esc(link.replace(/^https:\/\//, '')) + '</div></div>' + '<div class="qr" id="ppQr" data-link="' + esc(link) + '"></div><div class="micro" style="color:var(--muted)">or scan · ' + esc(link.replace(/^https:\/\//, '')) + '</div></div>' +
'<div class="foot">Independent team resource shared by an individual member. Participation takes real effort and involves cryptocurrency risk, including risk of total loss. No income is guaranteed. ' + '<div class="foot">Independent team resource shared by an individual member. Participation takes real effort and involves cryptocurrency risk, including risk of total loss. No income is guaranteed. ' +
'<a href="/disclaimer" style="color:var(--teal)">Disclaimers</a> · <a href="/contract" style="color:var(--teal)">How the contract works</a></div>' + '<a href="/disclaimer" style="color:var(--teal)">Disclaimers</a> · <a href="/contract" style="color:var(--teal)">How the contract works</a></div>' +
@@ -166,4 +210,4 @@ function render(rec) {
'</body></html>'; '</body></html>';
} }
module.exports = { init, load, save, remove, list, slugify, generate, render, ANGLES, parseSections }; module.exports = { init, load, save, remove, list, slugify, slugFromTitle, pageLimit, PAGE_LIMIT, generate, render, ANGLES, parseSections };
+7 -1
View File
@@ -101,7 +101,13 @@ async function notifyEvent(evt) {
const chat = memberChat(evt.toId); const chat = memberChat(evt.toId);
if (!chat) return; if (!chat) return;
const kind = evt.kind === 'upline' ? `${evt.gen ? `Gen ${evt.gen} ` : ''}upgrade pass-up from #${evt.fromId}` : `referral reward from #${evt.fromId}'s entry`; const kind = evt.kind === 'upline' ? `${evt.gen ? `Gen ${evt.gen} ` : ''}upgrade pass-up from #${evt.fromId}` : `referral reward from #${evt.fromId}'s entry`;
await dm(chat, `💰 You just caught ${evt.pol.toFixed(2)} POL — ${kind}.\nVerify: https://polygonscan.com/tx/${evt.tx}\n\nYour pipeline: https://rmcircle.team/my/${evt.toId}`); // Upgrade pass-up: tell the catcher what the person below just unlocked.
// It makes the reason for the payment concrete, and it is a natural nudge
// toward their own next rung without asking for anything.
const upTools = evt.kind === 'upline' ? suiteTools.unlockText(0, evt.levelName, getConfig()) : '';
await dm(chat, `💰 You just caught ${evt.pol.toFixed(2)} POL — ${kind}.` +
(upTools ? `\n\n#${evt.fromId} reached ${evt.levelName}, which unlocked ${upTools} for them — and their climb is what put this on you.` : '') +
`\nVerify: https://polygonscan.com/tx/${evt.tx}\n\nYour pipeline: https://rmcircle.team/my/${evt.toId}`);
} else if (evt.type === 'upgraded') { } else if (evt.type === 'upgraded') {
// Congratulate the member who upgraded, and tell them what it opened up. // Congratulate the member who upgraded, and tell them what it opened up.
const chat = memberChat(evt.id); const chat = memberChat(evt.id);