Referral rewards: pay a hunter for bringing someone who actually hunts

PolHunter retains well and recruits badly. On 2026-09-22 it had its best day,
105 finds from 37 hunters, and had sent three people to InstantAdPay in its
whole life, all three on one member's link. Earning and sharing were separate
actions and only one of them paid.

Bringing someone is now the best-paid thing on the board, and the card sits
first in the mission list rather than on a promo page nobody opens. A flat 3
POL the first time someone they brought finds a code, then 20% on top of that
person's finds for their first 30 days, paid from the pool and never taken out
of the newcomer's drip.

Who counts as "brought by me" is the sponsor they actually joined
InstantAdPay under, handed over in the sign-in payload, so the bounty and the
commission always land on the same person. The bounty only pays for a member
who is genuinely new, once per person, and referral drips have their own daily
ceiling so a good referral day can never starve the finds.

Also: findsToday defaulted its day argument to undefined and silently returned
zero for every caller that omitted it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-23 04:34:22 -05:00
parent bed75d1b0f
commit 1c9e99f91e
7 changed files with 231 additions and 11 deletions
+27
View File
@@ -46,6 +46,26 @@
: '<div style="margin-top:14px"><button class="btn" data-start="' + esc(m.id) + '">Start mission</button></div>')
+ '</div>';
}
// The best-paid thing on the board, and the reason it sits first: sharing used to live on a page
// nobody opened, so nobody shared (Marty, 2026-09-23).
function referralCard() {
const rf = board.referral; if (!rf || !rf.on) return '';
const link = (board.share && board.share.link) || '';
const today = rf.broughtToday, total = rf.brought;
const people = (rf.people || []).slice(0, 5).map(p =>
'<div class="ref-row"><span>' + esc(p.name) + '</span><span class="ref-state">' + esc(p.state) + '</span></div>').join('');
return '<div class="card ref-card">'
+ '<span class="tag gold">Best paid</span>'
+ '<h3>🤝 Bring a hunter</h3>'
+ '<p>Send your link. When someone new opens PolHunter and finds their first code, you get <b>' + rf.bountyPol + ' POL</b>. '
+ 'After that you earn <b>' + rf.matchPct + '%</b> on top of everything they find for their first ' + rf.matchDays + ' days. '
+ 'It comes out of our pool, never theirs.</p>'
+ '<div class="codebox"><input id="refLink" readonly value="' + esc(link) + '"><button class="btn pol" id="refCopy">Copy</button></div>'
+ '<p style="margin-top:10px"><span class="range">' + today + ' brought today</span> '
+ '<span style="color:var(--dim);font-size:12px">· ' + total + ' all time · ' + rf.earnedPol + ' POL earned</span></p>'
+ (people ? '<div class="ref-list">' + people + '</div>' : '<p style="color:var(--dim);font-size:12px;margin-top:10px">Nobody yet. One message is all it takes.</p>')
+ '</div>';
}
const IAP = 'https://instantadpay.com';
function until(ms) { const s = Math.max(0, Math.round((ms - Date.now()) / 60000)); const h = Math.floor(s / 60), m = s % 60; return h ? h + 'h ' + m + 'm' : m + 'm'; }
// no wallet on the InstantAdPay account yet: the three steps, in place of the missions
@@ -83,7 +103,14 @@
const pool = board.pool || {};
if (!board.me.wallet) $('missions').innerHTML = onboarding();
else $('missions').innerHTML = (pool.spent ? '<div class="notice" style="grid-column:1/-1"><b>Today\u2019s POL pool is spent.</b> No more claims today. Claims reopen at midnight Central' + (pool.resetsAt ? ', in ' + until(pool.resetsAt) : '') + '.</div>' : '')
+ referralCard()
+ (board.missions.length ? board.missions.map(card).join('') : '<div class="card"><p>No missions are open right now. Check back soon.</p></div>');
const rc = $('refCopy');
if (rc) rc.addEventListener('click', async () => {
const v = $('refLink').value;
try { await navigator.clipboard.writeText(v); } catch (e) { $('refLink').select(); document.execCommand('copy'); }
rc.textContent = 'Copied'; setTimeout(() => { rc.textContent = 'Copy'; }, 1600);
});
// standing: badges and rank
const rk = board.rank || {}; const line = [];
if (rk.week) line.push('This week <b>#' + rk.week.rank + '</b> of ' + rk.week.of); if (rk.all) line.push('All time <b>#' + rk.all.rank + '</b> of ' + rk.all.of);