simbiot-harness/sites/shkor-com/v2/account/index.html

233 lines
22 KiB
HTML
Raw Permalink 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}
</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 LV = { 1: 'Community', 2: 'Studio builder', 3: 'Studio partner' };
var h = '<div class="topbar"><div><p class="kicker">Your account</p><h1>Hi, ' + esc(first) + ' <span class="lvl">Level ' + (c ? c.level : 1) + ' · ' + LV[c ? c.level : 1] + '</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>Your cloud workspace: OpenCode is ready to go; Claude Code ' + ((c.claude || {}).status === 'connected' ? 'runs on the studio (Level 2).' : 'works with your own Claude account — or on the studio from Level 2.') + ' Both are connected to the lab MCP — git, subdomains and payments.</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>'
+ '<a class="btn" style="margin-top:14px" href="' + esc(d.panel.url) + '" target="_blank" rel="noopener"><span>Open my workspace</span>' + ARROW + '</a></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>';
var st = {}; (c.steps || []).forEach(function (s) { st[s.key] = s.done; });
var agr = c.agreement, cl = c.claude || { status: 'locked' };
// ── Level 2 · Studio builder: one step — the contractor agreement
var CL = { locked: ['todo', 'Unlocks when you sign'], requested: ['wip', 'Setting up your account…'], connected: ['ok', 'Connected'] }[cl.status] || ['wip', cl.status];
var clBody = cl.status === 'connected' ? 'Open your workspace and choose <b>Claude Code</b> — it runs on the studio’s account, nothing to pay or sign in.'
: cl.status === 'requested' ? 'The studio is setting up a Claude Code account for you — usually within a day. This turns green by itself once it works on your server.' + (cl.detail ? '<br><span class="muted">' + esc(cl.detail) + '</span>' : '')
: 'A Claude Code account from the studio, connected to your workspace for you.';
h += '<div class="panel"><h2>' + (agr ? 'Level 2 · Studio builder <span class="pill ok">Active</span>' : 'Level 2 · Studio builder — one step away') + '</h2>'
+ '<p>' + (agr ? 'Your perks are on.' : 'Sign the contractor agreement and these switch on right away:') + '</p><div class="gives">'
+ '<div><b>Claude Code on the studio <span class="pill ' + CL[0] + '">' + esc(CL[1]) + '</span></b>' + clBody + '</div>'
+ '<div><b>Live payments <span class="pill ' + (agr ? 'ok' : 'todo') + '">' + (agr ? 'Unlocked' : 'Locked') + '</span></b>Request real card payments for your products — switched on after a quick review.</div>'
+ '</div>';
h += stepHTML(1, 'agreement', !!agr, '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 and unlock Level 2</span>' + ARROW + '</button><p class="form-err"></p></form>', !agr);
h += '</div>';
// ── Level 3 · Studio partner: project, live product, traction
var firstOpen = agr ? ['project', 'product', 'traction'].filter(function (k) { return !st[k]; })[0] : null;
h += '<div class="panel"><h2>' + (c.level === 3 ? 'Level 3 · Studio partner <span class="pill ok">Active</span>' : 'Next: Level 3 · Studio partner') + '</h2>'
+ '<p>Build a business with the studio behind you:</p><div class="gives">'
+ '<div><b>Lab agents</b>The studio’s AI agents help with research, sales and 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">Beyond that: a paid role in the studio — by invitation of the board.</p>';
// 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(1, '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(2, '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 1).</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(3, '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();
}
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); }); });
document.querySelectorAll('[data-copy]').forEach(function (b) { b.addEventListener('click', function () { navigator.clipboard && navigator.clipboard.writeText(b.dataset.copy); b.textContent = 'Copied'; }); });
}
// пока студия подключает Claude Code — обновлять кабинет раз в минуту
setInterval(function () {
var cl = S && S.cabinet && S.cabinet.claude;
if (!document.hidden && cl && cl.status === 'requested' && !document.querySelector('form :focus')) load();
}, 60000);
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>