Wall page: mint QR, achievement badge, admin-ad backfill, join gated on views
- QR is mint-green (still high-contrast for reliable scanning). - The wall shows the owner's achievement badge next to their avatar. - Positions 2 & 3 fill with upline line-banners, then ADMIN ADS when there's no upline (configurable via data/admin-wall-ads.json; sensible default) so the wall is never sparse. - "Join free through this wall" is disabled until the visitor has viewed every ad on the wall, with a "X/Y viewed" prompt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,12 @@ async function uplineSlides(email, depth = 3) {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
// admin fallback ads for wall positions 2 & 3 when a member has no upline.
|
||||
// Configurable by dropping data/admin-wall-ads.json ([{name,targetUrl,bannerUrl}]).
|
||||
function getAdminWallAds() {
|
||||
try { const j = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'admin-wall-ads.json'), 'utf8')); if (Array.isArray(j) && j.length) return j; } catch (e) {}
|
||||
return [{ name: 'InstantAdPay', targetUrl: 'https://instantadpay.com/', bannerUrl: null }];
|
||||
}
|
||||
const chatHits = new Map();
|
||||
function chatLimited(ip) {
|
||||
const now = Date.now(), rec = chatHits.get(ip);
|
||||
@@ -870,7 +876,7 @@ const server = http.createServer(async (req, res) => {
|
||||
if (!QR || !data) { res.writeHead(404, baseHeaders()); return res.end(); }
|
||||
try {
|
||||
const svg = await QR.toString(data, { type: 'svg', margin: 1, width: 240,
|
||||
color: { dark: '#0b1512', light: '#eef7f3' } });
|
||||
color: { dark: '#0e7d5f', light: '#f2fbf8' } }); // mint-green modules, still high-contrast for reliable scanning
|
||||
res.writeHead(200, baseHeaders({ 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=3600' }));
|
||||
return res.end(svg);
|
||||
} catch (e) { res.writeHead(500, baseHeaders()); return res.end(); }
|
||||
@@ -944,14 +950,31 @@ const server = http.createServer(async (req, res) => {
|
||||
let a = await accounts.byUsername(tok);
|
||||
if (!a) a = await accounts.byCode(tok);
|
||||
if (!a) return json(res, 404, { error: 'No wall under that name.' });
|
||||
const ladder = [a, ...await uplineSlides(a.email, 2)].slice(0, 3)
|
||||
const ladder = [a, ...await uplineSlides(a.email, 2)]
|
||||
.map(x => ({ name: x.username ? '@' + x.username : x.memberId ? 'member #' + x.memberId : 'a member',
|
||||
bannerUrl: x.lineBannerUrl || null, targetUrl: x.lineTargetUrl || null }));
|
||||
// fill positions 2 & 3 with admin ads when there isn't enough upline, so the wall is never sparse
|
||||
const adAds = getAdminWallAds();
|
||||
let ai = 0;
|
||||
while (ladder.length < 3 && adAds.length) {
|
||||
const ad = adAds[ai++ % adAds.length];
|
||||
ladder.push({ name: ad.name || 'InstantAdPay', bannerUrl: ad.bannerUrl || null, targetUrl: ad.targetUrl || 'https://instantadpay.com/', admin: true });
|
||||
}
|
||||
const finalLadder = ladder.slice(0, 3);
|
||||
// the wall owner's achievement badge (their highest reached tier)
|
||||
let badge = null;
|
||||
if (a.memberId) {
|
||||
try {
|
||||
const bc = (await chain.member(a.memberId)).buyerCount || 0;
|
||||
const t = bc >= 5 ? ['nexus', 'Nexus'] : bc >= 2 ? ['circuit', 'Circuit'] : bc >= 1 ? ['surge', 'Surge'] : ['spark', 'Spark'];
|
||||
badge = { img: '/badges/badge-' + t[0] + '.jpg', label: t[1] };
|
||||
} catch (e) {}
|
||||
}
|
||||
const joinPath = '/join/' + (a.username || a.code);
|
||||
let socials = null; try { socials = a.socials ? JSON.parse(a.socials) : null; } catch (e) {}
|
||||
return json(res, 200, { name: a.username ? '@' + a.username : 'member #' + (a.memberId || 0),
|
||||
avatarUrl: a.avatarUrl || null, bio: a.bio || null, socials,
|
||||
joinUrl: joinPath, qrUrl: '/api/qr?d=' + encodeURIComponent('https://instantadpay.com' + joinPath), ladder });
|
||||
avatarUrl: a.avatarUrl || null, bio: a.bio || null, socials, badge,
|
||||
joinUrl: joinPath, qrUrl: '/api/qr?d=' + encodeURIComponent('https://instantadpay.com' + joinPath), ladder: finalLadder });
|
||||
}
|
||||
// -- watch-to-earn video ads: serve one, then reward a server-clock-verified watch
|
||||
if (p === '/api/my/videos' && req.method === 'GET') {
|
||||
|
||||
Reference in New Issue
Block a user