Founder Desk (L8): weekly campaign pack + Suite API
Completes the ladder — no tier is a placeholder now. Every tier below builds one thing at a time, which is right for someone still finding their words. At the top the constraint is different: these are people running organisations who don't have an afternoon to spend clicking. So one pass produces five social posts across five different angles, three outreach messages (first approach, follow-up, pyramid-scheme answer), an email, and three text ads — all carrying their link, all in the member's own voice profile if they have one. Partial failures are reported rather than hidden: if the engine can't finish a section the pack says which, instead of quietly handing over a short week. API access is real, not a label: a personal key over GET /me, GET /team, POST /generate. Keys are stored hashed and shown exactly once, compared in constant time, and every call goes through the same meters and compliance rules as the web tools. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex"><meta name="description" content="Founder Desk — a whole week of promotion built in one pass, plus API access to the Suite."><title>Founder Desk | The Circle Suite</title><link rel="icon" type="image/png" href="/favicon.png"><link rel="stylesheet" href="/styles.css">
|
||||
<style>
|
||||
.dw{max-width:900px;margin:0 auto;padding:24px 20px 70px}
|
||||
.dw h1{font-size:clamp(26px,4.5vw,36px);margin:6px 0 8px}
|
||||
.dw .lede{color:var(--muted);line-height:1.6;margin-bottom:18px}
|
||||
#dGate{display:none;border:2px solid var(--gold);border-radius:12px;padding:16px 20px;margin-bottom:18px;line-height:1.6}
|
||||
.d-card{background:var(--card,#0d2236);border:1px solid var(--line,#24425d);border-radius:14px;padding:18px 20px;margin-bottom:16px}
|
||||
.d-card h2{font-size:19px;margin:0 0 4px}
|
||||
.d-card .sub{color:var(--muted);font-size:13.5px;line-height:1.55;margin-bottom:12px}
|
||||
.d-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:14px}
|
||||
#dErr{display:none;color:#f0a05a;margin-top:12px;font-size:14px;line-height:1.55}
|
||||
.d-sec{margin-top:24px}
|
||||
.d-sec > h3{font-size:16px;margin:0 0 4px;color:var(--gold);letter-spacing:.3px}
|
||||
.d-sec > .why{color:var(--muted);font-size:12.5px;margin-bottom:10px;line-height:1.5}
|
||||
.d-item{border:1px solid var(--line);border-radius:12px;padding:13px 15px;margin-bottom:10px;position:relative}
|
||||
.d-item .tag{font-size:10.5px;font-weight:900;letter-spacing:1px;text-transform:uppercase;color:var(--teal);margin-bottom:6px}
|
||||
.d-item .body{white-space:pre-wrap;line-height:1.65;font-size:14.5px}
|
||||
.d-item .subj{font-weight:800;margin-bottom:5px}
|
||||
.d-copy{position:absolute;top:11px;right:11px}
|
||||
.d-ads{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:10px}
|
||||
.d-ad{border:1px solid var(--line);border-radius:10px;padding:11px 12px;background:var(--bg,#0b1d2e)}
|
||||
.d-ad .s{font-weight:800;font-size:15px;color:var(--gold);margin-bottom:4px;word-break:break-word}
|
||||
.d-ad .l{font-size:13px;line-height:1.5;word-break:break-word}
|
||||
.k-key{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;background:var(--bg,#0b1d2e);
|
||||
border:1px solid var(--gold);border-radius:8px;padding:10px 12px;word-break:break-all;margin-top:10px}
|
||||
.k-meta{color:var(--muted);font-size:12.5px;margin-top:8px;line-height:1.55}
|
||||
pre.k-doc{background:var(--bg,#0b1d2e);border:1px solid var(--line);border-radius:10px;padding:12px 14px;
|
||||
overflow-x:auto;font-size:12.5px;line-height:1.6;margin-top:10px}
|
||||
.spin{display:inline-block;width:13px;height:13px;border:2px solid rgba(255,255,255,.25);border-top-color:var(--gold);border-radius:50%;animation:sp .8s linear infinite;vertical-align:-2px;margin-right:7px}
|
||||
@keyframes sp{to{transform:rotate(360deg)}}
|
||||
@media (prefers-reduced-motion:reduce){.spin{animation:none}}
|
||||
.d-note{margin-top:22px;font-size:12.5px;color:var(--muted);line-height:1.6;border-top:1px solid var(--line);padding-top:12px}
|
||||
</style></head>
|
||||
<body>
|
||||
<header class="wrap nav"><a class="brand" href="/suite"><img class="brand-mark" src="/logo.jpg" alt="The RM Circle" width="42" height="42"><span><span id="brandName">RM Circle</span><small>Founder Desk</small></span></a><div class="nav-actions"><a class="btn btn-secondary hide-mobile" href="/suite">← The Suite</a><a class="btn btn-primary" href="/my">My Dashboard</a></div></header>
|
||||
<main class="dw">
|
||||
<div class="eyebrow" style="color:var(--gold)">CIRCLE SUITE · CORONA (LEVEL 8)</div>
|
||||
<h1>Founder <span class="gold">Desk.</span></h1>
|
||||
<p class="lede">Every tool below this builds one thing at a time, which is right when you are still finding your words. At this level the problem is different: you are running an organisation and you do not have an afternoon to spend clicking. So this builds the whole week at once.</p>
|
||||
|
||||
<div id="dGate"></div>
|
||||
|
||||
<div id="dBody" style="display:none">
|
||||
<div class="d-card">
|
||||
<h2>This week's campaign pack</h2>
|
||||
<div class="sub">Five social posts across five different angles, three outreach messages, an email for your list, and three text ads ready for the Traffic Desk — all carrying your link, all in one pass.</div>
|
||||
<div class="d-row">
|
||||
<button id="dGo" class="btn btn-primary">👑 Build my week</button>
|
||||
<span class="k-meta" id="dMeter" style="margin:0"></span>
|
||||
</div>
|
||||
<div id="dErr"></div>
|
||||
</div>
|
||||
|
||||
<div id="dPack"></div>
|
||||
|
||||
<div class="d-card" style="margin-top:24px">
|
||||
<h2>API access</h2>
|
||||
<div class="sub">A personal key for pulling your position, your organisation triage, and the writer into whatever you already run.</div>
|
||||
<div id="dKeyState"></div>
|
||||
<div class="d-row">
|
||||
<button id="dIssue" class="btn btn-secondary">Create a key</button>
|
||||
<button id="dRevoke" class="btn btn-secondary" style="display:none">Revoke it</button>
|
||||
</div>
|
||||
<pre class="k-doc">GET /api/suite/v1/me your position, level and link
|
||||
GET /api/suite/v1/team your organisation, triaged
|
||||
POST /api/suite/v1/generate {"kind":"post","brief":"..."}
|
||||
|
||||
curl -H "Authorization: Bearer YOUR_KEY" \
|
||||
https://rmcircle.team/api/suite/v1/me</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="d-note"><strong style="color:var(--text)">Everything here obeys the same rules as the rest of the Suite</strong> — no income claims, no invented figures, POL quantities only, Premium only. Generating a week does not post anything anywhere; it hands you the words and you decide what goes out. <br><br>Independent team resource · No income is guaranteed · Cryptocurrency involves risk.</p>
|
||||
</main>
|
||||
<script src="/suite-founder.js"></script>
|
||||
<script src="/chat.js" defer></script>
|
||||
<script src="/translate.js" defer></script><script src="/tg-app.js" defer></script><script src="/nav-dash.js" defer></script></body></html>
|
||||
@@ -0,0 +1,203 @@
|
||||
// Founder Desk client — builds the weekly pack and manages the API key.
|
||||
(function () {
|
||||
'use strict';
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
function esc(s) {
|
||||
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
|
||||
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
||||
});
|
||||
}
|
||||
|
||||
function gate(msg) {
|
||||
$('dGate').style.display = 'block';
|
||||
$('dGate').innerHTML = msg;
|
||||
}
|
||||
|
||||
function copyBtn(getText) {
|
||||
var b = document.createElement('button');
|
||||
b.className = 'btn btn-secondary btn-sm d-copy';
|
||||
b.textContent = 'Copy';
|
||||
b.addEventListener('click', function () {
|
||||
if (navigator.clipboard) navigator.clipboard.writeText(getText());
|
||||
b.textContent = 'Copied';
|
||||
setTimeout(function () { b.textContent = 'Copy'; }, 1400);
|
||||
});
|
||||
return b;
|
||||
}
|
||||
|
||||
function item(tag, subject, body) {
|
||||
var d = document.createElement('div');
|
||||
d.className = 'd-item';
|
||||
var html = '<div class="tag">' + esc(tag) + '</div>';
|
||||
if (subject) html += '<div class="subj">' + esc(subject) + '</div>';
|
||||
html += '<div class="body">' + esc(body) + '</div>';
|
||||
d.innerHTML = html;
|
||||
d.appendChild(copyBtn(function () { return (subject ? subject + '\n\n' : '') + body; }));
|
||||
return d;
|
||||
}
|
||||
|
||||
function section(title, why) {
|
||||
var s = document.createElement('div');
|
||||
s.className = 'd-sec';
|
||||
s.innerHTML = '<h3>' + esc(title) + '</h3><div class="why">' + esc(why) + '</div>';
|
||||
return s;
|
||||
}
|
||||
|
||||
function renderPack(pack) {
|
||||
var host = $('dPack');
|
||||
host.innerHTML = '';
|
||||
if (!pack) return;
|
||||
|
||||
if ((pack.posts || []).length) {
|
||||
var s1 = section('Social posts — one per angle',
|
||||
'Post one a day. They deliberately take different angles so the week does not read as the same message five times.');
|
||||
pack.posts.forEach(function (p) { s1.appendChild(item(p.angle, '', p.text)); });
|
||||
host.appendChild(s1);
|
||||
}
|
||||
|
||||
if ((pack.messages || []).length) {
|
||||
var s2 = section('Outreach messages',
|
||||
'For the conversations that actually build the team — a first approach, a follow-up for someone gone quiet, and the pyramid-scheme answer.');
|
||||
pack.messages.forEach(function (mm) { s2.appendChild(item(mm.label, '', mm.text)); });
|
||||
host.appendChild(s2);
|
||||
}
|
||||
|
||||
if ((pack.emails || []).length) {
|
||||
var s3 = section('Email for your list', 'Drop straight into your autoresponder.');
|
||||
pack.emails.forEach(function (em) { s3.appendChild(item('Email', em.subject, em.body)); });
|
||||
host.appendChild(s3);
|
||||
}
|
||||
|
||||
if ((pack.textAds || []).length) {
|
||||
var s4 = section('Text ads', 'Ready to run from the Traffic Desk.');
|
||||
var grid = document.createElement('div');
|
||||
grid.className = 'd-ads';
|
||||
pack.textAds.forEach(function (a) {
|
||||
var c = document.createElement('div');
|
||||
c.className = 'd-ad';
|
||||
var h = '<div class="s">' + esc(a.subject) + '</div>';
|
||||
(a.lines || []).forEach(function (l) { if (l) h += '<div class="l">' + esc(l) + '</div>'; });
|
||||
c.innerHTML = h;
|
||||
grid.appendChild(c);
|
||||
});
|
||||
s4.appendChild(grid);
|
||||
host.appendChild(s4);
|
||||
}
|
||||
|
||||
// Be straight about anything the engine could not produce, rather than
|
||||
// quietly handing over a short pack.
|
||||
if ((pack.problems || []).length) {
|
||||
var warn = document.createElement('div');
|
||||
warn.className = 'd-item';
|
||||
warn.style.borderColor = '#f0a05a';
|
||||
warn.innerHTML = '<div class="tag" style="color:#f0a05a">Incomplete</div><div class="body">' +
|
||||
'The engine could not finish: ' + esc(pack.problems.join(', ')) +
|
||||
'. Everything else above is fine — build again to retry the missing pieces.</div>';
|
||||
host.appendChild(warn);
|
||||
}
|
||||
}
|
||||
|
||||
function renderKey(info, freshKey) {
|
||||
var host = $('dKeyState');
|
||||
if (freshKey) {
|
||||
host.innerHTML = '<div class="k-meta"><b style="color:var(--gold)">Copy this now — it is shown once and never again.</b></div>' +
|
||||
'<div class="k-key" id="dFresh">' + esc(freshKey) + '</div>';
|
||||
var b = copyBtn(function () { return freshKey; });
|
||||
b.style.position = 'static';
|
||||
b.style.marginTop = '8px';
|
||||
host.appendChild(b);
|
||||
$('dIssue').textContent = 'Replace this key';
|
||||
$('dRevoke').style.display = '';
|
||||
return;
|
||||
}
|
||||
if (info && info.exists) {
|
||||
host.innerHTML = '<div class="k-meta">A key is active (<b>' + esc(info.hint) + '</b>), created ' +
|
||||
esc(String(info.createdAt || '').slice(0, 10)) +
|
||||
(info.lastUsedAt ? ', last used ' + esc(String(info.lastUsedAt).slice(0, 10)) : ', never used yet') +
|
||||
'. The key itself is stored hashed, so it cannot be shown again — create a new one if you have lost it.</div>';
|
||||
$('dIssue').textContent = 'Replace this key';
|
||||
$('dRevoke').style.display = '';
|
||||
} else {
|
||||
host.innerHTML = '<div class="k-meta">No key yet.</div>';
|
||||
$('dIssue').textContent = 'Create a key';
|
||||
$('dRevoke').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function build(btn) {
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spin"></span>Building your week — this takes a minute…';
|
||||
$('dErr').style.display = 'none';
|
||||
try {
|
||||
var r = await fetch('/api/public/suite-founder', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}'
|
||||
});
|
||||
var d = await r.json();
|
||||
if (!r.ok) {
|
||||
$('dErr').textContent = d.error || 'That did not build — try again.';
|
||||
$('dErr').style.display = 'block';
|
||||
} else {
|
||||
renderPack(d.pack);
|
||||
if (d.meter) $('dMeter').textContent = d.meter.remaining + ' of ' + d.meter.limit + ' packs left this month';
|
||||
}
|
||||
} catch (e) {
|
||||
$('dErr').textContent = 'Connection hiccup — try again.';
|
||||
$('dErr').style.display = 'block';
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '👑 Build my week';
|
||||
}
|
||||
|
||||
async function issue() {
|
||||
if (!window.confirm('Create a new API key? Any existing key stops working immediately.')) return;
|
||||
try {
|
||||
var r = await fetch('/api/public/suite-founder', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ issueKey: true })
|
||||
});
|
||||
var d = await r.json();
|
||||
if (r.ok) renderKey(d.info, d.key);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function revoke() {
|
||||
if (!window.confirm('Revoke your API key? Anything using it stops working.')) return;
|
||||
try {
|
||||
var r = await fetch('/api/public/suite-founder', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ revokeKey: true })
|
||||
});
|
||||
var d = await r.json();
|
||||
if (r.ok) renderKey(d.info, null);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function boot() {
|
||||
try {
|
||||
var r = await fetch('/api/public/suite-founder');
|
||||
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();
|
||||
$('dBody').style.display = 'block';
|
||||
renderPack(d.pack);
|
||||
renderKey(d.key, null);
|
||||
if (d.meter) $('dMeter').textContent = d.meter.remaining + ' of ' + d.meter.limit + ' packs left this month';
|
||||
if (d.writerReady === false) {
|
||||
$('dGo').disabled = true;
|
||||
$('dGo').textContent = 'The writer is warming up';
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
boot();
|
||||
$('dGo').addEventListener('click', function () { build(this); });
|
||||
$('dIssue').addEventListener('click', issue);
|
||||
$('dRevoke').addEventListener('click', revoke);
|
||||
});
|
||||
})();
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
{ 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: 'A separate hosted landing page for every audience you talk to — each on its own address, each one something you can point a different ad at.', href: '/suite/funnel', live: true },
|
||||
{ lv: 7, ico: '🧭', name: 'Leader Ops', desc: 'Everyone below you, read live from the contract and sorted by who needs you most — plus a written coaching plan you can teach forward to your own two.', href: '/suite/leader', live: true },
|
||||
{ lv: 8, ico: '👑', name: 'Founder Desk', desc: 'Every tool in the Suite at its highest allowance, and the top of the ladder — nothing above this to unlock.', live: false }
|
||||
{ lv: 8, ico: '👑', name: 'Founder Desk', desc: 'A whole week of promotion built in one pass — posts, outreach messages, an email and text ads — plus an API key to pull your position, your organisation and the writer into anything you already run.', href: '/suite/founder', live: true }
|
||||
];
|
||||
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
|
||||
Reference in New Issue
Block a user