257 lines
23 KiB
HTML
257 lines
23 KiB
HTML
<!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 { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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>
|