a57af32f4a
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>
145 lines
5.3 KiB
JavaScript
145 lines
5.3 KiB
JavaScript
// 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);
|
||
});
|
||
})();
|