Co-Branded Pages: give Fastigium a change of kind, not a bigger number

Once every member from Ascensus up could hold several pages, the Funnel
Factory's differentiator became a count — the same quantity-not-kind flaw the
upper tiers had. L6 now unlocks identity instead: a member's own name,
monogram and accent colour on every page they build. Below Fastigium you have
a page on the team site; at Fastigium it becomes yours.

Three deliberate limits.

NO IMAGE UPLOADS. A logo file uploaded by a member and served from
rmcircle.team is an abuse surface — whatever they upload lives on our domain.
The mark is a monogram generated from their brand name instead: real identity,
nothing to moderate. Same reasoning that keeps Traffic Desk creative
team-only.

ACCENTS ARE A CURATED SET, not a colour picker. Every option is checked
against the dark ground, so nobody can pick something that makes their own
page unreadable and then wonder why it converts badly.

THE DISCLAIMERS ARE NOT THEIRS TO BRAND. Branding replaces the header
identity only; the footer — independent team resource, no income guaranteed,
cryptocurrency risk — is rendered by us and cannot be removed or restyled.
Co-branding, not white-labelling. Verified in the renderer.

Branding is resolved against the LIVE contract level at render time, so a
position that drops below Fastigium reverts to team identity on its own
rather than keeping something it no longer holds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-29 06:14:28 -05:00
parent c8ae4d55b0
commit a57af32f4a
7 changed files with 382 additions and 4 deletions
+144
View File
@@ -0,0 +1,144 @@
// Co-Branded Pages client. Everything previews live, because the whole value of
// this tool is seeing what your pages will look like before you commit.
(function () {
'use strict';
var $ = function (id) { return document.getElementById(id); };
var state = { accents: [], accent: 'gold' };
function gate(msg) {
$('bGate').style.display = 'block';
$('bGate').innerHTML = msg;
}
function hexFor(key) {
var a = state.accents.filter(function (x) { return x.key === key; })[0];
return a ? a.hex : '#f3be43';
}
// Same rule the server uses, so the preview cannot disagree with what gets
// saved: initials of the first two words, or the first two characters.
function monogram(name) {
var clean = String(name || '').replace(/[^A-Za-z0-9 ]+/g, ' ').trim();
if (!clean) return '';
var w = clean.split(/\s+/).filter(Boolean);
if (w.length >= 2) return (w[0][0] + w[1][0]).toUpperCase();
return w[0].slice(0, 2).toUpperCase();
}
function renderSwatches() {
var host = $('bSwatches');
host.innerHTML = '';
state.accents.forEach(function (a) {
var b = document.createElement('button');
b.type = 'button';
b.className = 'b-sw' + (a.key === state.accent ? ' on' : '');
b.style.background = a.hex;
b.title = a.label;
b.setAttribute('aria-label', a.label);
b.addEventListener('click', function () { state.accent = a.key; renderSwatches(); preview(); });
host.appendChild(b);
});
}
function preview() {
var name = $('bName').value.trim();
var tag = $('bTag').value.trim();
var hex = hexFor(state.accent);
var mono = monogram(name);
$('bMono').textContent = mono || '—';
$('bMono').style.color = hex;
$('bMono').style.border = '2px solid ' + hex;
$('bMono').style.background = hex + '22';
$('bPrevName').textContent = name || 'Your brand';
$('bPrevTag').textContent = tag || 'A personal invitation';
$('bPrevGold').style.color = hex;
var btn = $('bPrevBtn');
btn.style.background = hex;
btn.style.borderColor = hex;
btn.style.color = '#071421';
}
async function save() {
$('bErr').style.display = 'none';
$('bOk').style.display = 'none';
var name = $('bName').value.trim();
if (name.length < 2) {
$('bErr').textContent = 'Give your brand a name — two characters or more.';
$('bErr').style.display = 'block';
return;
}
$('bSave').disabled = true;
try {
var r = await fetch('/api/public/suite-brand', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ brandName: name, tagline: $('bTag').value.trim(), accent: state.accent })
});
var d = await r.json();
if (!r.ok) {
$('bErr').textContent = d.error || 'That did not save.';
$('bErr').style.display = 'block';
} else {
$('bOk').innerHTML = '✅ <b>Saved.</b> Every page you have built now carries your branding — ' +
'<a href="/suite/page" style="color:var(--gold);font-weight:700">go and look at one</a>.';
$('bOk').style.display = 'block';
}
} catch (e) {
$('bErr').textContent = 'Connection hiccup — try again.';
$('bErr').style.display = 'block';
}
$('bSave').disabled = false;
}
async function clearBrand() {
if (!window.confirm('Go back to team branding on all your pages?')) return;
try {
var r = await fetch('/api/public/suite-brand', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ clear: true })
});
var d = await r.json();
if (r.ok) {
$('bName').value = ''; $('bTag').value = '';
state.accent = 'gold';
renderSwatches(); preview();
$('bOk').innerHTML = 'Back to team branding on all your pages.';
$('bOk').style.display = 'block';
}
} catch (e) {}
}
async function boot() {
try {
var r = await fetch('/api/public/suite-brand');
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.accents = d.accents || [];
var b = d.brand || {};
state.accent = b.accent || 'gold';
$('bName').value = b.brandName || '';
$('bTag').value = b.tagline || '';
$('bBody').style.display = 'block';
renderSwatches();
preview();
if (!(d.pages || []).length) {
$('bOk').innerHTML = 'You have no pages yet — <a href="/suite/page" style="color:var(--gold);font-weight:700">build one</a> and your branding will be on it.';
$('bOk').style.display = 'block';
}
} catch (e) {}
}
document.addEventListener('DOMContentLoaded', function () {
boot();
$('bName').addEventListener('input', preview);
$('bTag').addEventListener('input', preview);
$('bSave').addEventListener('click', save);
$('bClear').addEventListener('click', clearBrand);
});
})();