Add qualification badges to dashboard and matrix cards

Gold "★ Qualified" pill in the member-dashboard header (or an amber
"N more directs to qualify" pill), and a gold ✓ corner medallion on
every qualified position card in both the member tree and admin pyramid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-13 14:36:36 -05:00
parent 9f8898d2bd
commit 481423368b
4 changed files with 16 additions and 3 deletions
+2 -1
View File
@@ -78,7 +78,8 @@ const mtFmt=n=>Number(n||0).toLocaleString(undefined,{maximumFractionDigits:0});
function mtCard(n){ function mtCard(n){
if(!n)return '<div class="mtp-card mtp-open">open<br>slot</div>'; if(!n)return '<div class="mtp-card mtp-open">open<br>slot</div>';
const down=n.downCount?`<span class="mtp-down">⬇ ${n.downCount} below · ${mtFmt(n.downPol)} POL</span>`:'<span class="mtp-down mtp-down-none">no downline yet</span>'; const down=n.downCount?`<span class="mtp-down">⬇ ${n.downCount} below · ${mtFmt(n.downPol)} POL</span>`:'<span class="mtp-down mtp-down-none">no downline yet</span>';
return `<button class="mtp-card${n.id===matrixUI.rootId?' mtp-focus':''}" data-mid="${n.id}">${mtBadge(n)} <span class="mt-id">#${n.id}</span><span class="mtp-lvl">${esc(n.levelName)}</span><span class="mtp-sub">${n.directCount}/2 · ${mtFmt(n.earnedPol)} POL</span>${down}</button>`; const qmark=n.directCount>=2?'<span class="mtp-qmark" title="Qualified — 2/2 directs">✓</span>':'';
return `<button class="mtp-card${n.id===matrixUI.rootId?' mtp-focus':''}" data-mid="${n.id}">${qmark}${mtBadge(n)} <span class="mt-id">#${n.id}</span><span class="mtp-lvl">${esc(n.levelName)}</span><span class="mtp-sub">${n.directCount}/2 · ${mtFmt(n.earnedPol)} POL</span>${down}</button>`;
} }
function renderPyramid(){ function renderPyramid(){
const out=document.getElementById('matrixTree'),nav=document.getElementById('matrixNav'); const out=document.getElementById('matrixTree'),nav=document.getElementById('matrixNav');
+1 -1
View File
@@ -4,7 +4,7 @@
<main class="wrap" style="padding:26px 0 60px"> <main class="wrap" style="padding:26px 0 60px">
<section id="idPrompt" class="login-panel" style="margin:8vh auto"><h1>Your position, live from the blockchain.</h1><p>Enter your RM Circle member ID to see your payments, your team, and your lineage — everything verified on Polygon.</p><form id="idForm"><div class="field"><label for="memberId">Your member ID</label><input id="memberId" class="input" inputmode="numeric" placeholder="e.g. 46" required></div><button class="btn btn-primary" style="width:100%">Show My Dashboard</button><div id="idError" class="micro" style="color:var(--danger)"></div></form></section> <section id="idPrompt" class="login-panel" style="margin:8vh auto"><h1>Your position, live from the blockchain.</h1><p>Enter your RM Circle member ID to see your payments, your team, and your lineage — everything verified on Polygon.</p><form id="idForm"><div class="field"><label for="memberId">Your member ID</label><input id="memberId" class="input" inputmode="numeric" placeholder="e.g. 46" required></div><button class="btn btn-primary" style="width:100%">Show My Dashboard</button><div id="idError" class="micro" style="color:var(--danger)"></div></form></section>
<section id="dash" class="hidden"> <section id="dash" class="hidden">
<div style="display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:6px"><div><div class="eyebrow">Member position</div><h1 style="margin:6px 0 0">Position <span class="gold" id="dTitle">#—</span> <span id="dVerified" class="live-badge" style="vertical-align:middle;margin-left:8px"><span class="dot"></span> Verified on-chain</span></h1></div><button id="switchId" class="btn btn-secondary btn-sm">Look up another ID</button></div> <div style="display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:6px"><div><div class="eyebrow">Member position</div><h1 style="margin:6px 0 0">Position <span class="gold" id="dTitle">#—</span> <span id="dVerified" class="live-badge" style="vertical-align:middle;margin-left:8px"><span class="dot"></span> Verified on-chain</span> <span id="dQualified"></span></h1></div><button id="switchId" class="btn btn-secondary btn-sm">Look up another ID</button></div>
<div id="dFacts" class="facts" style="grid-template-columns:repeat(3,1fr);margin:18px 0"></div> <div id="dFacts" class="facts" style="grid-template-columns:repeat(3,1fr);margin:18px 0"></div>
<div class="table-card" style="margin-bottom:18px"><h2 style="margin:0 0 4px">Your team</h2><p style="color:var(--muted);font-size:13px;margin:0 0 14px">Your position's matrix — the rollup line on each card counts everyone underneath, all the way down. Open slots are where the next placements land.</p><div id="dTree"></div></div> <div class="table-card" style="margin-bottom:18px"><h2 style="margin:0 0 4px">Your team</h2><p style="color:var(--muted);font-size:13px;margin:0 0 14px">Your position's matrix — the rollup line on each card counts everyone underneath, all the way down. Open slots are where the next placements land.</p><div id="dTree"></div></div>
<div class="table-card" style="margin-bottom:18px"><h2 style="margin:0 0 4px">Payments received</h2><p style="color:var(--muted);font-size:13px;margin:0 0 10px">Every payment your position has received, straight from the smart contract.</p><div id="dIncome"></div></div> <div class="table-card" style="margin-bottom:18px"><h2 style="margin:0 0 4px">Payments received</h2><p style="color:var(--muted);font-size:13px;margin:0 0 10px">Every payment your position has received, straight from the smart contract.</p><div id="dIncome"></div></div>
+6 -1
View File
@@ -9,8 +9,9 @@
function card(n,focus){ function card(n,focus){
if(!n)return '<div class="mtp-card mtp-open">open<br>slot</div>'; if(!n)return '<div class="mtp-card mtp-open">open<br>slot</div>';
const badge=n.tier===2?'<span class="mt-badge mt-prem">P</span>':'<span class="mt-badge">S</span>'; const badge=n.tier===2?'<span class="mt-badge mt-prem">P</span>':'<span class="mt-badge">S</span>';
const qmark=n.directCount>=2?'<span class="mtp-qmark" title="Qualified — 2/2 directs">✓</span>':'';
const down=n.downCount?`<span class="mtp-down">⬇ ${n.downCount} below · ${fmt(n.downPol)} POL</span>`:'<span class="mtp-down mtp-down-none">no downline yet</span>'; const down=n.downCount?`<span class="mtp-down">⬇ ${n.downCount} below · ${fmt(n.downPol)} POL</span>`:'<span class="mtp-down mtp-down-none">no downline yet</span>';
return `<div class="mtp-card${focus?' mtp-focus':''}" style="cursor:default">${badge} <span class="mt-id">#${n.id}</span><span class="mtp-lvl">${esc(n.levelName)}</span><span class="mtp-sub">${n.directCount}/2 · ${fmt(n.earnedPol)} POL</span>${down}</div>`; return `<div class="mtp-card${focus?' mtp-focus':''}" style="cursor:default">${qmark}${badge} <span class="mt-id">#${n.id}</span><span class="mtp-lvl">${esc(n.levelName)}</span><span class="mtp-sub">${n.directCount}/2 · ${fmt(n.earnedPol)} POL</span>${down}</div>`;
} }
function renderTree(root){ function renderTree(root){
if(!root){document.getElementById('dTree').innerHTML='<div class="empty">Team view is still indexing — check back in a few minutes.</div>';return} if(!root){document.getElementById('dTree').innerHTML='<div class="empty">Team view is still indexing — check back in a few minutes.</div>';return}
@@ -22,6 +23,10 @@
function render(d){ function render(d){
prompt.classList.add('hidden');dash.classList.remove('hidden'); prompt.classList.add('hidden');dash.classList.remove('hidden');
document.getElementById('dTitle').textContent='#'+d.id; document.getElementById('dTitle').textContent='#'+d.id;
const qb=document.getElementById('dQualified');
if(qb)qb.outerHTML=d.directCount>=2
?'<span id="dQualified" class="q-badge q-yes">★ Qualified</span>'
:`<span id="dQualified" class="q-badge q-no">${2-d.directCount} more direct${2-d.directCount===1?'':'s'} to qualify</span>`;
document.getElementById('dFacts').innerHTML= document.getElementById('dFacts').innerHTML=
`<div class="fact"><small>Tier</small><strong>${esc(d.tierName)}</strong></div>`+ `<div class="fact"><small>Tier</small><strong>${esc(d.tierName)}</strong></div>`+
`<div class="fact"><small>Level</small><strong>${esc(d.levelName)}</strong></div>`+ `<div class="fact"><small>Level</small><strong>${esc(d.levelName)}</strong></div>`+
+7
View File
@@ -99,3 +99,10 @@ a{color:inherit}.wrap{width:min(1160px,calc(100% - 32px));margin:auto}.nav{heigh
.mtp-down{font-size:10.5px;color:var(--gold);border-top:1px solid #21405a;margin-top:4px;padding-top:4px;width:100%} .mtp-down{font-size:10.5px;color:var(--gold);border-top:1px solid #21405a;margin-top:4px;padding-top:4px;width:100%}
.mtp-down-none{color:#4e6578} .mtp-down-none{color:#4e6578}
/* Qualification badges */
.q-badge{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:7px 12px;font-size:12px;font-weight:900;text-transform:uppercase;letter-spacing:.8px;vertical-align:middle}
.q-yes{background:linear-gradient(135deg,rgba(243,190,67,.18),rgba(243,190,67,.08));color:var(--gold);border:1px solid rgba(243,190,67,.45);box-shadow:0 0 18px rgba(243,190,67,.15)}
.q-no{background:rgba(174,189,202,.08);color:var(--muted);border:1px solid #33506a}
.mtp-card{position:relative}
.mtp-qmark{position:absolute;top:-8px;right:-8px;width:20px;height:20px;border-radius:50%;background:linear-gradient(135deg,var(--gold),#f7d173);color:#152033;font-size:12px;font-weight:900;display:grid;place-items:center;box-shadow:0 2px 8px rgba(0,0,0,.4)}