Page Builder ships: labelled-section AI output parsed deterministically, escaped renderer, one page per position at public /p/<id> with angle video + QR, rebuild keeps the same URL; L2 tile live

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-28 07:15:11 -05:00
parent 2a8b311a83
commit 6a7867588d
6 changed files with 389 additions and 8 deletions
+75
View File
@@ -0,0 +1,75 @@
<!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="Page Builder — your own personal invitation page, written for you and hosted on the team site."><title>Page Builder | The Circle Suite</title><link rel="icon" type="image/png" href="/favicon.png"><link rel="stylesheet" href="/styles.css">
<style>
.pw{max-width:880px;margin:0 auto;padding:24px 20px 70px}
.pw h1{font-size:clamp(26px,4.5vw,36px);margin:6px 0 8px}
.pw .lede{color:var(--muted);line-height:1.6;margin-bottom:18px}
#pgGate{display:none;border:2px solid var(--gold);border-radius:12px;padding:16px 20px;margin-bottom:18px;line-height:1.6}
#pgGate b{color:var(--gold)}
.pg-card{background:var(--card,#0d2236);border:1px solid var(--line,#24425d);border-radius:14px;padding:18px 20px;margin-bottom:16px}
label.pg-lab{display:block;font-weight:700;font-size:14px;margin:14px 0 6px}
label.pg-lab:first-of-type{margin-top:0}
.pg-card input[type=text],.pg-card textarea{width:100%;background:var(--bg,#0b1d2e);border:1px solid var(--line);
border-radius:10px;color:var(--text);padding:11px 13px;font-size:15px;font-family:inherit;line-height:1.5}
.pg-card textarea{min-height:88px;resize:vertical}
.pg-hint{color:var(--muted);font-size:12.5px;margin-top:6px}
.pg-angles{display:flex;flex-wrap:wrap;gap:8px}
.pg-angle{border:1px solid var(--line);background:transparent;color:var(--muted);border-radius:999px;
padding:7px 14px;font-size:13.5px;font-weight:700;cursor:pointer}
.pg-angle.on{border-color:var(--teal);background:rgba(78,214,203,.12);color:var(--teal)}
.pg-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-top:16px}
#pgMeter{font-size:12.5px;color:var(--muted);margin-left:auto}
#pgMeter b{color:var(--text)}
#pgErr{display:none;color:#f0a05a;margin-top:12px;font-size:14px;line-height:1.55}
#pgLive{display:none;border:2px solid var(--teal);border-radius:14px;padding:16px 20px;margin-bottom:16px}
#pgLive .u{font-family:ui-monospace,monospace;font-size:15px;color:var(--gold);font-weight:700;word-break:break-all}
#pgPrev{display:none;margin-top:16px}
#pgFrame{width:100%;height:560px;border:1px solid var(--line);border-radius:12px;background:#0b1d2e}
.pg-note{margin-top:20px;font-size:12.5px;color:var(--muted);line-height:1.6;border-top:1px solid var(--line);padding-top:12px}
.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}}
</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>Page Builder</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/copy">Copy Engine</a></div></header>
<main class="pw">
<div class="eyebrow" style="color:var(--gold)">CIRCLE SUITE · LEVEL 2 · ASCENSUS</div>
<h1>Page <span class="gold">Builder.</span></h1>
<p class="lede">Answer four questions and you get your own invitation page — your name, your story, your angle video, your QR code — hosted on the team site at a link you can send anywhere.</p>
<div id="pgGate"></div>
<div id="pgLive">
<div style="font-weight:700;margin-bottom:6px">✅ Your page is live</div>
<div class="u" id="pgUrl"></div>
<div class="pg-row"><a class="btn btn-teal btn-sm" id="pgOpen" target="_blank" rel="noopener">Open it →</a><button class="btn btn-secondary btn-sm" id="pgCopyUrl">📋 Copy link</button><span class="pg-hint" id="pgCopied" style="display:none;color:var(--teal)">Copied.</span></div>
</div>
<div class="pg-card" id="pgCard">
<label class="pg-lab" for="pgName">What should people call you?</label>
<input type="text" id="pgName" maxlength="60" placeholder="e.g. Marty B.">
<label class="pg-lab">Which angle fits you best?</label>
<div class="pg-angles" id="pgAngles"></div>
<div class="pg-hint">This picks the short video that plays on your page.</div>
<label class="pg-lab" for="pgAudience">Who are you talking to?</label>
<input type="text" id="pgAudience" maxlength="300" placeholder="e.g. friends who work too many hours and are curious but skeptical about crypto">
<label class="pg-lab" for="pgStory">In your own words — why are you doing this, and what do you want them to know?</label>
<textarea id="pgStory" maxlength="1200" placeholder="Doesn't have to be polished. A few honest sentences is plenty — the writer turns it into the page."></textarea>
<div class="pg-hint">Write it messy. Real beats perfect, and it gets rewritten in your voice either way.</div>
<div class="pg-row">
<button id="pgGo" class="btn btn-primary">🧱 Build my page</button>
<span id="pgMeter"></span>
</div>
<div id="pgErr"></div>
</div>
<div id="pgPrev">
<div style="font-weight:700;margin-bottom:8px">Preview</div>
<iframe id="pgFrame" title="Your page preview"></iframe>
</div>
<p class="pg-note"><strong style="color:var(--text)">Teach it forward:</strong> when your two join, walk them through building theirs — a personal page beats a bare link every time. <br>Rebuilding replaces your page at the same link, so anything you've already shared keeps working. Independent team resource · No income is guaranteed · Cryptocurrency involves risk.</p>
</main>
<script src="/suite-page.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>
+118
View File
@@ -0,0 +1,118 @@
// Page Builder client. Loads the member's existing page (if any), takes a
// four-question brief, and rebuilds at the same /p/<id> URL so shared links
// never break.
(function () {
'use strict';
var $ = function (id) { return document.getElementById(id); };
var angle = 'overview', busy = false, myId = null;
function renderAngles(angles) {
var host = $('pgAngles');
host.innerHTML = '';
Object.keys(angles).forEach(function (k) {
var b = document.createElement('button');
b.type = 'button';
b.className = 'pg-angle' + (k === angle ? ' on' : '');
b.textContent = angles[k].label;
b.addEventListener('click', function () { angle = k; renderAngles(angles); });
host.appendChild(b);
});
}
function showMeter(m) {
if (!m || !m.limit) { $('pgMeter').textContent = ''; return; }
$('pgMeter').innerHTML = '<b>' + m.remaining + '</b> of ' + m.limit + ' page builds left this month';
}
function showLive(url) {
$('pgUrl').textContent = url.replace(/^https:\/\//, '');
$('pgOpen').href = url;
$('pgLive').style.display = 'block';
$('pgFrame').src = url + '?preview=1';
$('pgPrev').style.display = 'block';
$('pgGo').textContent = '🔁 Rebuild my page';
}
function gate(msg) {
var g = $('pgGate');
g.style.display = 'block';
g.innerHTML = msg;
$('pgCard').style.opacity = '.55';
$('pgGo').disabled = true;
}
async function boot() {
try {
var r = await fetch('/api/public/suite-page');
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.');
renderAngles({ overview: { label: 'General overview' } });
return;
}
if (r.status === 403) { gate('The Page Builder isn’t 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();
myId = d.id;
renderAngles(d.angles || {});
if (d.meter && d.meter.reason === 'locked') {
gate('The Page Builder unlocks at <b>Ascensus (level 2)</b>. You’re at level ' + d.level + ' — your next upgrade opens it, along with the Copy Engine. <a href="/how-pay-works#exact-costs" style="color:var(--gold);font-weight:700">See what an upgrade costs →</a>');
return;
}
showMeter(d.meter);
if (d.page) {
if (d.page.name) $('pgName').value = d.page.name;
if (d.page.audience) $('pgAudience').value = d.page.audience;
if (d.page.story) $('pgStory').value = d.page.story;
if (d.page.angle) { angle = d.page.angle; renderAngles(d.angles || {}); }
showLive('https://rmcircle.team/p/' + d.id);
}
} catch (e) {}
}
async function run() {
if (busy) return;
busy = true;
$('pgErr').style.display = 'none';
$('pgGo').disabled = true;
var label = $('pgGo').textContent;
$('pgGo').innerHTML = '<span class="spin"></span>Writing your page… (up to a minute)';
try {
var r = await fetch('/api/public/suite-page', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: $('pgName').value.trim(),
audience: $('pgAudience').value.trim(),
story: $('pgStory').value.trim(),
angle: angle
})
});
var d = await r.json();
if (!r.ok) {
$('pgErr').textContent = d.error || 'That didn’t go through — try again.';
$('pgErr').style.display = 'block';
if (d.meter) showMeter(d.meter);
} else {
showMeter(d.meter);
showLive(d.url);
$('pgLive').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
} catch (e) {
$('pgErr').textContent = 'Connection hiccup — try again.';
$('pgErr').style.display = 'block';
}
busy = false;
$('pgGo').disabled = false;
$('pgGo').textContent = label.indexOf('Rebuild') !== -1 ? '🔁 Rebuild my page' : '🧱 Build my page';
}
document.addEventListener('DOMContentLoaded', function () {
boot();
$('pgGo').addEventListener('click', run);
$('pgCopyUrl').addEventListener('click', function () {
navigator.clipboard.writeText($('pgOpen').href).then(function () {
$('pgCopied').style.display = '';
setTimeout(function () { $('pgCopied').style.display = 'none'; }, 2500);
});
});
});
})();
+1 -1
View File
@@ -14,7 +14,7 @@
{ 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 five questions, get your own hosted bridge page — your story, your angle video, your QR.', live: false },
{ 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: 'Follow-up sequences and broadcasts in the team voice, exportable to any autoresponder.', live: false },
{ 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.', live: false },
{ lv: 4, ico: '🗣️', name: 'Voice Profile + Funnels', desc: 'Output that sounds like YOU, plus multi-page funnels on your own team subdomain.', live: false },