simbiot-harness/sites/shkor-com/v2/account/index.html
2026-10-18 19:57:19 +00:00

257 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Your account — Shkor Labs</title>
<meta name="robots" content="noindex">
<meta name="referrer" content="no-referrer">
<meta name="theme-color" content="#050506">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><rect width='64' height='64' rx='14' fill='%23050506'/><circle cx='32' cy='32' r='20' fill='none' stroke='%23c6ff4d' stroke-width='3' stroke-dasharray='4 6'/><circle cx='32' cy='32' r='7' fill='%23c6ff4d'/></svg>">
<link rel="stylesheet" href="../styles.css">
<link rel="stylesheet" href="../apply/apply.css">
<style>
.acc{position:relative;max-width:900px}
.acc h1{margin-bottom:6px}
.lvl{display:inline-flex;gap:8px;align-items:center;font:12px/1 var(--mono);letter-spacing:.1em;text-transform:uppercase;padding:8px 12px;border-radius:999px;color:#0a0d02;background:var(--acid);vertical-align:middle}
.panel{margin-top:28px;padding:28px;border-radius:20px;background:linear-gradient(180deg,var(--bg-2),var(--bg-1));box-shadow:inset 0 0 0 1px var(--line-2)}
.panel h2{font:700 24px/1.2 var(--display);color:var(--fg);letter-spacing:-.02em;margin-bottom:8px}
.panel>p{color:var(--fg-3);font-size:15px}
.creds{margin-top:14px;padding:14px 16px;border-radius:12px;background:rgba(255,255,255,.035);box-shadow:inset 0 0 0 1px var(--line-2);font:14px/1.9 var(--mono);color:var(--fg-2);word-break:break-all}
.creds b{color:var(--fg);font-weight:400}
.gives{display:grid;gap:10px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));margin:16px 0 4px}
.gives div{padding:14px 16px;border-radius:12px;background:rgba(198,255,77,.05);box-shadow:inset 0 0 0 1px rgba(198,255,77,.18);font-size:14px;color:var(--fg-2)}
.gives b{display:block;color:var(--fg);font-size:15px;margin-bottom:2px}
.step{margin-top:14px;border-radius:16px;box-shadow:inset 0 0 0 1px var(--line-2);overflow:hidden}
.step>summary{list-style:none;cursor:pointer;display:grid;grid-template-columns:28px 1fr auto;gap:14px;align-items:center;padding:16px 18px}
.step>summary::-webkit-details-marker{display:none}
.step .dot{width:24px;height:24px;border-radius:50%;box-shadow:inset 0 0 0 2px var(--line-2);display:grid;place-items:center;font:12px var(--mono);color:var(--fg-3)}
.step.done .dot{background:var(--acid);box-shadow:none;color:#0a0d02}
.step summary b{color:var(--fg);font:600 17px/1.3 var(--display)}
.step summary small{display:block;color:var(--fg-3);font-size:13px;margin-top:2px}
.step .body{padding:4px 18px 20px 60px}
@media(max-width:600px){.step .body{padding:4px 16px 18px}}
.bar{height:8px;border-radius:8px;background:var(--line-2);overflow:hidden;margin:8px 0 14px}.bar i{display:block;height:100%;background:var(--acid)}
.prod{padding:14px 16px;border-radius:12px;background:rgba(255,255,255,.03);box-shadow:inset 0 0 0 1px var(--line-2);margin:10px 0}
.prod b{color:var(--fg)}
.share{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
.share a,.share button{font:13px var(--body);padding:8px 12px;border-radius:10px;border:0;cursor:pointer;color:var(--fg);background:rgba(255,255,255,.06);text-decoration:none}
.share a:hover,.share button:hover{background:rgba(198,255,77,.15)}
.row3{display:grid;gap:0 12px;grid-template-columns:1fr}
@media(min-width:640px){.row3{grid-template-columns:2fr 1fr 1fr}}
.ok-msg{color:var(--acid);font-size:14px;min-height:20px;margin-top:10px}
.muted{color:var(--fg-3);font-size:14px}
.topbar{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}
.chat{display:flex;flex-direction:column;gap:10px;max-height:420px;overflow-y:auto;margin-top:14px;padding:14px;border-radius:14px;background:rgba(0,0,0,.25);box-shadow:inset 0 0 0 1px var(--line-2)}
.msg{max-width:82%;padding:10px 14px;border-radius:14px;font-size:15px;line-height:1.5;white-space:pre-wrap;word-wrap:break-word;color:var(--fg)}
.msg.me{align-self:flex-end;background:rgba(198,255,77,.14);box-shadow:inset 0 0 0 1px rgba(198,255,77,.3)}
.msg.ag{align-self:flex-start;background:rgba(255,255,255,.05);box-shadow:inset 0 0 0 1px var(--line-2)}
.msg small{display:block;margin-top:4px;font:11px var(--mono);color:var(--fg-3)}
.chat-in{display:flex;gap:10px;margin-top:12px}.chat-in textarea{flex:1;min-height:50px;max-height:160px;resize:vertical;padding:12px 14px;border:0;border-radius:12px;color:var(--fg);font:15px/1.5 var(--body);background:rgba(255,255,255,.035);box-shadow:inset 0 0 0 1px var(--line-2)}
.chat-in textarea:focus{outline:none;box-shadow:inset 0 0 0 1px var(--acid)}
</style>
</head>
<body>
<header class="nav">
<div class="wrap nav-in">
<a class="brand" href="../" aria-label="Shkor Labs — home">
<svg viewBox="0 0 32 32" aria-hidden="true"><circle cx="16" cy="16" r="12" fill="none" stroke="currentColor" stroke-width="2" stroke-dasharray="3 4"/><circle cx="16" cy="16" r="4.5" fill="currentColor"/></svg>
<span>Shkor<b>Labs</b></span>
</a>
<nav aria-label="Account"><button class="btn btn-sm btn-ghost" id="logout" hidden type="button">Sign out</button></nav>
</div>
</header>
<main id="main" class="apply">
<div class="grid-bg" aria-hidden="true"></div>
<div class="wrap acc" id="app"><p class="kicker">Your account</p><h1>Loading…</h1></div>
</main>
<script>
(function () {
var app = document.getElementById('app'), q = new URLSearchParams(location.search), S = null;
var ARROW = '<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M4 10h11M11 5l5 5-5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
var DEC = [['d1_mission', 'Mission', 'Why this project exists.'], ['d2_vision', 'Vision', 'What the world looks like when you win.'],
['d3_market', 'Market', 'Who exactly you sell to.'], ['d4_need', 'Need', 'The pain or job they hire you for.'],
['d5_principle', 'Principle', 'The rule you won’t break.'], ['d6_product', 'Product', 'What you sell and how it solves the need.'],
['d7_tech', 'Technology', 'How it is built and run (agents, stack, data).']];
function esc(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]; }); }
function money(v) { return '$' + Number(v || 0).toFixed(2); }
function api(path, body) {
return fetch(path, body ? { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), credentials: 'same-origin' } : { cache: 'no-store', credentials: 'same-origin' })
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, status: r.status, j: j }; }); });
}
function loginView(msg) {
document.getElementById('logout').hidden = true;
app.innerHTML = '<p class="kicker">Your account</p><h1>Sign in</h1><p class="lead">We’ll email you a one-time sign-in link. Use the email you started with.</p>'
+ '<form id="lf" class="panel" style="max-width:520px" novalidate><div class="field"><label for="le">Email</label><input id="le" type="email" autocomplete="email" required maxlength="254"></div>'
+ '<button class="btn" type="submit"><span>Email me a link</span>' + ARROW + '</button><p class="form-err" id="lerr">' + esc(msg || '') + '</p></form>'
+ '<p class="muted" style="margin-top:20px">No account yet? <a href="../start/">Start your autopilot</a>.</p>';
document.getElementById('lf').addEventListener('submit', function (e) {
e.preventDefault();
var em = document.getElementById('le').value.trim(), err = document.getElementById('lerr');
if (!/^[^@\s]+@[^@\s]+\.[A-Za-z]{2,}$/.test(em)) { err.textContent = 'Please enter a valid email.'; return; }
api('/api/account/login', { email: em }).then(function (r) {
if (!r.ok) { err.textContent = r.j.error || 'Something went wrong.'; return; }
app.innerHTML = '<p class="kicker">Your account</p><h1>Check your inbox</h1><p class="lead">If ' + esc(em) + ' has a Shkor Labs account, a sign-in link is on its way. It works once, for 30 minutes.</p>';
});
});
}
function stepHTML(i, key, done, title, sub, body, open) {
return '<details class="step' + (done ? ' done' : '') + '"' + (open ? ' open' : '') + ' id="st-' + key + '"><summary><span class="dot">' + (done ? '✓' : i) + '</span><div><b>' + title + '</b><small>' + sub + '</small></div><span class="muted">' + (done ? 'Done' : '') + '</span></summary><div class="body">' + body + '</div></details>';
}
function shareHTML(url, text) {
var u = encodeURIComponent(url), t = encodeURIComponent(text);
return '<div class="share">'
+ '<a target="_blank" rel="noopener" href="https://twitter.com/intent/tweet?text=' + t + '&url=' + u + '">Share on X</a>'
+ '<a target="_blank" rel="noopener" href="https://www.linkedin.com/sharing/share-offsite/?url=' + u + '">LinkedIn</a>'
+ '<a target="_blank" rel="noopener" href="https://t.me/share/url?url=' + u + '&text=' + t + '">Telegram</a>'
+ '<a target="_blank" rel="noopener" href="https://www.facebook.com/sharer/sharer.php?u=' + u + '">Facebook</a>'
+ '<button type="button" data-copy="' + esc(url) + '">Copy link</button></div>';
}
function render() {
var d = S, c = d.cabinet, first = (d.name || '').split(' ')[0];
document.getElementById('logout').hidden = false;
var h = '<div class="topbar"><div><p class="kicker">Your account</p><h1>Hi, ' + esc(first) + ' <span class="lvl">Level ' + (c ? c.level : 1) + (c && c.level === 2 ? ' · Studio builder' : ' · Community') + '</span></h1></div></div>';
if (d.stage !== 'ready' || !c) {
var msg = { kyc: 'Finish identity verification to continue.', pay: 'Verified — one step left: payment.', declined: 'Identity verification was not approved.', deploying: 'Your workspace is deploying — usually 15–40 minutes.' }[d.stage] || '';
h += '<div class="panel"><h2>Getting started</h2><p>' + esc(msg) + '</p><a class="btn" style="margin-top:14px" href="../start/status/?t=' + encodeURIComponent(d.token) + '"><span>Open setup status</span>' + ARROW + '</a></div>';
app.innerHTML = h; return;
}
// workspace
if (d.panel) h += '<div class="panel"><h2>Your workspace</h2><p>Claude Code (your own Claude account) and OpenCode, both connected to the lab MCP.</p><div class="creds">Panel: <b><a href="' + esc(d.panel.url) + '" target="_blank" rel="noopener">' + esc(d.panel.url) + '</a></b><br>Login: <b>' + esc(d.panel.user) + '</b> · Password: <b>' + esc(d.panel.password) + '</b></div></div>';
// chat with the agent (same channel as the portal chat; the agent answers within ~1 min)
h += '<div class="panel"><h2>Chat with your agent</h2><p>Your Simbiot agent runs on your server around the clock. Ask it anything or give it a task — it usually replies within a minute.</p>'
+ '<div class="chat" id="chat"><p class="muted">Loading…</p></div>'
+ '<form class="chat-in" id="cf"><textarea id="ct" maxlength="8000" placeholder="Message your agent… (Enter to send, Shift+Enter for a new line)"></textarea><button class="btn" type="submit"><span>Send</span></button></form><p class="form-err" id="cerr"></p></div>';
// earnings
var e = c.earnings || {};
h += '<div class="panel"><h2>Earnings <span class="pill ' + (e.available > 0 ? 'ok' : 'wip') + '">' + money(e.available) + ' available</span></h2><p>Sales through the cooperative’s payments. The studio share is withheld automatically at each payment.</p>'
+ '<div class="creds">Sales: <b>' + money(e.gross) + '</b> · Studio share: <b>' + money(e.studio_share) + '</b> · Yours: <b>' + money(e.net) + '</b> · Paid out: <b>' + money(e.paid_out) + '</b></div>'
+ '<p class="muted" style="margin-top:10px">Withdrawals are coming soon — your balance is already tracked.</p></div>';
// level 2
var st = {}; (c.steps || []).forEach(function (s) { st[s.key] = s.done; });
var firstOpen = ['agreement', 'project', 'product', 'traction'].filter(function (k) { return !st[k]; })[0];
h += '<div class="panel"><h2>' + (c.level === 2 ? 'You’re a Studio builder' : 'Next: Level 2 · Studio builder') + '</h2>'
+ '<p>Level 2 gives you:</p><div class="gives">'
+ '<div><b>Claude Code on the studio</b>A coding-agent seat paid by the studio, for your project.</div>'
+ '<div><b>Lab agents</b>Ask the studio’s AI agents for help — research, sales, content.</div>'
+ '<div><b>Studio network</b>Contacts, bookings and introductions from the cooperative.</div>'
+ '<div><b>Investment pool</b>Apply for the quarterly pool — investors vote where it goes.</div></div>'
+ '<p class="muted">And next, Level 3: a paid role in the studio — by invitation of the board.</p>';
// 1 kyc
h += stepHTML(1, 'kyc', true, 'Verify your identity', 'Done with Didit.', '', false);
// 2 agreement
var agr = c.agreement;
h += stepHTML(2, 'agreement', st.agreement, 'Sign the contractor agreement', agr ? 'Signed by ' + esc(agr.signer_name) + ' · ' + esc(agr.signed_at) + ' UTC' : 'You keep your project. The studio gets a share of revenue you set (min ' + c.min_studio_pct + '%).',
agr ? '<p class="muted">Version ' + esc(agr.version) + '. <a href="agreement/" target="_blank" rel="noopener">Read the agreement</a>.</p>'
: '<p class="muted"><a href="agreement/" target="_blank" rel="noopener">Read the agreement (' + esc(c.agreement_version) + ')</a> — about 3 minutes. Key points: you own your project; the studio share (min ' + c.min_studio_pct + '%) is withheld from payments through the studio; you’re an independent contractor; 30 days’ notice to end.</p>'
+ '<form id="fa" novalidate><div class="field"><label for="an">Type your full name as verified: <em>' + esc(d.name) + '</em></label><input id="an" autocomplete="name" maxlength="120"></div>'
+ '<label class="check"><input type="checkbox" id="ac"><span>I have read and accept the Contractor Agreement (' + esc(c.agreement_version) + ') and sign it electronically.</span></label>'
+ '<button class="btn" style="margin-top:16px" type="submit"><span>Sign agreement</span>' + ARROW + '</button><p class="form-err"></p></form>', firstOpen === 'agreement');
// 3 project
var p = c.project || { decisions: {} }, decs = '';
DEC.forEach(function (x) { decs += '<div class="field"><label for="p-' + x[0] + '">' + x[1] + ' <em>*</em></label><textarea id="p-' + x[0] + '" maxlength="600" placeholder="' + esc(x[2]) + '">' + esc((p.decisions || {})[x[0]]) + '</textarea></div>'; });
h += stepHTML(3, 'project', st.project, 'Describe your project', c.project ? esc(c.project.name) + ' · studio share ' + esc(c.project.studio_pct) + '%' : 'Name, studio share and the 7 decisions every studio project defines.',
'<form id="fp" novalidate><div class="row2"><div class="field"><label for="pn">Project name <em>*</em></label><input id="pn" maxlength="120" value="' + esc(p.name) + '"' + (c.project ? ' disabled' : '') + '></div>'
+ '<div class="field"><label for="pp">Studio share, % <em>*</em></label><input id="pp" type="number" min="' + c.min_studio_pct + '" max="100" step="1" value="' + esc(c.project ? c.project.studio_pct : c.min_studio_pct) + '"' + (c.project ? ' disabled' : '') + '><small>Min ' + c.min_studio_pct + '%. Withheld from each payment through the studio.</small></div></div>'
+ '<div class="field"><label for="pd">One-line description</label><input id="pd" maxlength="600" value="' + esc(p.description) + '"' + (c.project ? ' disabled' : '') + '></div>'
+ decs + '<button class="btn" type="submit"><span>' + (c.project ? 'Save decisions' : 'Create project') + '</span>' + ARROW + '</button><p class="form-err"></p></form>', firstOpen === 'project');
// 4 product
var prods = '';
(c.products || []).forEach(function (x) {
var live = x.mode === 'live', url = (c.project && c.project.site_url) || x.link || '';
prods += '<div class="prod"><b>' + esc(x.name) + '</b> · ' + money(x.price_usd) + (x.interval && x.interval !== 'one_time' ? '/' + esc(x.interval) : '') + ' <span class="pill ' + (live ? 'ok' : 'wip') + '">' + (live ? 'Live' : 'Test mode') + '</span>'
+ (x.link ? '<div class="creds" style="margin-top:8px">Payment link: <b><a href="' + esc(x.link) + '" target="_blank" rel="noopener">' + esc(x.link) + '</a></b></div>' : '')
+ (url ? shareHTML(url, (live ? 'Now live: ' : 'Coming soon: ') + x.name + ' — built at Shkor Labs') : '')
+ (live ? '' : x.live_request === 'pending' ? '<p class="muted" style="margin-top:10px">Live payments requested — the studio is reviewing.</p>'
: '<form class="fl" data-id="' + x.id + '" style="margin-top:12px"><div class="field"><label>What exactly will customers pay for?</label><input class="fl-note" maxlength="1000" placeholder="e.g. Monthly access to … for small clinics"></div><button class="btn btn-sm" type="submit"><span>Request live payments</span></button><p class="form-err"></p></form>')
+ '</div>';
});
h += stepHTML(4, 'product', st.product, 'Launch a product', 'Generate a product with a payment link, share it, then go live after a quick review.',
(c.project ? prods + '<form id="fr" novalidate style="margin-top:12px"><div class="row3"><div class="field"><label for="rn">Product name</label><input id="rn" maxlength="120" placeholder="e.g. Pro plan"></div>'
+ '<div class="field"><label for="rp">Price, USD</label><input id="rp" type="number" min="1" max="20000" step="1" value="29"></div>'
+ '<div class="field"><label for="ri">Billing</label><select id="ri"><option value="month">Monthly</option><option value="year">Yearly</option><option value="one_time">One-time</option></select></div></div>'
+ '<button class="btn" type="submit"><span>Generate product</span>' + ARROW + '</button><p class="form-err"></p><p class="muted">Starts in test mode: the link works, but no money moves until live payments are approved.</p></form>'
: '<p class="muted">Create your project first (step 3).</p>'), firstOpen === 'product');
// 5 traction
var th = c.thresholds || { gross_usd: 300, payments: 10 }, g = Math.min(100, (e.gross || 0) / th.gross_usd * 100), n = Math.min(100, (e.payments || 0) / th.payments * 100);
h += stepHTML(5, 'traction', st.traction, 'Show traction', 'Either ' + money(th.gross_usd) + ' in sales or ' + th.payments + ' paying customers through studio payments.',
'<p class="muted">Sales: ' + money(e.gross) + ' of ' + money(th.gross_usd) + '</p><div class="bar"><i style="width:' + g + '%"></i></div>'
+ '<p class="muted">Payments: ' + (e.payments || 0) + ' of ' + th.payments + '</p><div class="bar"><i style="width:' + n + '%"></i></div>', firstOpen === 'traction');
h += '</div>';
app.innerHTML = h;
bind();
}
var chatSig = '';
function drawChat(r) {
var box = document.getElementById('chat'); if (!box) return;
var msgs = (r && r.messages) || [], sig = msgs.length + ':' + (msgs.length ? msgs[msgs.length - 1].id : '');
if (sig === chatSig) return; chatSig = sig;
var atBottom = box.scrollHeight - box.scrollTop - box.clientHeight < 40;
box.innerHTML = msgs.length ? msgs.map(function (m) {
var t = new Date(m.ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
return '<div class="msg ' + (m.from === 'agent' ? 'ag' : 'me') + '">' + esc(m.text) + '<small>' + (m.from === 'agent' ? 'Agent' : 'You') + ' · ' + t + (m.from !== 'agent' && !m.seen ? ' · sent' : '') + '</small></div>';
}).join('') : '<p class="muted">No messages yet — say hi to your agent.</p>';
if (atBottom || msgs.length && msgs[msgs.length - 1].from !== 'agent') box.scrollTop = box.scrollHeight;
}
function chat(text) {
return api('/api/account/chat', text ? { text: text } : {}).then(function (r) {
if (r.status === 401) return loginView('Your session expired — sign in again.');
var err = document.getElementById('cerr');
if (!r.ok) { if (err) err.textContent = r.j.error || 'Chat is unavailable right now.'; return; }
if (err) err.textContent = ''; drawChat(r.j);
});
}
setInterval(function () { if (!document.hidden && document.getElementById('chat')) chat(''); }, 5000);
function act(path, body, form) {
var err = form.querySelector('.form-err'), btn = form.querySelector('button[type=submit]');
err.textContent = ''; btn.disabled = true;
api('/api/account/' + path, body).then(function (r) {
btn.disabled = false;
if (r.status === 401) return loginView('Your session expired — sign in again.');
if (!r.ok || r.j.error) { err.textContent = r.j.error || 'Something went wrong.'; return; }
S.cabinet = r.j; render();
}).catch(function () { btn.disabled = false; err.textContent = 'Network error — please try again.'; });
}
function bind() {
var fa = document.getElementById('fa');
if (fa) fa.addEventListener('submit', function (e) { e.preventDefault(); act('agreement', { signer_name: document.getElementById('an').value.trim(), accept: document.getElementById('ac').checked }, fa); });
var fp = document.getElementById('fp');
if (fp) fp.addEventListener('submit', function (e) {
e.preventDefault(); var dec = {};
DEC.forEach(function (x) { dec[x[0]] = document.getElementById('p-' + x[0]).value.trim(); });
act('project', { name: document.getElementById('pn').value.trim(), studio_pct: Number(document.getElementById('pp').value), description: document.getElementById('pd').value.trim(), decisions: dec }, fp);
});
var fr = document.getElementById('fr');
if (fr) fr.addEventListener('submit', function (e) { e.preventDefault(); act('product', { name: document.getElementById('rn').value.trim(), price_usd: Number(document.getElementById('rp').value), interval: document.getElementById('ri').value }, fr); });
document.querySelectorAll('.fl').forEach(function (f) { f.addEventListener('submit', function (e) { e.preventDefault(); act('product-live', { product_id: Number(f.dataset.id), note: f.querySelector('.fl-note').value.trim() }, f); }); });
var cf = document.getElementById('cf'), ct = document.getElementById('ct');
if (cf) {
chatSig = ''; chat('');
cf.addEventListener('submit', function (e) { e.preventDefault(); var t = ct.value.trim(); if (!t) return; ct.value = ''; chat(t); });
ct.addEventListener('keydown', function (e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); cf.requestSubmit(); } });
}
document.querySelectorAll('[data-copy]').forEach(function (b) { b.addEventListener('click', function () { navigator.clipboard && navigator.clipboard.writeText(b.dataset.copy); b.textContent = 'Copied'; }); });
}
function load() {
api('/api/account/me').then(function (r) {
if (r.status === 401) return loginView(q.get('expired') ? 'That link has expired or was already used — request a new one.' : '');
if (!r.ok) { app.innerHTML = '<p class="kicker">Your account</p><h1>Can’t load right now</h1><p class="lead">' + esc(r.j.error || 'Please refresh in a moment.') + '</p>'; return; }
S = r.j; render();
}).catch(function () { app.innerHTML = '<p class="kicker">Your account</p><h1>Can’t load right now</h1><p class="lead">Please refresh in a moment.</p>'; });
}
document.getElementById('logout').addEventListener('click', function () { api('/api/account/logout', {}).then(function () { loginView(''); }); });
load();
})();
</script>
</body>
</html>