d5c9e33e0e
Text placements out-perform banners on this network — 357 text ads average ~171 clicks each against ~150 for banners, on far less inventory — so members can now run them too. The format is measured, not guessed. Across 60 live text ads: Subject caps at 20 characters and Body is ALWAYS exactly three <br>-joined lines of at most 24 characters. Every ad in the sample obeyed it. Emoji: the NAS columns are latin1, but so is the connection, so UTF-8 bytes pass through and render — which is how the existing emoji ads got there. The bridge's clean() would have deleted every emoji via iconv //TRANSLIT//IGNORE, so text copy now uses clean_ad_text(), which preserves them and strips angle brackets instead. The bridge builds the <br> separators itself from a lines[] array, so a member can never inject markup. The engine writes plain text to a tight budget and we apply emoji ourselves: models cannot count characters, least of all with emoji, so decoration is arithmetic we control. It also lets the emoji rotate independently of the copy. The palette carries no money emoji — the network is full of them, but implying earnings outranks matching the neighbours. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
126 lines
8.8 KiB
JavaScript
126 lines
8.8 KiB
JavaScript
// The Circle Suite — P0 tool wall. Wallet sign-in (same challenge/verify flow
|
||
// as the training gate) -> live level from the contract -> tiles light up.
|
||
// Every tile is always VISIBLE: unlocked at-or-below the member's level,
|
||
// dimmed above it. Future tools carry an honest "in development" tag.
|
||
(function () {
|
||
'use strict';
|
||
var LEVELS = ['Scintilla', 'Ascensus', 'Fabrica', 'Culmen', 'Apex', 'Fastigium', 'Vertex', 'Corona'];
|
||
var TOOLS = [
|
||
{ lv: 1, ico: '🎬', name: 'Promo Center', desc: 'Videos, posts, swipes, emails and the banner kit — every piece pre-personalized with your link.', href: '/tools', live: true },
|
||
{ lv: 1, ico: '🖨️', name: 'Printable Handouts', desc: 'Five full-color designs that print two per page — with YOUR QR code on the paper.', href: '/flyers', live: true },
|
||
{ lv: 1, ico: '✅', name: 'Fast Start + Weekly Rhythm', desc: 'The 48-hour launch checklist and the 20-minute weekly routine, personalized and printable.', href: '/fast-start', live: true },
|
||
{ lv: 1, ico: '📊', name: 'Generation Pay Chart', desc: 'Your personal when-does-each-generation-pay-me chart, read live from the contract.', href: '/generation-pay', live: true },
|
||
{ lv: 1, ico: '🎓', name: 'The Circle Method', desc: 'The 10-lesson recruiting and coaching course. Lessons unlock with this same wallet.', href: '/training', live: true },
|
||
{ lv: 1, ico: '📈', name: 'Live Dashboard', desc: 'Your pipeline, organization bar, coach panel and wallet-verified team messages.', href: '/my', live: true },
|
||
{ lv: 1, ico: '🤖', name: 'AI Coach', desc: '24/7 answers about the system, in 21+ languages — the chat bubble on every page of this site.', href: '#', live: true, coach: true },
|
||
{ lv: 2, ico: '✍️', name: 'Copy Engine', desc: 'An AI copywriter tuned to this business: posts, DMs, follow-ups and objection replies with your link filled in.', href: '/suite/copy', live: true },
|
||
{ lv: 2, ico: '🧱', name: 'Page Builder', desc: 'Answer four questions, get your own hosted invitation page — your story, your angle video, your QR.', href: '/suite/page', live: true },
|
||
{ lv: 3, ico: '📧', name: 'Email Engine', desc: 'Welcome series, follow-up sequences and broadcasts in the team voice — exportable to any autoresponder.', href: '/suite/email', live: true },
|
||
{ lv: 3, ico: '🎥', name: 'Video Maker', desc: 'The team’s master promo videos rendered with your personal end-card — your name, your QR, your link.', href: '/suite/video', live: true },
|
||
{ lv: 4, ico: '🗣️', name: 'Voice Profile + Funnels', desc: 'Output that sounds like YOU, plus multi-page funnels on your own team subdomain.', live: false },
|
||
{ lv: 1, ico: '🚦', name: 'Traffic Desk', desc: 'Syndicated network display advertising — run banners or AI-written text ads on the team’s own ad network. Monthly impressions scale with your level: 2,500 at Scintilla up to 150,000 at Corona.', href: '/suite/traffic', live: true },
|
||
{ lv: 6, ico: '🏭', name: 'Funnel Factory', desc: 'Complete hosted funnels with A/B variants, replay funnels (your team webinar as an on-demand registration page with a timed CTA), and a lead CRM.', live: false },
|
||
{ lv: 7, ico: '🧭', name: 'Leader Ops', desc: 'Team radar, AI coaching digests for your legs, and cohort training rooms.', live: false },
|
||
{ lv: 8, ico: '👑', name: 'Founder Desk', desc: 'Your own AI operator running your promotion, API access, and the inner circle.', live: false }
|
||
];
|
||
|
||
var $ = function (id) { return document.getElementById(id); };
|
||
var me = null; // {id, level, levelName, tierName, directCount, inOrg}
|
||
|
||
function pips() {
|
||
var host = $('suPips');
|
||
host.querySelectorAll('.su-pip').forEach(function (p) { p.remove(); });
|
||
for (var k = 1; k <= 8; k++) {
|
||
var p = document.createElement('span');
|
||
p.className = 'su-pip' + (me && me.inOrg && me.level >= k ? (k === 8 ? ' gold' : ' on') : '');
|
||
p.title = 'Level ' + k + ' · ' + LEVELS[k - 1];
|
||
host.appendChild(p);
|
||
}
|
||
}
|
||
|
||
function render() {
|
||
var grid = $('suGrid');
|
||
grid.innerHTML = '';
|
||
var lastLv = 0;
|
||
TOOLS.forEach(function (t) {
|
||
if (t.lv !== lastLv) {
|
||
lastLv = t.lv;
|
||
var h = document.createElement('div');
|
||
h.className = 'su-lvhead';
|
||
h.innerHTML = '<span class="n">L' + t.lv + '</span><span class="nm">' + LEVELS[t.lv - 1] + '</span><span class="rule"></span>';
|
||
grid.appendChild(h);
|
||
}
|
||
var open = !!(me && me.inOrg && me.level >= t.lv);
|
||
var d = document.createElement('div');
|
||
d.className = 'su-tile ' + (me ? (open ? 'open' : 'locked') : 'locked');
|
||
var badge = me
|
||
? (open ? '✓ YOURS' : 'UNLOCKS AT ' + LEVELS[t.lv - 1].toUpperCase())
|
||
: 'LEVEL ' + t.lv;
|
||
d.innerHTML = '<span class="badge">' + badge + '</span><span class="ico">' + t.ico + '</span><h3>' + t.name + '</h3><p>' + t.desc + '</p>'
|
||
+ (t.live
|
||
? (open || !me ? '<a class="go" href="' + t.href + '"' + (t.coach ? ' data-coach="1"' : '') + '>Open →</a>' : '')
|
||
: '<span class="dev">IN DEVELOPMENT — ships with the Suite build-out</span>');
|
||
grid.appendChild(d);
|
||
});
|
||
var coach = grid.querySelector('[data-coach]');
|
||
if (coach) coach.addEventListener('click', function (ev) { ev.preventDefault(); var f = document.querySelector('.chat-fab'); if (f) f.click(); });
|
||
}
|
||
|
||
async function refreshMe() {
|
||
try {
|
||
var r = await fetch('/api/public/suite-me');
|
||
if (!r.ok) return false;
|
||
me = await r.json();
|
||
if (me.beta && !me.allowed) {
|
||
$('suMe').style.display = 'block';
|
||
$('suMe').innerHTML = '<b>#' + me.id + '</b> — you’re early! The Circle Suite is in <b>team beta</b> right now. Your license is already reserved by your position, and every tool below activates for you the moment we open the doors. Nothing to do — watch the team channel.';
|
||
$('suConnect').style.display = 'none';
|
||
me = null; pips(); render();
|
||
return true;
|
||
}
|
||
$('suMe').style.display = 'block';
|
||
$('suMe').innerHTML = me.inOrg
|
||
? '<b>#' + me.id + '</b> · ' + me.tierName + ' · Level ' + me.level + ' (' + me.levelName + ')' +
|
||
(me.directCount >= 2 ? ' · <span style="color:var(--teal)">✓ qualified</span>' : ' · ' + me.directCount + '/2 directs') +
|
||
'<br>Your license covers <b>Level ' + me.level + '</b> and everything below it' +
|
||
(me.level < 8 ? ' — your next upgrade extends it to ' + LEVELS[me.level] + '.' : ' — full Suite.')
|
||
: '<b>#' + me.id + '</b> — this position isn’t inside our team’s organization. The Suite is a benefit of building with our circle; ask the person who shared this page about joining through the team.';
|
||
$('suConnect').style.display = 'none';
|
||
pips(); render();
|
||
return true;
|
||
} catch (e) { return false; }
|
||
}
|
||
|
||
async function connect() {
|
||
var btn = $('suConnect'), err = $('suErr');
|
||
err.style.display = 'none';
|
||
btn.disabled = true; btn.textContent = 'Connecting…';
|
||
try {
|
||
var eth = await window.RMCWallet.pick();
|
||
if (!eth) { err.textContent = 'No wallet found in this browser. On a phone, open this page inside your wallet app’s browser (the DApps tab) — type rmcircle.team/suite there.'; err.style.display = 'block'; btn.disabled = false; btn.textContent = '🔑 Connect wallet — light up my tools'; return; }
|
||
var accs = await eth.request({ method: 'eth_requestAccounts' });
|
||
var account = accs[0];
|
||
try { await window.RMCWallet.ensureChain(eth); } catch (ce) {}
|
||
var ch = await (await fetch('/api/public/msg-challenge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: account }) })).json();
|
||
if (!ch.message) throw new Error(ch.error || 'Could not start sign-in.');
|
||
var sig = await eth.request({ method: 'personal_sign', params: [ch.message, account] });
|
||
var vr = await (await fetch('/api/public/msg-verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: account, signature: sig }) })).json();
|
||
if (!vr.ok) throw new Error(vr.error || 'Signature check failed.');
|
||
await refreshMe();
|
||
} catch (e) {
|
||
var m = String((e && e.message) || e);
|
||
err.textContent = /not registered|no position/i.test(m)
|
||
? 'This wallet doesn’t hold a position yet. The Suite comes with one — see the Getting Started page.'
|
||
: 'Sign-in didn’t complete: ' + m + ' — tap the button to try again.';
|
||
err.style.display = 'block';
|
||
}
|
||
btn.disabled = false; btn.textContent = '🔑 Connect wallet — light up my tools';
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
pips(); render();
|
||
$('suConnect').addEventListener('click', connect);
|
||
refreshMe(); // silent: already signed in (training gate / Mini App bridge)
|
||
});
|
||
})();
|