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:
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user