diff --git a/chain.js b/chain.js index d52b2e1..8d6bb7d 100644 --- a/chain.js +++ b/chain.js @@ -173,6 +173,7 @@ function decodeLog(log) { function loadState() { try { state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8')); } catch (e) { state = null; } if (!state || state.v !== 1) state = { v: 1, lastBlock: getConfig().deployBlock - 1, events: [] }; + if (!state.totals) { for (const ev of state.events) tally(ev); saveState(); } // one-time backfill } function saveState() { try { @@ -194,6 +195,7 @@ async function tail() { const ev = decodeLog(lg); if (!ev) continue; state.events.push(ev); + tally(ev); if (onEvent) { try { onEvent(ev); } catch (e) { console.error('chain onEvent', e.message); } } } if (state.events.length > KEEP_EVENTS) state.events = state.events.slice(-KEEP_EVENTS); @@ -205,6 +207,19 @@ async function tail() { } function recentEvents(n) { return state ? state.events.slice(-(n || 100)).reverse() : []; } +// running totals for the public counters — every number provable on-chain +function tally(ev) { + if (!state.totals) state.totals = { purchases: 0, paidInWei: '0', payouts: 0, payoutWei: '0', activations: 0 }; + const t = state.totals; + if (ev.type === 'Purchase') { t.purchases += 1; t.paidInWei = (BigInt(t.paidInWei) + BigInt(ev.paidWei)).toString(); } + if (ev.type === 'TierPaid') { t.payouts += 1; t.payoutWei = (BigInt(t.payoutWei) + BigInt(ev.amountWei)).toString(); } + if (ev.type === 'AwardPaid') { t.payouts += 1; t.payoutWei = (BigInt(t.payoutWei) + BigInt(ev.amountWei)).toString(); } + if (ev.type === 'MemberActivated') t.activations += 1; +} +function totals() { + return state && state.totals ? state.totals : { purchases: 0, paidInWei: '0', payouts: 0, payoutWei: '0', activations: 0 }; +} + function init(opts) { if (opts && opts.onEvent) onEvent = opts.onEvent; getConfig(); loadState(); @@ -213,4 +228,4 @@ function init(opts) { } module.exports = { init, getConfig, reloadConfig, memberIdByAccount, memberCount, member, - product, productCount, quoteWei, creditBalance, catalog, recentEvents, rpc }; + product, productCount, quoteWei, creditBalance, catalog, recentEvents, totals, rpc }; diff --git a/public/assets/home.js b/public/assets/home.js index e8e99d5..ad65e59 100644 --- a/public/assets/home.js +++ b/public/assets/home.js @@ -46,5 +46,26 @@ IAP.status('Purchase failed: ' + (e.message || e), 'bad'); } finally { btn.disabled = false; } } + async function loadStats() { + try { + const s = await (await fetch('/api/stats')).json(); + IAP.$('stMembers').textContent = (s.onchainMembers || 0).toLocaleString(); + IAP.$('stPurchases').textContent = (s.purchases || 0).toLocaleString(); + IAP.$('stPaid').textContent = IAP.fmtPol(s.paidInWei || '0'); + IAP.$('stPayouts').textContent = (s.payouts || 0).toLocaleString(); + } catch (e) {} + } + async function loadTicker() { + try { + const { events } = await (await fetch('/api/feed?n=30')).json(); + if (!events.length) return; + const inner = IAP.$('tickerInner'); + inner.innerHTML = events.map(ev => '' + IAP.describeEvent(ev, c) + '').join(''); + IAP.$('ticker').hidden = false; + } catch (e) {} + } loadLadder(); + loadStats(); + loadTicker(); + setInterval(loadStats, 60000); })(); diff --git a/public/assets/site.css b/public/assets/site.css index 53ee7e8..f41eb59 100644 --- a/public/assets/site.css +++ b/public/assets/site.css @@ -1,82 +1,131 @@ -/* InstantAdPay — site-wide styles. - Identity: "wire-transfer receipt meets neon ledger" — dark bank-slate ground, - electric mint for money-in-motion, warm amber for calls to action. */ +/* InstantAdPay visual system v2 — "commercial ad-network polish x DeFi flash". + Refs: Adsterra/PropellerAds (dual-audience hero, real-number counters, + format showcase) x DeFi landing patterns (aurora glows, glass cards, + bento grids, ticker heartbeat). Dark luxe ground; mint = money in motion; + amber gradient = action. Display face: Sora. */ :root{ - --ground:#0d1420; --panel:#141d2e; --panel2:#1a2538; --line:#26344d; - --ink:#e8eef7; --muted:#8fa1bb; --mint:#3ee6a8; --mint-soft:#10362b; - --amber:#f5b83d; --amber-ink:#0d1420; --bad:#ff8f7d; --mono:"Consolas","JetBrains Mono",monospace; + --ground:#070b16; --panel:rgba(19,28,48,.66); --panel-solid:#131c30; + --line:rgba(126,144,186,.16); --line-strong:rgba(126,144,186,.32); + --ink:#eef2fb; --muted:#8b9ab5; + --mint:#3de8a8; --mint-dim:#1c5b44; --mint-soft:rgba(61,232,168,.1); + --amber:#ffb238; --amber2:#ff7a45; --amber-ink:#160f04; + --violet:#8b6cf6; --cyan:#5cc8ee; --bad:#ff8f7d; + --mono:"Consolas","JetBrains Mono",monospace; + --disp:"Sora","Segoe UI",system-ui,sans-serif; + --radius:16px; } *{box-sizing:border-box} -body{margin:0;background:var(--ground);color:var(--ink);font:16px/1.55 "Segoe UI",system-ui,sans-serif} +body{margin:0;background:var(--ground);color:var(--ink);font:16px/1.6 "Segoe UI",system-ui,sans-serif;overflow-x:hidden} +/* aurora ground: two blurred glow fields, fixed behind everything */ +body::before{content:"";position:fixed;inset:0;z-index:-1;pointer-events:none; + background: + radial-gradient(560px 420px at 82% -6%, rgba(139,108,246,.22), transparent 65%), + radial-gradient(640px 460px at -8% 34%, rgba(61,232,168,.13), transparent 65%), + radial-gradient(520px 420px at 58% 108%, rgba(255,122,69,.10), transparent 65%);} a{color:var(--mint);text-decoration:none} a:hover{text-decoration:underline} -.wrap{max-width:1020px;margin:0 auto;padding:0 18px} +.wrap{max-width:1060px;margin:0 auto;padding:0 20px} +h1,h2,h3{font-family:var(--disp);letter-spacing:-.01em} /* nav */ -nav{border-bottom:1px solid var(--line);background:rgba(13,20,32,.92);position:sticky;top:0;z-index:10} -nav .wrap{display:flex;align-items:center;gap:22px;min-height:58px;flex-wrap:wrap;padding-top:6px;padding-bottom:6px} -.logo{font-weight:800;font-size:19px;color:var(--ink)} -.logo b{color:var(--amber)} -nav .links{display:flex;gap:18px;font-size:14.5px;flex:1} +nav{border-bottom:1px solid var(--line);background:rgba(7,11,22,.72);backdrop-filter:blur(14px); + -webkit-backdrop-filter:blur(14px);position:sticky;top:0;z-index:10} +nav .wrap{display:flex;align-items:center;gap:22px;min-height:62px;flex-wrap:wrap;padding-top:6px;padding-bottom:6px} +.logo{font-family:var(--disp);font-weight:800;font-size:20px;color:var(--ink)} +.logo b{background:linear-gradient(100deg,var(--amber),var(--amber2));-webkit-background-clip:text;background-clip:text;color:transparent} +nav .links{display:flex;gap:20px;font-size:14.5px;flex:1;flex-wrap:wrap} nav a{color:var(--muted)} nav a:hover{color:var(--ink);text-decoration:none} #navWallet{font-size:13.5px} -.rehearsal{background:#3d2a52;color:#c9a9f7;text-align:center;font-size:13px;padding:6px 10px} -.rehearsal b{color:#e6d5ff} +.rehearsal{background:linear-gradient(90deg,#2c2150,#1c3050);color:#c9b8f7;text-align:center;font-size:13px;padding:7px 12px} +.rehearsal b{color:#eadfff} /* buttons */ -.btn{display:inline-block;background:var(--amber);color:var(--amber-ink);border:0;border-radius:10px; - padding:12px 22px;font-weight:800;font-size:15.5px;cursor:pointer;font-family:inherit} -.btn.sec{background:transparent;color:var(--mint);border:2px solid var(--mint);padding:10px 20px} -.btn.small{padding:8px 14px;font-size:13.5px} -.btn:disabled{opacity:.45;cursor:default} -/* layout blocks */ -.hero{padding:64px 0 40px} -.hero h1{font-size:clamp(30px,5.5vw,50px);line-height:1.08;margin:0 0 14px;max-width:640px} -.hero h1 em{color:var(--mint);font-style:normal} -.hero p.lead{color:var(--muted);font-size:18px;max-width:560px;margin:0 0 26px} -.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:20px;margin:0 0 16px} +.btn{display:inline-block;background:linear-gradient(100deg,var(--amber),var(--amber2));color:var(--amber-ink); + border:0;border-radius:999px;padding:13px 26px;font-weight:800;font-size:15.5px;cursor:pointer;font-family:var(--disp); + box-shadow:0 6px 24px rgba(255,150,60,.28);transition:transform .15s ease,box-shadow .15s ease} +.btn:hover{transform:translateY(-2px);box-shadow:0 10px 30px rgba(255,150,60,.4);text-decoration:none} +.btn.sec{background:transparent;color:var(--mint);border:1.5px solid var(--mint);box-shadow:none} +.btn.sec:hover{box-shadow:0 6px 22px rgba(61,232,168,.22)} +.btn.small{padding:8px 16px;font-size:13.5px} +.btn:disabled{opacity:.45;cursor:default;transform:none;box-shadow:none} +/* hero */ +.hero{padding:76px 0 40px} +.eyebrow{font-family:var(--mono);font-size:12px;letter-spacing:.22em;text-transform:uppercase;color:var(--mint);margin:0 0 14px} +.hero h1{font-size:clamp(32px,5.6vw,58px);font-weight:800;line-height:1.06;margin:0 0 18px;max-width:720px;text-wrap:balance} +.hero h1 em{font-style:normal;background:linear-gradient(95deg,var(--mint),var(--cyan) 55%,var(--violet)); + -webkit-background-clip:text;background-clip:text;color:transparent} +.hero p.lead{color:var(--muted);font-size:19px;max-width:600px;margin:0 0 30px} +.hero p.lead b{color:var(--ink)} +/* live counter band — real chain numbers only */ +.stats{display:grid;grid-template-columns:repeat(2,1fr);gap:14px;margin:38px 0 8px} +@media(min-width:760px){.stats{grid-template-columns:repeat(4,1fr)}} +.stat{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:18px 20px; + backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)} +.stat .n{font-family:var(--disp);font-size:30px;font-weight:800;font-variant-numeric:tabular-nums;color:var(--mint)} +.stat .l{font-size:12.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-top:2px} +.stat .sub{font-size:11.5px;color:var(--muted)} +/* cards / grids */ +.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:22px;margin:0 0 16px; + backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)} +.card:hover{border-color:var(--line-strong)} .grid{display:grid;gap:16px} @media(min-width:760px){.grid.c3{grid-template-columns:1fr 1fr 1fr}.grid.c2{grid-template-columns:1fr 1fr}} -h2{font-size:24px;margin:36px 0 14px} -h3{font-size:17px;margin:0 0 8px} +h2{font-size:clamp(22px,3vw,30px);font-weight:800;margin:52px 0 16px;text-wrap:balance} +h3{font-size:17px;margin:0 0 8px;font-weight:700} .muted{color:var(--muted)} .small{font-size:13.5px} .mono{font-family:var(--mono)} +/* ticker: live money marquee */ +.ticker{overflow:hidden;border-top:1px solid var(--line);border-bottom:1px solid var(--line); + background:rgba(61,232,168,.05);white-space:nowrap;padding:9px 0;margin:26px 0 0} +.ticker .inner{display:inline-block;font-family:var(--mono);font-size:13px;color:var(--mint);padding-left:100%; + animation:tick 40s linear infinite} +.ticker .inner span{margin-right:44px;color:var(--muted)} +.ticker .inner span b{color:var(--mint);font-weight:600} +@keyframes tick{to{transform:translateX(-100%)}} +@media(prefers-reduced-motion:reduce){.ticker .inner{animation:none;padding-left:0}} /* ladder table */ table{width:100%;border-collapse:collapse;font-size:15px} -th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:middle} -th{color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:1px} +th,td{text-align:left;padding:12px 14px;border-bottom:1px solid var(--line);vertical-align:middle} +th{color:var(--muted);font-size:11.5px;text-transform:uppercase;letter-spacing:.1em;font-family:var(--disp)} td.num,th.num{font-variant-numeric:tabular-nums} tr:last-child td{border-bottom:0} .tablewrap{overflow-x:auto} /* ledger feed */ .feed{font-family:var(--mono);font-size:13.5px;line-height:1.7} -.feed .row{padding:7px 10px;border-bottom:1px solid var(--line);display:flex;gap:10px;align-items:baseline;flex-wrap:wrap} +.feed .row{padding:8px 12px;border-bottom:1px solid var(--line);display:flex;gap:10px;align-items:baseline;flex-wrap:wrap} .feed .row:first-child{background:var(--mint-soft)} .feed .t-Purchase{color:var(--amber)} .feed .t-TierPaid{color:var(--mint)} .feed .t-AdminPaid{color:var(--muted)} .feed .t-AwardPaid{color:var(--amber)} .feed .tx a{color:var(--muted);font-size:12px} -.badge{display:inline-block;background:var(--mint-soft);color:var(--mint);border-radius:10px;padding:2px 10px;font-size:12.5px;font-weight:700} -.badge.amber{background:#3c2f10;color:var(--amber)} -/* split diagram strip */ -.split{display:flex;gap:8px;margin:14px 0} -.split div{border-radius:8px;padding:10px 6px;text-align:center;font-size:12.5px;font-weight:700} -.split .s50{flex:5;background:var(--mint-soft);color:var(--mint)} -.split .s20{flex:2;background:#173347;color:#6cc4ee} -.split .s10{flex:1;background:#2c2440;color:#b39df1} -.split .sa{flex:2;background:#33290f;color:var(--amber)} -/* status line + toasts */ -#status{position:fixed;left:50%;transform:translateX(-50%);bottom:22px;background:var(--panel2); - border:1px solid var(--line);border-radius:12px;padding:12px 20px;font-size:14px;max-width:90vw; - box-shadow:0 8px 30px rgba(0,0,0,.5)} +.badge{display:inline-block;background:var(--mint-soft);color:var(--mint);border:1px solid rgba(61,232,168,.3); + border-radius:999px;padding:2px 12px;font-size:12.5px;font-weight:700} +.badge.amber{background:rgba(255,178,56,.1);border-color:rgba(255,178,56,.35);color:var(--amber)} +/* split strip */ +.split{display:flex;gap:8px;margin:16px 0} +.split div{border-radius:10px;padding:13px 6px;text-align:center;font-size:12.5px;font-weight:700;font-family:var(--disp)} +.split .s50{flex:5;background:var(--mint-soft);color:var(--mint);border:1px solid rgba(61,232,168,.3); + box-shadow:0 0 24px rgba(61,232,168,.12)} +.split .s20{flex:2;background:rgba(92,200,238,.09);color:var(--cyan);border:1px solid rgba(92,200,238,.3)} +.split .s10{flex:1;background:rgba(139,108,246,.1);color:var(--violet);border:1px solid rgba(139,108,246,.3)} +.split .sa{flex:2;background:rgba(255,178,56,.08);color:var(--amber);border:1px solid rgba(255,178,56,.28)} +/* FAQ accordions */ +details{border:1px solid var(--line);border-radius:12px;margin:0 0 10px;background:var(--panel)} +summary{cursor:pointer;padding:14px 18px;font-weight:700;font-family:var(--disp);font-size:15px} +details p{margin:0;padding:0 18px 16px;color:var(--muted);font-size:14.5px} +/* status toast */ +#status{position:fixed;left:50%;transform:translateX(-50%);bottom:22px;background:var(--panel-solid); + border:1px solid var(--line-strong);border-radius:14px;padding:13px 22px;font-size:14px;max-width:90vw;z-index:30; + box-shadow:0 12px 40px rgba(0,0,0,.55)} #status.ok{border-color:var(--mint)} #status.bad{border-color:var(--bad)} -footer{border-top:1px solid var(--line);margin-top:60px;padding:26px 0;color:var(--muted);font-size:13.5px} -input,select{background:var(--ground);border:1px solid var(--line);color:var(--ink);border-radius:8px; - padding:10px 12px;font-size:14.5px;font-family:inherit} +footer{border-top:1px solid var(--line);margin-top:70px;padding:30px 0;color:var(--muted);font-size:13.5px} +input,select{background:rgba(7,11,22,.6);border:1px solid var(--line-strong);color:var(--ink);border-radius:10px; + padding:11px 14px;font-size:14.5px;font-family:inherit} +input:focus,select:focus{border-color:var(--mint)} :focus-visible{outline:2px solid var(--mint);outline-offset:2px} @media(prefers-reduced-motion:no-preference){ .feed .row:first-child{animation:landed .9s ease} - @keyframes landed{from{background:#1d5c44}to{background:var(--mint-soft)}} + @keyframes landed{from{background:rgba(61,232,168,.28)}to{background:var(--mint-soft)}} } diff --git a/public/index.html b/public/index.html index e001655..158a30e 100644 --- a/public/index.html +++ b/public/index.html @@ -4,11 +4,13 @@
Advertising · settled on-chain · instantly
Buy real ad packages from 5 to 250 dollars. Every purchase settles through a smart contract you can read yourself, and your earnings land in @@ -18,8 +20,21 @@ Watch payments land live
-Every number above comes from the blockchain, not a marketing database. + Click any line on the live ledger to verify it yourself.
+ +