Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94910275c0 | |||
| f6490c9444 |
+2
-2
@@ -6,7 +6,7 @@
|
||||
<title>Admin | 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=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
<style>
|
||||
.adm-table th,.adm-table td{padding:8px 10px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line);font-size:13.5px}
|
||||
.adm-table th{color:var(--muted);font-size:11.5px;text-transform:uppercase;letter-spacing:.08em}
|
||||
@@ -306,6 +306,6 @@
|
||||
</div>
|
||||
|
||||
<script src="/assets/common.js?v=20260910c"></script>
|
||||
<script src="/assets/admin.js?v=20260910b"></script>
|
||||
<script src="/assets/admin.js?v=20260910c"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -276,11 +276,11 @@
|
||||
const q = ($('memFilter').value || '').trim().toLowerCase();
|
||||
const list = allMembers.filter(a => !q || [a.email, a.username, a.memberId, a.sponsorRef, a.address, a.code].join(' ').toLowerCase().includes(q));
|
||||
$('memSub').textContent = list.length + ' of ' + allMembers.length;
|
||||
$('memTable').innerHTML = '<tr><th>Email</th><th>Username</th><th>Member #</th><th>Wallet</th><th>Sponsor</th><th>Via</th><th>Code</th><th>Joined</th><th></th></tr>'
|
||||
$('memTable').innerHTML = '<tr><th>Email</th><th>Username</th><th>Member #</th><th>Wallet</th><th>Sponsor</th><th>Positions</th><th>Via</th><th>Code</th><th>Joined</th><th></th></tr>'
|
||||
+ list.map(a => '<tr><td>' + esc(a.email) + '</td><td>' + (a.username ? '@' + esc(a.username) : '<span class="muted">none</span>') + '</td>'
|
||||
+ '<td>' + (a.memberId ? '#' + a.memberId : '<span class="muted">free</span>') + '</td>'
|
||||
+ '<td class="mono small">' + (a.address ? esc(a.address.slice(0, 8) + '…' + a.address.slice(-6)) : '<span class="muted">none</span>') + '</td>'
|
||||
+ '<td>' + esc(a.sponsorRef || '') + '</td><td class="small muted">' + esc(a.joinedVia || '') + '</td><td class="mono small">' + esc(a.code || '') + '</td>'
|
||||
+ '<td>' + esc(a.sponsorRef || '') + '</td><td class="small" title="linked Qualified Start positions' + (a.positionIds && a.positionIds.length ? ': #' + a.positionIds.join(', #') : '') + '">' + (a.positions ? a.positions : '<span class="muted">0</span>') + '</td><td class="small muted">' + esc(a.joinedVia || '') + '</td><td class="mono small">' + esc(a.code || '') + '</td>'
|
||||
+ '<td class="small muted">' + when(a.created) + '</td>'
|
||||
+ '<td class="act"><button class="btn small sec" data-spon="' + esc(a.email) + '" data-cur="' + esc(a.sponsorRef || '') + '">Sponsor</button></td></tr>').join('');
|
||||
}
|
||||
|
||||
+36
-1
@@ -81,6 +81,15 @@ window.IAPPromo = (function () {
|
||||
return a;
|
||||
}
|
||||
|
||||
// hook videos: one per angle; the matched link continues the same hook on the join page
|
||||
const VID_BASE = 'https://coolify-saasytop.nyc3.digitaloceanspaces.com/promo/';
|
||||
const VIDEOS = [
|
||||
{ angle: 'instant', title: 'Paid before the page reloads', hook: 'What if your commission landed before the thank-you page finished loading?', caption: 'What if your commission landed before the thank-you page finished loading? On InstantAdPay a smart contract splits every ad package the second it sells, straight to wallets, on a public ledger. Free to join by email: {{LINK:instant}}' },
|
||||
{ angle: 'adspend', title: 'You were buying traffic anyway', hook: 'Every ad dollar you have ever spent went one direction. Out.', caption: 'Every ad dollar you have ever spent went one direction. Out. Here the ad spend in your line pays you back in the same transaction. Seven formats, packages from $5, free to join: {{LINK:adspend}}' },
|
||||
{ angle: 'free', title: 'Watch first, spend never', hook: 'You can run your first ad campaign here for exactly zero dollars.', caption: 'Run your first ad campaign for exactly zero dollars. Join free by email, earn credits by viewing ads, launch a real campaign, and watch real payouts land on a public ledger before you spend a cent: {{LINK:free}}' },
|
||||
{ angle: 'ledger', title: 'No back office. No payday.', hook: 'Your last affiliate program paid you on the fifteenth. If it paid you.', caption: 'Your last affiliate program paid you on the fifteenth. If it paid you. Here the payroll is the blockchain: every payout is a public transaction, nothing is held, nobody can change the split. Open the ledger, then join free: {{LINK:ledger}}' },
|
||||
{ angle: 'two', title: 'Two buyers open level two', hook: 'Two buyers. Then five. That is the whole ladder.', caption: 'Two buyers. Then five. That is the whole ladder. Level 1 pays from your first buyer, two qualifying buyers open level 2, five open level 3, all in the same transaction, straight to your wallet. Free to join: {{LINK:two}}' }
|
||||
];
|
||||
function fill(link, me) {
|
||||
const token = (me && (me.username || me.refCode || me.memberId)) || '';
|
||||
// invite strip
|
||||
@@ -128,6 +137,32 @@ window.IAPPromo = (function () {
|
||||
sw.appendChild(block(text, '<span class="pb-net">' + esc(s.tier) + '</span> <b>' + esc(s.subject) + '</b>'));
|
||||
}
|
||||
}
|
||||
// hook videos
|
||||
const vw = $('promoVideos');
|
||||
if (vw && vw.dataset.filled !== link) {
|
||||
vw.dataset.filled = link; vw.innerHTML = '';
|
||||
for (const v of VIDEOS) {
|
||||
const mlink = angleLink(link, v.angle);
|
||||
const card = document.createElement('div'); card.className = 'pv';
|
||||
card.innerHTML = '<div class="pv-head"><span class="pb-net">' + esc(v.angle) + ' angle</span> <b>' + esc(v.title) + '</b><div class="muted small">' + esc(v.hook) + '</div></div>'
|
||||
+ '<video src="' + VID_BASE + v.angle + '.mp4" poster="' + VID_BASE + v.angle + '.jpg" preload="none" controls playsinline style="width:100%;max-width:640px;border-radius:12px;background:#000;margin:10px 0"></video>'
|
||||
+ '<p class="small"><span class="muted">Matched link:</span> <span class="mono" style="overflow-wrap:anywhere">' + esc(mlink) + '</span></p>';
|
||||
const row = document.createElement('p'); row.className = 'pb-actions';
|
||||
row.appendChild(copyBtn(mlink, 'Copy matched link'));
|
||||
row.appendChild(linkBtn(VID_BASE + v.angle + '.mp4', 'Download 16:9'));
|
||||
row.appendChild(linkBtn(VID_BASE + v.angle + '-portrait.mp4', 'Download 9:16'));
|
||||
card.appendChild(row);
|
||||
const cap = fillLink(v.caption, link);
|
||||
const extras = [
|
||||
linkBtn('https://twitter.com/intent/tweet?text=' + encodeURIComponent(cap), 'Post on X'),
|
||||
linkBtn('https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent(mlink), 'Share on Facebook'),
|
||||
linkBtn('https://t.me/share/url?url=' + encodeURIComponent(mlink) + '&text=' + encodeURIComponent(cap.replace(/https?:\/\/\S+$/, '').trim()), 'Telegram'),
|
||||
linkBtn('https://wa.me/?text=' + encodeURIComponent(cap), 'WhatsApp')
|
||||
];
|
||||
card.appendChild(block(cap, '<span class="lab">Caption</span>', extras));
|
||||
vw.appendChild(card);
|
||||
}
|
||||
}
|
||||
// objections
|
||||
const ob = $('promoObjections');
|
||||
if (ob && ob.dataset.filled !== link) {
|
||||
@@ -146,5 +181,5 @@ window.IAPPromo = (function () {
|
||||
}
|
||||
}
|
||||
|
||||
return { fill, POSTS, TEXTS, SWIPES, OBJECTIONS };
|
||||
return { fill, POSTS, TEXTS, SWIPES, OBJECTIONS, VIDEOS };
|
||||
})();
|
||||
|
||||
@@ -218,6 +218,8 @@ tr:last-child td{border-bottom:0}
|
||||
.ad-strip a{color:var(--ink)}
|
||||
.ad-strip b{color:var(--mint)}
|
||||
.ad-foot{text-align:center}
|
||||
.pv{border:1px solid var(--line);border-radius:14px;padding:16px 18px;margin:0 0 16px;background:rgba(4,8,7,.35)}
|
||||
.pv-head b{display:block;font-size:18px;margin:4px 0 2px}
|
||||
.done-big{display:flex;flex-direction:column;align-items:center;gap:6px;padding:26px 16px;text-align:center}
|
||||
.done-big .tick{width:84px;height:84px;border-radius:50%;display:grid;place-items:center;font-size:48px;font-weight:800;color:var(--mint-ink);background:var(--mint);box-shadow:0 0 0 10px rgba(67,232,195,.15),0 10px 30px rgba(67,232,195,.35)}
|
||||
.done-big b{font-family:var(--disp);font-size:22px;margin-top:8px}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<meta name="theme-color" content="#043b2f">
|
||||
<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=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<title>Disclaimer | InstantAdPay</title>
|
||||
<link rel="icon" type="image/png" href="/logo-icon.png">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
</head>
|
||||
<body>
|
||||
<div id="nav"></div>
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
<meta name="theme-color" content="#043b2f">
|
||||
<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=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<title>You're invited | 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=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
<style>
|
||||
.jn-nav{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:16px 0}
|
||||
.jn-hero{padding:14px 0 4px;text-align:left}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
<meta name="theme-color" content="#043b2f">
|
||||
<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=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
|
||||
+5
-3
@@ -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=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
</head>
|
||||
<body class="bo-body">
|
||||
|
||||
@@ -373,6 +373,7 @@
|
||||
<span class="small muted" id="qsHint"></span></p>
|
||||
<div id="qsList" class="small"></div>
|
||||
<p class="muted small" style="margin:12px 0 0">Your own money, your own wallets, a faster start. It is not an income promise: qualification only pays on future purchases in your line, and every purchase here is a real ad package you can spend.</p>
|
||||
<p class="small" style="border:1px solid var(--line-strong);border-radius:10px;padding:8px 12px;margin:10px 0 0"><b>Positions are for qualifying.</b> Up to five per account, each registered directly under your main member. Once you are qualified, buy from your main wallet: that keeps your own sponsor paid in full, the same way your people's purchases pay you.</p>
|
||||
</div>
|
||||
<div class="card" id="qsCalcCard">
|
||||
<h3>Qualified Start calculator</h3>
|
||||
@@ -684,8 +685,9 @@
|
||||
<div class="card promo-sec" id="promo-videos" hidden>
|
||||
<h3>Hook videos</h3>
|
||||
<p class="muted small">Short videos, one per angle, each with a matched invite link that lands on a
|
||||
page continuing the same hook. In production now. When they arrive, this tab holds the previews,
|
||||
your matched links, and paste-ready captions.</p>
|
||||
page continuing the same hook. Post the video with the matched link, or download it and use it anywhere.
|
||||
Every caption below already carries your link.</p>
|
||||
<div id="promoVideos"></div>
|
||||
</div>
|
||||
<div class="card ad-foot" id="adSlotPane-promo" hidden></div>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
<meta name="theme-color" content="#043b2f">
|
||||
<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=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
<style>
|
||||
.pl-rule{border-left:4px solid #f2c94c;background:rgba(242,201,76,.08);padding:16px 20px;border-radius:0 12px 12px 0;margin:0 0 28px}
|
||||
.pl-rule b{font-family:var(--disp);font-size:17px;display:block;margin-bottom:6px}
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<title>Privacy Policy | InstantAdPay</title>
|
||||
<link rel="icon" type="image/png" href="/logo-icon.png">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
</head>
|
||||
<body>
|
||||
<div id="nav"></div>
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<title>Shorts | 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=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
<style>
|
||||
html,body{height:100%;margin:0;overflow:hidden;background:#000}
|
||||
.sh{position:fixed;inset:0;display:flex;flex-direction:column;background:#000;color:var(--ink,#e8fff7)}
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<title>Terms of Service | InstantAdPay</title>
|
||||
<link rel="icon" type="image/png" href="/logo-icon.png">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
</head>
|
||||
<body>
|
||||
<div id="nav"></div>
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<title>Transaction | 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=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
</head>
|
||||
<body>
|
||||
<div id="nav"></div>
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Viewing ad — InstantAdPay</title>
|
||||
<link rel="icon" type="image/png" href="/logo-icon.png">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
<style>
|
||||
html,body{height:100%;margin:0;overflow:hidden}
|
||||
.vw{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg,#04110c);color:var(--ink,#e8fff7)}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<!--OG-->
|
||||
<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=20260910g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260910h">
|
||||
</head>
|
||||
<body>
|
||||
<div id="nav"></div>
|
||||
|
||||
@@ -717,6 +717,19 @@ const server = http.createServer(async (req, res) => {
|
||||
// Qualified Start: a second (third…) wallet on the same account. It becomes
|
||||
// its own on-chain member under this member's id when it buys; the session
|
||||
// stays on the main wallet.
|
||||
// policy (Marty, 2026-09-10): positions exist to qualify, so at most five per account, and a
|
||||
// wallet that is already registered on-chain under anything other than this account's main
|
||||
// member is refused (a position-under-position chain would recapture 70% of a self-buy)
|
||||
const MAX_POS = 5;
|
||||
const have = await accounts.positions(s.email);
|
||||
if (!have.find(p => p.address === r.address.toLowerCase()) && have.length >= MAX_POS)
|
||||
return json(res, 400, { error: 'You can link up to ' + MAX_POS + ' positions, which is everything Qualified Start needs. Once qualified, buy from your main wallet so your sponsor is paid in full.' });
|
||||
if (memberId) {
|
||||
const mainId = await auth.refreshMemberId(s);
|
||||
let mm = null; try { mm = await chain.member(memberId); } catch (e) {}
|
||||
if (mm && mainId && mm.sponsorId !== mainId)
|
||||
return json(res, 400, { error: 'That wallet is already registered on-chain under ' + (mm.sponsorId ? 'member #' + mm.sponsorId : 'no sponsor') + ', not under your main member #' + mainId + '. Only positions registered directly under you can be linked.' });
|
||||
}
|
||||
const pr = await accounts.addPosition(s.email, r.address);
|
||||
if (pr.error) return json(res, 400, pr);
|
||||
if (memberId) await accounts.setPositionMember(r.address, memberId);
|
||||
@@ -1641,7 +1654,9 @@ const server = http.createServer(async (req, res) => {
|
||||
}
|
||||
if (p === '/api/admin/members' && req.method === 'GET') {
|
||||
if (!isAdmin(req)) return json(res, 401, { error: 'auth' });
|
||||
return json(res, 200, { members: await accounts.listAll(500) });
|
||||
const members = await accounts.listAll(500);
|
||||
for (const m of members) { try { const ps = await accounts.positions(m.email); m.positions = ps.length; m.positionIds = ps.map(p => p.memberId).filter(Boolean); } catch (e) { m.positions = 0; } }
|
||||
return json(res, 200, { members });
|
||||
}
|
||||
if (p === '/api/admin/members' && req.method === 'PATCH') {
|
||||
if (!isAdmin(req)) return json(res, 401, { error: 'auth' });
|
||||
|
||||
Reference in New Issue
Block a user