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:
@@ -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="Co-Branded Pages — put your own name, monogram and colour on every page you build."><title>Co-Branded Pages | The Circle Suite</title><link rel="icon" type="image/png" href="/favicon.png"><link rel="stylesheet" href="/styles.css">
|
||||
<style>
|
||||
.bw{max-width:800px;margin:0 auto;padding:24px 20px 70px}
|
||||
.bw h1{font-size:clamp(26px,4.5vw,36px);margin:6px 0 8px}
|
||||
.bw .lede{color:var(--muted);line-height:1.6;margin-bottom:18px}
|
||||
#bGate{display:none;border:2px solid var(--gold);border-radius:12px;padding:16px 20px;margin-bottom:18px;line-height:1.6}
|
||||
.b-card{background:var(--card,#0d2236);border:1px solid var(--line,#24425d);border-radius:14px;padding:18px 20px;margin-bottom:16px}
|
||||
label.b-lab{display:block;font-weight:700;font-size:14px;margin:16px 0 5px}
|
||||
label.b-lab:first-of-type{margin-top:0}
|
||||
.b-hint{color:var(--muted);font-size:12.5px;line-height:1.5;margin-bottom:7px}
|
||||
.b-card input[type=text]{width:100%;background:var(--bg,#0b1d2e);border:1px solid var(--line);border-radius:10px;
|
||||
color:var(--text);padding:11px 13px;font:inherit;font-size:15px}
|
||||
.b-swatches{display:flex;flex-wrap:wrap;gap:9px}
|
||||
.b-sw{width:44px;height:44px;border-radius:50%;border:3px solid transparent;cursor:pointer;padding:0;
|
||||
box-shadow:0 0 0 1px var(--line) inset}
|
||||
.b-sw.on{border-color:var(--text)}
|
||||
.b-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:18px}
|
||||
#bErr{display:none;color:#f0a05a;margin-top:12px;font-size:14px;line-height:1.55}
|
||||
#bOk{display:none;border:2px solid var(--teal);border-radius:12px;padding:14px 18px;margin-top:14px;line-height:1.6}
|
||||
.b-prev{border:1px solid var(--line);border-radius:14px;overflow:hidden;margin-top:6px}
|
||||
.b-prevbar{display:flex;align-items:center;gap:11px;padding:14px 18px;background:#071421}
|
||||
.b-mono{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:50%;
|
||||
font-weight:900;font-size:16px;letter-spacing:.5px}
|
||||
.b-prevbar .nm{font-weight:800;font-size:16px;line-height:1.2}
|
||||
.b-prevbar .tg{font-size:12px;color:var(--muted)}
|
||||
.b-prevbody{padding:16px 18px;background:#071421;border-top:1px solid var(--line)}
|
||||
.b-prevbody h3{font-size:20px;margin:0 0 6px}
|
||||
.b-prevbody .btn{pointer-events:none;margin-top:10px}
|
||||
.b-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>Co-Branded Pages</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="/suite/page">My pages</a></div></header>
|
||||
<main class="bw">
|
||||
<div class="eyebrow" style="color:var(--gold)">CIRCLE SUITE · FASTIGIUM (LEVEL 6)</div>
|
||||
<h1>Co-Branded <span class="gold">Pages.</span></h1>
|
||||
<p class="lede">Every page you build carries the team's identity by default. From Fastigium it carries yours instead — your name, your monogram, your colour — so what you send people stops looking like a page on someone else's site.</p>
|
||||
|
||||
<div id="bGate"></div>
|
||||
|
||||
<div id="bBody" style="display:none">
|
||||
<div class="b-card">
|
||||
<label class="b-lab" for="bName">Your brand name</label>
|
||||
<div class="b-hint">What people should see at the top of your pages. Your own name works perfectly — "Marty Bostick" is a brand.</div>
|
||||
<input type="text" id="bName" maxlength="32" placeholder="Bostick Builds">
|
||||
|
||||
<label class="b-lab" for="bTag">One line under it</label>
|
||||
<div class="b-hint">Optional. A short line about what you do, not a slogan.</div>
|
||||
<input type="text" id="bTag" maxlength="60" placeholder="Real crypto, real people">
|
||||
|
||||
<label class="b-lab">Your colour</label>
|
||||
<div class="b-hint">Replaces the gold on your pages. Every option here is checked to stay readable on the dark background.</div>
|
||||
<div class="b-swatches" id="bSwatches"></div>
|
||||
|
||||
<label class="b-lab">How it will look</label>
|
||||
<div class="b-prev" id="bPrev">
|
||||
<div class="b-prevbar"><span class="b-mono" id="bMono">—</span>
|
||||
<span><div class="nm" id="bPrevName">Your brand</div><div class="tg" id="bPrevTag">A personal invitation</div></span></div>
|
||||
<div class="b-prevbody">
|
||||
<h3 id="bPrevHead">You already know two people. <span id="bPrevGold">That's the whole job.</span></h3>
|
||||
<span class="btn btn-primary" id="bPrevBtn">Join you →</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="b-row">
|
||||
<button id="bSave" class="btn btn-primary">Save my branding</button>
|
||||
<button id="bClear" class="btn btn-secondary">Use team branding</button>
|
||||
</div>
|
||||
<div id="bErr"></div>
|
||||
<div id="bOk"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="b-note"><strong style="color:var(--text)">What branding does not change:</strong> the disclaimers at the foot of every page — independent team resource, no income guaranteed, cryptocurrency involves risk — are ours and stay exactly as they are. This is co-branding, not white-labelling; anyone reading your page can still see what they're looking at. <br><br>There's no logo upload on purpose: your monogram is generated from your brand name, so there's no image to moderate and nothing of yours hosted on our domain.</p>
|
||||
</main>
|
||||
<script src="/suite-brand.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,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);
|
||||
});
|
||||
})();
|
||||
@@ -20,6 +20,7 @@
|
||||
{ lv: 4, ico: '🗣️', name: 'Voice Profile', desc: 'Teach the Copy Engine and Email Engine how you actually talk, so everything they write for you stops sounding like a company.', href: '/suite/voice', live: true },
|
||||
{ lv: 5, ico: '🔬', name: 'Split Tester', desc: 'Run two or three versions of an ad head to head and find out which wording people actually click. Apex is where your impressions jump to 50,000 — enough for the answer to mean something.', href: '/suite/split', live: true },
|
||||
{ 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: 'Co-Branded Pages', desc: 'Your own name, monogram and colour on every page you build — so it stops looking like a page on the team site and starts looking like yours.', href: '/suite/brand', 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: 'Team Grants', desc: 'Hand Suite capacity down into your own organisation — impressions, generations, page builds. Your level stops being something you bought for yourself and becomes something you give your team.', href: '/suite/grants', live: true },
|
||||
{ lv: 8, ico: '🛰️', name: 'Network Intelligence', desc: 'What is actually working across every ad the whole team runs — best angles, headlines and creative, pooled and anonymised. Nobody can buy this; it is the team\'s own accumulated result, and it compounds every month.', href: '/suite/intel', live: true },
|
||||
|
||||
Reference in New Issue
Block a user