Member identity, promo tools, back-office color, seeded inventory
Real people, not numbers: usernames (unique, profile pane to set them), shown across the ledger, activity, rosters, and the sidebar chip; vanity invite links (/join/<username>) with late chain binding intact. Promo tools pane ships Branded Voice share posts and an email swipe personalized with each member's link. The earn viewer excludes a member's own campaigns, so nobody earns from their own spend. Back office gains the cyan/violet/amber accent family over the green base. Three house campaigns seeded so every surface shows live inventory. Assets v=20260905i. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+16
-11
@@ -52,10 +52,11 @@ window.IAP = (function () {
|
||||
const el = $('navWallet');
|
||||
if (!el) return;
|
||||
if (me.signedIn) {
|
||||
// email-only members have no wallet address yet
|
||||
const who = me.address
|
||||
? '<span class="mono">' + me.address.slice(0, 6) + '…' + me.address.slice(-4) + '</span>'
|
||||
: (me.email ? String(me.email).replace(/[&<>]/g, '') : 'signed in');
|
||||
// identity order: username, then email, then wallet
|
||||
const who = me.username
|
||||
? '<b>@' + String(me.username).replace(/[&<>]/g, '') + '</b>'
|
||||
: (me.email ? String(me.email).replace(/[&<>]/g, '')
|
||||
: (me.address ? '<span class="mono">' + me.address.slice(0, 6) + '…' + me.address.slice(-4) + '</span>' : 'signed in'));
|
||||
el.innerHTML = (me.memberId ? '<span class="badge">member #' + me.memberId + '</span> ' : '') + who;
|
||||
} else {
|
||||
el.innerHTML = '<a href="/my">Sign in</a>';
|
||||
@@ -65,17 +66,21 @@ window.IAP = (function () {
|
||||
}
|
||||
function describeEvent(ev, c) {
|
||||
const pol = w => fmtPol(w) + ' POL';
|
||||
// real people, not numbers: use usernames when the site knows them
|
||||
const nm = id => (ev.names && ev.names[id])
|
||||
? String(ev.names[id]).replace(/[&<>]/g, '')
|
||||
: 'member #' + id;
|
||||
switch (ev.type) {
|
||||
case 'Purchase': return '🧾 member #' + ev.buyerId + ' bought package #' + ev.productId
|
||||
case 'Purchase': return '🧾 ' + nm(ev.buyerId) + ' bought package #' + ev.productId
|
||||
+ ' (' + fmtUsd(ev.priceCents) + ') for ' + pol(ev.paidWei) + ' → +' + ev.creditAmount.toLocaleString() + ' credits';
|
||||
case 'TierPaid': return '💸 level ' + ev.tier + ' payout → member #' + ev.recipientId + ': ' + pol(ev.amountWei)
|
||||
case 'TierPaid': return '💸 level ' + ev.tier + ' payout → ' + nm(ev.recipientId) + ': ' + pol(ev.amountWei)
|
||||
+ (ev.hops ? ' (passed up ' + ev.hops + ')' : '');
|
||||
case 'PassedUp': return '↷ level ' + ev.tier + ' passed over #' + ev.skippedId + ' (' + ev.reason + ')';
|
||||
case 'PassedUp': return '↷ level ' + ev.tier + ' passed over ' + nm(ev.skippedId) + ' (' + ev.reason + ')';
|
||||
case 'AdminPaid': return '🏛 platform fee settled: ' + pol(ev.amountWei);
|
||||
case 'BuyerCounted': return '⭐ member #' + ev.sponsorId + ' now has ' + ev.newCount + ' qualifying buyer(s)';
|
||||
case 'MemberActivated': return '👤 member #' + ev.id + ' activated a payout wallet';
|
||||
case 'AwardPaid': return '🎁 award: ' + pol(ev.amountWei) + ' → member #' + ev.toId;
|
||||
case 'CreditsConsumed': return '📣 member #' + ev.memberId + ' ran ads: −' + ev.amount.toLocaleString() + ' credits';
|
||||
case 'BuyerCounted': return '⭐ ' + nm(ev.sponsorId) + ' now has ' + ev.newCount + ' qualifying buyer(s)';
|
||||
case 'MemberActivated': return '👤 ' + nm(ev.id) + ' activated a payout wallet';
|
||||
case 'AwardPaid': return '🎁 award: ' + pol(ev.amountWei) + ' → ' + nm(ev.toId);
|
||||
case 'CreditsConsumed': return '📣 ' + nm(ev.memberId) + ' ran ads: −' + ev.amount.toLocaleString() + ' credits';
|
||||
case 'PriceCached': return '🔮 oracle price refreshed';
|
||||
case 'FallbackPriceUsed': return '🔮 cached price bridged an oracle gap';
|
||||
default: return '· ' + ev.type;
|
||||
|
||||
+57
-8
@@ -45,7 +45,7 @@
|
||||
if (t) t.remove();
|
||||
t = document.createElement('table');
|
||||
t.className = 'roster';
|
||||
t.innerHTML = d.referrals.map(r => '<tr><td>' + r.email + '</td>'
|
||||
t.innerHTML = d.referrals.map(r => '<tr><td>' + String(r.name || r.email || '').replace(/[&<>]/g, '') + '</td>'
|
||||
+ '<td>' + new Date(r.joined).toLocaleDateString() + '</td>'
|
||||
+ '<td><span class="badge' + (r.status === 'joined free' ? ' amber' : '') + '">' + r.status + '</span></td></tr>').join('');
|
||||
wrap.appendChild(t);
|
||||
@@ -72,8 +72,9 @@
|
||||
rows.slice(0, 6).forEach(r => ov.appendChild(r));
|
||||
}
|
||||
} catch (e) {}
|
||||
// ready-to-send share message
|
||||
const link = location.origin + '/join/' + (d.refCode || d.memberId || '');
|
||||
// ready-to-send share message + promo tools, personalized
|
||||
const link = location.origin + '/join/' + (d.username || d.refCode || d.memberId || '');
|
||||
fillPromo(link);
|
||||
if (d.refCode || d.memberId) {
|
||||
const pitch = 'I found an advertising site that pays referrals instantly to your own wallet. '
|
||||
+ 'No withdrawals, no waiting, and every payment is public on a blockchain ledger you can check yourself. '
|
||||
@@ -90,9 +91,9 @@
|
||||
}
|
||||
|
||||
// ── back-office menu: hash-routed panes ───────────────
|
||||
const PANES = ['overview', 'line', 'buy', 'campaigns', 'earn', 'earnings', 'wallet'];
|
||||
const PANES = ['overview', 'line', 'buy', 'campaigns', 'earn', 'earnings', 'promo', 'wallet', 'profile'];
|
||||
const TITLES = { overview: 'Overview', line: 'My line', buy: 'Buy packages', campaigns: 'Campaigns',
|
||||
earn: 'Earn credits', earnings: 'Earnings', wallet: 'Wallet & account' };
|
||||
earn: 'Earn credits', earnings: 'Earnings', promo: 'Promo tools', wallet: 'Wallet & account', profile: 'Profile' };
|
||||
function setPane(name) {
|
||||
if (!PANES.includes(name)) name = 'overview';
|
||||
for (const p of PANES) {
|
||||
@@ -148,9 +149,15 @@
|
||||
$('campaignCard').hidden = false;
|
||||
loadCampaigns();
|
||||
|
||||
// the share link works from day one; codes resolve to your chain id later
|
||||
if (me.refCode || me.memberId) {
|
||||
$('inviteLine').textContent = location.origin + '/join/' + (me.refCode || me.memberId);
|
||||
// profile pane state
|
||||
$('pfCurrent').textContent = me.username ? 'Current username: @' + me.username : 'No username yet. Members see you as a number until you pick one.';
|
||||
if (!$('pfUsername').value) $('pfUsername').value = me.username || '';
|
||||
$('pfDetails').innerHTML = 'Email: ' + (me.email || 'none') + '<br>Wallet: '
|
||||
+ (me.address ? '<span class="mono">' + me.address.slice(0, 10) + '…' + me.address.slice(-6) + '</span>' : 'not linked yet')
|
||||
+ '<br>On-chain member: ' + (me.memberId ? '#' + me.memberId : 'not yet');
|
||||
// the share link works from day one; usernames make it a vanity link
|
||||
if (me.username || me.refCode || me.memberId) {
|
||||
$('inviteLine').textContent = location.origin + '/join/' + (me.username || me.refCode || me.memberId);
|
||||
$('copyInvite').hidden = false;
|
||||
} else {
|
||||
$('inviteLine').textContent = 'Sign in with your email to get your link.';
|
||||
@@ -233,6 +240,48 @@
|
||||
// defers the busy() lookup to click time (busy is declared below)
|
||||
function busy2(btn, fn) { return (...a) => busy(btn, fn)(...a); }
|
||||
|
||||
// ── promo tools: Branded Voice copy, personalized with the member link ──
|
||||
const PROMO_POSTS = [
|
||||
'A membership site where money is handled by code, not people. Every purchase splits instantly to sponsor wallets on the Polygon blockchain. Nothing to withdraw. The money just lands in your wallet. Plus you earn ad credits for viewing ads while you\'re there. {{LINK}}',
|
||||
'No withdrawal button. Think about that. A smart contract on Polygon splits every payment the second it hits. 50% to the sponsor. 20% to the next level. 10% to the next. Lands straight in your own wallet. No button to push. No waiting. Just money where it belongs. See how it works: {{LINK}}',
|
||||
'Every payment is public on the blockchain. You can watch the ledger move in real time. Every split, every wallet, every transaction. Nothing hidden. Nothing you have to take on faith. That\'s the difference between a platform that asks for trust and one where trust isn\'t needed. See for yourself: {{LINK}}'
|
||||
];
|
||||
const PROMO_SWIPE = {
|
||||
subject: 'Your Wallet Gets Paid Instantly..',
|
||||
body: 'You know the usual drill. Someone buys on your link, you wait for a payout. Maybe days. Maybe an approval hold. Maybe a "your account is under review."\n\nInstantAdPay doesn\'t work like that.\n\nA smart contract on the Polygon blockchain handles every purchase the second it happens. 50% to the sponsor. 20% to the next level. 10% to the one after that. 20% to the platform. Each split lands directly in your own wallet. No withdrawal button. No "request payout." No approval queue.\n\nThe money just shows up.\n\nYou can watch every transaction on the public ledger. Real time. Anyone can verify it.\n\nFree to join. Packages from $5 to $250. No income promises. It\'s advertising, not investing.\n\n{{LINK}}'
|
||||
};
|
||||
function promoBlock(text) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'promo-block';
|
||||
div.textContent = text;
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'btn small sec';
|
||||
btn.textContent = 'Copy';
|
||||
btn.addEventListener('click', async () => {
|
||||
try { await navigator.clipboard.writeText(text); IAP.status('Copied. Paste it anywhere.', 'ok'); }
|
||||
catch (e) { IAP.status('Copy failed. Select the text instead.', 'bad'); }
|
||||
});
|
||||
div.appendChild(btn);
|
||||
return div;
|
||||
}
|
||||
function fillPromo(link) {
|
||||
const posts = $('promoPosts');
|
||||
if (!posts || posts.dataset.filled === link) return;
|
||||
posts.dataset.filled = link;
|
||||
posts.innerHTML = '';
|
||||
for (const p of PROMO_POSTS) posts.appendChild(promoBlock(p.replace('{{LINK}}', link)));
|
||||
const sw = $('promoSwipeWrap');
|
||||
sw.innerHTML = '';
|
||||
sw.appendChild(promoBlock('Subject: ' + PROMO_SWIPE.subject + '\n\n' + PROMO_SWIPE.body.replace('{{LINK}}', link)));
|
||||
}
|
||||
|
||||
// ── profile ── (busy2 defers the busy lookup past its TDZ)
|
||||
$('pfSaveBtn').addEventListener('click', busy2($('pfSaveBtn'), async () => {
|
||||
const r = await api('/api/my/profile', { username: $('pfUsername').value });
|
||||
IAP.status('You are @' + r.account.username + ' now.', 'ok');
|
||||
await render();
|
||||
}));
|
||||
|
||||
// ── in-dashboard package buying ──
|
||||
const PKG = { 1: 'Micro', 2: 'Activation', 3: 'Builder', 4: 'Growth', 5: 'Leader' };
|
||||
async function loadBuyTiles() {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
--ink:#eef7f3; --muted:#8ba69c;
|
||||
--mint:#43e8c3; --mint-hi:#8ffbe3; --mint-ink:#03211a;
|
||||
--mint-soft:rgba(67,232,195,.09); --bad:#ff8f7d;
|
||||
--cyan:#54ccff; --violet:#9d7dff; --amber:#ffb238;
|
||||
--mono:"Consolas","JetBrains Mono",monospace;
|
||||
--disp:"Sora","Segoe UI",system-ui,sans-serif;
|
||||
--radius:18px;
|
||||
@@ -257,6 +258,25 @@ input:focus,select:focus{border-color:var(--mint)}
|
||||
#boBurger{display:block}
|
||||
.bo-content{padding:18px}
|
||||
}
|
||||
/* back-office accent family: green leads, cyan/violet/amber season the cards */
|
||||
.bo .stats .stat:nth-child(2) .n{color:var(--cyan)}
|
||||
.bo .stats .stat:nth-child(2)::before{background:linear-gradient(90deg,transparent,var(--cyan),transparent)}
|
||||
.bo .stats .stat:nth-child(3) .n{color:var(--violet)}
|
||||
.bo .stats .stat:nth-child(3)::before{background:linear-gradient(90deg,transparent,var(--violet),transparent)}
|
||||
.bo .stats .stat:nth-child(4) .n{color:var(--amber)}
|
||||
.bo .stats .stat:nth-child(4)::before{background:linear-gradient(90deg,transparent,var(--amber),transparent)}
|
||||
.bo .card h3::before{content:"";display:inline-block;width:9px;height:9px;border-radius:2.5px;
|
||||
background:var(--mint);margin-right:10px;transform:rotate(45deg);vertical-align:1px}
|
||||
#pane-line .card h3::before{background:var(--cyan)}
|
||||
#pane-buy .card h3::before,#pane-campaigns .card h3::before{background:var(--amber)}
|
||||
#pane-earn .card h3::before,#pane-earnings .card h3::before{background:var(--violet)}
|
||||
#pane-promo .card h3::before{background:var(--cyan)}
|
||||
#pane-wallet .card h3::before,#pane-profile .card h3::before{background:var(--mint)}
|
||||
.bo .card{background:linear-gradient(165deg,rgba(24,36,31,.6),rgba(13,19,17,.66))}
|
||||
#pane-line .qualbar,#nextCard{border-left:3px solid rgba(67,232,195,.4)}
|
||||
.promo-block{background:rgba(4,8,7,.55);border:1px solid var(--line);border-radius:12px;
|
||||
padding:14px 16px;margin:0 0 12px;font-size:13.5px;line-height:1.6;white-space:pre-wrap}
|
||||
.promo-block .btn{margin-top:10px}
|
||||
/* back-office polish: pane transitions, quick actions */
|
||||
@media(prefers-reduced-motion:no-preference){
|
||||
.pane:not([hidden]){animation:panein .25s ease}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<title>The contract | InstantAdPay</title>
|
||||
<meta name="description" content="Plain-language review of the InstantAdPay settlement contract: what it does, what nobody can change, what the operator can and cannot touch, and how to verify all of it yourself.">
|
||||
<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=20260905g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905i">
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
@@ -129,8 +129,8 @@
|
||||
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.</div>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="/assets/common.js?v=20260905g"></script>
|
||||
<script src="/assets/contract.js?v=20260905g"></script>
|
||||
<script src="/assets/chat.js?v=20260905g"></script>
|
||||
<script src="/assets/common.js?v=20260905i"></script>
|
||||
<script src="/assets/contract.js?v=20260905i"></script>
|
||||
<script src="/assets/chat.js?v=20260905i"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+5
-5
@@ -5,7 +5,7 @@
|
||||
<title>InstantAdPay: advertise and earn, locked in code</title>
|
||||
<meta name="description" content="Real ad packages with instant on-chain settlement. Every purchase pays the sponsor line in the same transaction, verifiable by anyone on the live ledger.">
|
||||
<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=20260905g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905i">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -405,9 +405,9 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script src="/assets/common.js?v=20260905g"></script>
|
||||
<script src="/assets/wallet.js?v=20260905g"></script>
|
||||
<script src="/assets/home.js?v=20260905g"></script>
|
||||
<script src="/assets/chat.js?v=20260905g"></script>
|
||||
<script src="/assets/common.js?v=20260905i"></script>
|
||||
<script src="/assets/wallet.js?v=20260905i"></script>
|
||||
<script src="/assets/home.js?v=20260905i"></script>
|
||||
<script src="/assets/chat.js?v=20260905i"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+4
-4
@@ -5,7 +5,7 @@
|
||||
<title>Live ledger | InstantAdPay</title>
|
||||
<meta name="description" content="Every purchase, payout, and pass-up on InstantAdPay, streamed straight from the blockchain with a verify link on every line.">
|
||||
<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=20260905g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905i">
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
@@ -25,8 +25,8 @@
|
||||
<div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="/assets/common.js?v=20260905g"></script>
|
||||
<script src="/assets/ledger.js?v=20260905g"></script>
|
||||
<script src="/assets/chat.js?v=20260905g"></script>
|
||||
<script src="/assets/common.js?v=20260905i"></script>
|
||||
<script src="/assets/ledger.js?v=20260905i"></script>
|
||||
<script src="/assets/chat.js?v=20260905i"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+41
-5
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Member area | InstantAdPay</title>
|
||||
<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=20260905g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905i">
|
||||
</head>
|
||||
<body class="bo-body">
|
||||
|
||||
@@ -59,7 +59,9 @@
|
||||
<button data-pane="campaigns" type="button"><svg viewBox="0 0 24 24"><path d="M3 11l14-5v12L3 13v-2z"/><path d="M17 8a4 4 0 0 1 0 8M7 13v5a2 2 0 0 0 4 0v-3"/></svg>Campaigns</button>
|
||||
<button data-pane="earn" type="button"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5v9M9 10c0-1.1 1.3-1.8 3-1.8s3 .7 3 1.8-1.3 1.6-3 1.8-3 .7-3 1.8 1.3 1.8 3 1.8 3-.7 3-1.8"/></svg>Earn credits</button>
|
||||
<button data-pane="earnings" type="button"><svg viewBox="0 0 24 24"><path d="M4 17l5-5 4 3 7-8"/><path d="M14 7h6v6"/></svg>Earnings</button>
|
||||
<button data-pane="promo" type="button"><svg viewBox="0 0 24 24"><path d="M7 10s5-1 9-5v14c-4-4-9-5-9-5H5a2 2 0 0 1-2-2v0a2 2 0 0 1 2-2h2z"/><path d="M8 15l1 5h2l-1-5"/></svg>Promo tools</button>
|
||||
<button data-pane="wallet" type="button"><svg viewBox="0 0 24 24"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18"/><circle cx="16.5" cy="14.5" r="1.4"/></svg>Wallet</button>
|
||||
<button data-pane="profile" type="button"><svg viewBox="0 0 24 24"><circle cx="12" cy="8.5" r="3.6"/><path d="M4.5 20c1.6-3.6 4.2-5.2 7.5-5.2s5.9 1.6 7.5 5.2"/></svg>Profile</button>
|
||||
</nav>
|
||||
<div class="bo-links" id="adSlotSide" hidden style="border-top:1px solid var(--line)"></div>
|
||||
<div class="bo-links">
|
||||
@@ -227,6 +229,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pane" id="pane-promo" hidden>
|
||||
<div class="card">
|
||||
<h3>Share-ready posts, personalized with your link</h3>
|
||||
<p class="muted small">Copy, paste anywhere, done. Every post already carries your invite link,
|
||||
so the credit is always yours.</p>
|
||||
<div id="promoPosts"></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Email swipe</h3>
|
||||
<p class="muted small" id="promoSwipeWrap"></p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Banners</h3>
|
||||
<p class="muted small">Branded InstantAdPay banner sets are in production. They will land here
|
||||
sized for every standard placement, pre-tagged with your link.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pane" id="pane-profile" hidden>
|
||||
<div class="card">
|
||||
<h3>Your profile</h3>
|
||||
<p class="muted small">Your username is how other members see you: on the live ledger, in line
|
||||
rosters, and on your personal invite link. Pick something you would put on a business card.</p>
|
||||
<p><input id="pfUsername" placeholder="Username (3-20 letters, numbers, _)" style="width:100%;max-width:320px"></p>
|
||||
<button class="btn" id="pfSaveBtn">Save username</button>
|
||||
<p class="muted small" id="pfCurrent" style="margin-top:10px"></p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Account details</h3>
|
||||
<p class="muted small" id="pfDetails">…</p>
|
||||
<p class="muted small">More profile fields (avatar, bio, links) are on the roadmap.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pane" id="pane-wallet" hidden>
|
||||
<div class="grid c2">
|
||||
<div class="card"><h3>Your account</h3>
|
||||
@@ -253,9 +289,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/common.js?v=20260905g"></script>
|
||||
<script src="/assets/wallet.js?v=20260905g"></script>
|
||||
<script src="/assets/my.js?v=20260905g"></script>
|
||||
<script src="/assets/chat.js?v=20260905g"></script>
|
||||
<script src="/assets/common.js?v=20260905i"></script>
|
||||
<script src="/assets/wallet.js?v=20260905i"></script>
|
||||
<script src="/assets/my.js?v=20260905i"></script>
|
||||
<script src="/assets/chat.js?v=20260905i"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user