Files
rm-circle-team-router/public/suite-grants.js
T
martbost 6a7c5161ff Give the top of the ladder a real prize: Team Grants + Network Intelligence
Marty's read was right — the upper tiers felt flat, and the reason is that
they scaled QUANTITY, not KIND. L1->L2 was a category change (you couldn't
write, now you can). Everything above L4 was a multiplier on a capability
already owned at L2: sound more like you, measure it, more pages, five at
once. Meanwhile the price doubles each rung — Corona costs ~17x Culmen. No
batch button is worth 17x a voice profile.

The fix is a change of axis. Every tool from L1 to L8 was a "me" tool, but
past the first levels this business isn't about you: get two, help your two,
teach them to teach. So the top now operates on the ORGANISATION.

L7 Team Grants — hand Suite capacity down to anyone in your own org.
Deliberately from a separate pool that the level grants, NOT out of the
leader's own allowance: making someone choose between equipping their team
and using their own tools means nobody ever grants anything. Recipients are
verified in-org against the contract. Grants can lift a tool the recipient's
level hasn't unlocked — that's the point, not a loophole. Being helped is
made visible to the recipient, since half the value is knowing an upline
backed you.

L8 Network Intelligence — every team ad pooled and anonymised: which angles,
headlines, creative and formats actually earn their impressions. No member
alone has enough traffic to learn anything from display; together they do.
It can't be bought, and it compounds monthly with no further work. Holds two
lines: nothing identifies anyone, and nothing under 2,000 impressions gets a
reported rate — under-evidenced rows are counted and disclosed rather than
silently dropped. The written readout is hand-authored, not generated:
these are conclusions about money, and a model paraphrasing a table is
exactly where an invented number slips in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 12:26:39 -05:00

161 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Team Grants client.
(function () {
'use strict';
var $ = function (id) { return document.getElementById(id); };
function esc(s) {
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
});
}
function n(v) { return Number(v || 0).toLocaleString(); }
var state = { tool: null, status: null, level: 0, minLevel: 7 };
function gate(msg) {
$('gGate').style.display = 'block';
$('gGate').innerHTML = msg;
}
// Shown to everyone, including people below level 7 — being helped should be
// visible, not silent.
function renderReceived(received) {
var keys = Object.keys(received || {});
if (!keys.length) { $('gGot').innerHTML = ''; return; }
var parts = keys.map(function (k) {
var r = received[k];
var who = r.from.map(function (f) { return '#' + f.by; });
var uniq = who.filter(function (v, i) { return who.indexOf(v) === i; });
var t = (state.status && state.status.tools[k]) || {};
return '<div>· <b style="color:var(--text)">' + n(r.total) + ' ' + esc(t.unit || k) +
'</b> of ' + esc((t.label || k).toLowerCase()) + ', from ' + uniq.join(', ') + '</div>';
});
$('gGot').innerHTML = '<div class="g-got"><b style="color:var(--teal)">Someone above you topped you up this month.</b>' +
parts.join('') + '<div style="color:var(--muted);font-size:12.5px;margin-top:6px">' +
'It is already added to your allowances — nothing to claim.</div></div>';
}
function renderPools() {
var host = $('gPools');
host.innerHTML = '';
var tools = state.status.tools;
Object.keys(tools).forEach(function (k) {
var t = tools[k];
var d = document.createElement('div');
d.className = 'g-pool' + (state.tool === k ? ' on' : '');
d.innerHTML = '<div class="big">' + n(t.remaining) + '</div>' +
'<div class="lab">' + esc(t.label) + '</div>' +
'<div class="of">left to give of ' + n(t.pool) + '</div>';
d.addEventListener('click', function () {
state.tool = k;
renderPools();
var el = $('gN');
el.max = Math.min(t.max, t.remaining);
el.step = t.step;
el.value = Math.min(t.step, t.remaining) || '';
$('gNHint').textContent = 'Up to ' + n(Math.min(t.max, t.remaining)) + ' ' + t.unit +
' in one go. You have ' + n(t.remaining) + ' left to give this month.';
});
host.appendChild(d);
});
}
function renderHistory(hist) {
if (!hist || !hist.length) { $('gHistWrap').style.display = 'none'; return; }
$('gHistWrap').style.display = 'block';
var tb = $('gHist');
tb.innerHTML = '';
hist.forEach(function (h) {
var t = (state.status.tools[h.tool]) || {};
var tr = document.createElement('tr');
tr.innerHTML = '<td><b>#' + esc(h.to) + '</b></td>' +
'<td>' + esc(t.label || h.tool) + '</td>' +
'<td><b>' + n(h.n) + '</b> ' + esc(t.unit || '') + '</td>' +
'<td>' + esc(String(h.at || '').slice(0, 10)) + '</td>';
tb.appendChild(tr);
});
}
async function give() {
$('gErr').style.display = 'none';
$('gOk').style.display = 'none';
if (!state.tool) {
$('gErr').textContent = 'Pick what you want to give first.';
$('gErr').style.display = 'block';
return;
}
var to = Number($('gTo').value) || 0;
var amount = Number($('gN').value) || 0;
if (!to) {
$('gErr').textContent = 'Enter the position number you want to help.';
$('gErr').style.display = 'block';
return;
}
$('gGo').disabled = true;
$('gGo').innerHTML = '<span class="spin"></span>Sending…';
try {
var r = await fetch('/api/public/suite-grants', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ to: to, tool: state.tool, n: amount })
});
var d = await r.json();
if (!r.ok) {
$('gErr').textContent = d.error || 'That did not go through.';
$('gErr').style.display = 'block';
} else {
state.status = d.status;
var t = state.status.tools[d.granted.tool] || {};
$('gOk').innerHTML = '✅ <b>Sent.</b> #' + esc(d.granted.to) + ' now has ' + n(d.granted.n) + ' more ' +
esc(t.unit || '') + ' this month. Worth telling them — it will just appear in their allowance otherwise.';
$('gOk').style.display = 'block';
renderPools();
renderHistory(state.status.history);
}
} catch (e) {
$('gErr').textContent = 'Connection hiccup — try again.';
$('gErr').style.display = 'block';
}
$('gGo').disabled = false;
$('gGo').innerHTML = '🎁 Give it to them';
}
async function boot() {
try {
var r = await fetch('/api/public/suite-grants');
if (r.status === 401) { gate('You’re not signed in yet. <a href="/suite" style="color:var(--gold);font-weight:700">Open the Suite</a> and connect the wallet that holds your position, then come back.'); return; }
if (r.status === 403) {
var g = await r.json();
gate((g.error || 'Not open for this position yet.') + ' <a href="/suite" style="color:var(--gold);font-weight:700">See your Suite</a>.');
return;
}
if (!r.ok) return;
var d = await r.json();
state.status = d.status;
state.level = d.level;
state.minLevel = d.minLevel;
renderReceived(d.status.received);
if (!d.status.canGrant) {
gate('<b>Team Grants unlock at Vertex (level 7).</b> At that point you can hand your own Suite capacity ' +
'down to anyone in your organisation who needs it. Anything granted <em>to</em> you shows above and is already in your allowances.');
return;
}
$('gBody').style.display = 'block';
var first = Object.keys(d.status.tools)[0];
state.tool = first;
renderPools();
var t = d.status.tools[first];
$('gN').max = Math.min(t.max, t.remaining);
$('gN').step = t.step;
$('gN').value = Math.min(t.step, t.remaining) || '';
$('gNHint').textContent = 'Up to ' + n(Math.min(t.max, t.remaining)) + ' ' + t.unit + ' in one go.';
renderHistory(d.status.history);
} catch (e) {}
}
document.addEventListener('DOMContentLoaded', function () {
boot();
$('gGo').addEventListener('click', give);
});
})();