Add El source files for all client-side JS
Recovers original JS from git history and ports it into proper El source files under src/js/. Each file wraps the original JS in a native_js call inside a main() function, making it valid El that compiles to a self-contained IIFE via elc --target=js --bundle. Files added: src/js/account-auth.el - Supabase OTP magic-link (sendMagicLink) src/js/account-dashboard.el - Account dashboard: session, plan card, family src/js/chat-widget.el - Demo chat widget (neuronDemoToggle/Send/Reset) src/js/checkout-auth.el - Checkout auth: OAuth, email sign-in/up src/js/checkout-free.el - Free plan: auth-badge watch -> payment reveal src/js/checkout-stripe.el - Stripe Payment Element (reads NEURON_CFG) src/js/enterprise.el - Enterprise inquiry form + headcount filter src/js/environmental.el - Efficiency calculator slider src/js/gallery.el - Gallery nav, search/sort, Supabase voting src/js/main.el - Share page voting + copyForPlatform src/js/marketplace.el - Developer interest form src/js/nav.el - Nav hamburger + Mission dropdown src/js/styles.el - Landing: nav scroll, reveal, founding counter
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
// account-auth.el -- Supabase OTP magic-link auth for the account page.
|
||||
// Sends a PKCE magic link to the user's email address.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
//
|
||||
// Required HTML elements: #acct-email-input, #acct-magic-btn, #acct-email-msg
|
||||
// Required globals: window.NEURON_CFG.supabase_url, window.NEURON_CFG.supabase_anon_key
|
||||
// Required CDN: supabase-js@2 loaded before this script
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
'use strict';
|
||||
var cfg = window.NEURON_CFG || {};
|
||||
var sb = supabase.createClient(cfg.supabase_url, cfg.supabase_anon_key, {
|
||||
auth: { flowType: 'pkce' }
|
||||
});
|
||||
|
||||
window.sendMagicLink = async function() {
|
||||
var email = (document.getElementById('acct-email-input').value || '').trim();
|
||||
var msgEl = document.getElementById('acct-email-msg');
|
||||
var btn = document.getElementById('acct-magic-btn');
|
||||
if (!email) {
|
||||
msgEl.style.display = 'block';
|
||||
msgEl.style.color = '#c44';
|
||||
msgEl.textContent = 'Please enter your email address.';
|
||||
return;
|
||||
}
|
||||
if (btn) { btn.disabled = true; btn.textContent = 'Sending...'; }
|
||||
var result = await sb.auth.signInWithOtp({ email: email });
|
||||
if (btn) { btn.disabled = false; btn.textContent = 'Continue with email'; }
|
||||
msgEl.style.display = 'block';
|
||||
if (result.error) {
|
||||
msgEl.style.color = '#c44';
|
||||
msgEl.textContent = result.error.message;
|
||||
} else {
|
||||
msgEl.style.color = 'var(--navy)';
|
||||
msgEl.textContent = 'Check your inbox — we sent a sign-in link to ' + email + '.';
|
||||
}
|
||||
};
|
||||
})()")
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
// account-dashboard.el -- Account dashboard: session check, plan card, family.
|
||||
// Handles onAuthStateChange, plan card rendering, family member management.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
//
|
||||
// Required globals: window.NEURON_CFG.supabase_url, window.NEURON_CFG.supabase_anon_key
|
||||
// Required CDN: supabase-js@2
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
'use strict';
|
||||
|
||||
var cfg = window.NEURON_CFG || {};
|
||||
var sb = supabase.createClient(cfg.supabase_url, cfg.supabase_anon_key, {
|
||||
auth: { flowType: 'implicit' }
|
||||
});
|
||||
|
||||
function show(id) { var el = document.getElementById(id); if (el) el.style.display = ''; }
|
||||
function hide(id) { var el = document.getElementById(id); if (el) el.style.display = 'none'; }
|
||||
function setText(id, text) { var el = document.getElementById(id); if (el) el.textContent = text; }
|
||||
function setHtml(id, html) { var el = document.getElementById(id); if (el) el.innerHTML = html; }
|
||||
|
||||
window.signInWith = async function(provider) {
|
||||
var btn = document.getElementById('btn-' + provider);
|
||||
if (btn) { btn.disabled = true; btn.style.opacity = '0.6'; }
|
||||
try {
|
||||
var result = await sb.auth.signInWithOAuth({
|
||||
provider: provider,
|
||||
options: { redirectTo: window.location.origin + '/account' }
|
||||
});
|
||||
if (result.error) {
|
||||
if (btn) { btn.disabled = false; btn.style.opacity = '1'; }
|
||||
}
|
||||
} catch (e) {
|
||||
if (btn) { btn.disabled = false; btn.style.opacity = '1'; }
|
||||
}
|
||||
};
|
||||
|
||||
window.signInWithEmail = async function() {
|
||||
var email = document.getElementById('acct-email-input').value.trim();
|
||||
var pass = document.getElementById('acct-pass-input').value;
|
||||
var msg = document.getElementById('acct-email-msg');
|
||||
var signinBtn = document.getElementById('acct-signin-btn');
|
||||
if (!sb) { msg.style.display='block'; msg.style.color='#c44'; msg.textContent='Loading... try again in a moment.'; return; }
|
||||
if (!email || !pass) {
|
||||
msg.style.display='block'; msg.style.color='#c44'; msg.textContent='Please enter your email and password.'; return;
|
||||
}
|
||||
if (signinBtn) { signinBtn.disabled=true; signinBtn.textContent='Signing in...'; }
|
||||
var result = await sb.auth.signInWithPassword({ email: email, password: pass });
|
||||
if (result.error) {
|
||||
if (result.error.message && result.error.message.toLowerCase().includes('invalid')) {
|
||||
var signupResult = await sb.auth.signUp({
|
||||
email: email, password: pass,
|
||||
options: { emailRedirectTo: window.location.origin + '/account' }
|
||||
});
|
||||
if (signupResult.error) {
|
||||
msg.style.display='block'; msg.style.color='#c44'; msg.textContent=signupResult.error.message; return;
|
||||
}
|
||||
msg.style.display='block'; msg.style.color='var(--navy)'; msg.textContent='Check your email to confirm your account.'; return;
|
||||
}
|
||||
if (signinBtn) { signinBtn.disabled=false; signinBtn.textContent='Sign in'; }
|
||||
msg.style.display='block'; msg.style.color='#c44'; msg.textContent=result.error.message; return;
|
||||
}
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
window.signOut = async function() {
|
||||
var btn = document.getElementById('signout-btn');
|
||||
var btnTop = document.getElementById('signout-btn-top');
|
||||
if (btn) { btn.disabled=true; btn.textContent='Signing out...'; }
|
||||
if (btnTop) { btnTop.disabled=true; btnTop.textContent='Signing out...'; }
|
||||
await sb.auth.signOut();
|
||||
show('signin-section');
|
||||
hide('dashboard-section');
|
||||
if (btn) { btn.disabled=false; btn.textContent='Sign out'; }
|
||||
if (btnTop) { btnTop.disabled=false; btnTop.textContent='Sign out'; }
|
||||
};
|
||||
|
||||
async function renderPlanCard(row) {
|
||||
var plan = (row && row.plan) ? row.plan : 'free';
|
||||
var memberNum = (row && row.member_number) ? row.member_number : null;
|
||||
var createdAt = (row && row.created_at) ? row.created_at : null;
|
||||
|
||||
var planNames = { 'founding': 'Founding Member', 'professional': 'Professional', 'free': 'Free' };
|
||||
setText('plan-name-el', planNames[plan] || 'Free');
|
||||
|
||||
var statusHtml = '';
|
||||
if (plan === 'founding' || plan === 'professional') {
|
||||
statusHtml = '<span class=\"status-badge-preorder\" style=\"margin-top:.625rem;display:inline-flex\">'
|
||||
+ '<svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/></svg>'
|
||||
+ 'Launching within 30 days</span>';
|
||||
} else {
|
||||
statusHtml = '<span class=\"plan-status\" style=\"margin-top:.625rem;display:inline-flex\"><span class=\"plan-status-dot\"></span>Active</span>';
|
||||
}
|
||||
setHtml('plan-status-el', statusHtml);
|
||||
|
||||
var billingNote = '';
|
||||
if (plan === 'founding') {
|
||||
billingNote = '<p class=\"plan-billing-note\">Lifetime · Never billed again</p>';
|
||||
} else if (plan === 'professional') {
|
||||
billingNote = '<p class=\"plan-billing-note\">Billed monthly · <button class=\"plan-billing-link\" onclick=\"window.location.href=\'/contact\'\">Cancel</button></p>';
|
||||
} else {
|
||||
billingNote = '<p class=\"plan-billing-note\">On the waitlist</p>';
|
||||
}
|
||||
setHtml('plan-billing-note-el', billingNote);
|
||||
|
||||
var meta = '';
|
||||
if (createdAt) {
|
||||
var d = new Date(createdAt);
|
||||
var dateStr = d.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
|
||||
meta += '<div class=\"plan-meta-item\"><span class=\"plan-meta-label\">Joined</span><span class=\"plan-meta-value\">' + dateStr + '</span></div>';
|
||||
}
|
||||
if (memberNum) {
|
||||
meta += '<div class=\"plan-meta-item\"><span class=\"plan-meta-label\">Member number</span><span class=\"plan-meta-value\">#' + memberNum + ' of 1,000</span></div>';
|
||||
}
|
||||
if (meta) setHtml('plan-meta-el', meta);
|
||||
|
||||
if (plan === 'founding') {
|
||||
var badgeSection = document.getElementById('badge-section');
|
||||
var badgeContainer = document.getElementById('badge-html-container');
|
||||
if (badgeSection) badgeSection.style.display = '';
|
||||
var badgeN = memberNum || 0;
|
||||
fetch('/api/founding-badge?n=' + badgeN)
|
||||
.then(function(r) { return r.text(); })
|
||||
.then(function(html) { if (badgeContainer) badgeContainer.innerHTML = html; })
|
||||
.catch(function() {});
|
||||
}
|
||||
|
||||
var roadmapSection = document.getElementById('roadmap-section');
|
||||
if (plan === 'founding' && roadmapSection) roadmapSection.style.display = '';
|
||||
|
||||
if (plan === 'founding') {
|
||||
var famSection = document.getElementById('family-section');
|
||||
if (famSection) famSection.style.display = 'block';
|
||||
var session = await sb.auth.getSession();
|
||||
var userEmail = session.data.session && session.data.session.user ? session.data.session.user.email : '';
|
||||
if (userEmail) loadFamilyMembers(userEmail);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFamilyMembers(parentEmail) {
|
||||
var r = await fetch('/api/family/members?parent_email=' + encodeURIComponent(parentEmail));
|
||||
var members = await r.json();
|
||||
var list = document.getElementById('family-list');
|
||||
if (!list) return;
|
||||
if (!members || !members.length) {
|
||||
list.innerHTML = '<p style=\"color:var(--t3);font-size:.875rem;margin-bottom:1rem\">No family members yet.</p>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = members.map(function(m) {
|
||||
return '<div style=\"display:flex;justify-content:space-between;align-items:center;padding:.75rem 0;border-bottom:1px solid var(--border)\">'
|
||||
+ '<div><p style=\"font-size:.875rem;color:var(--t1)\">' + m.child_email + '</p>'
|
||||
+ '<p style=\"font-size:.75rem;color:var(--t3);text-transform:uppercase;letter-spacing:.06em\">' + m.status + '</p></div>'
|
||||
+ '<button onclick=\"removeFamilyMember(\'' + m.child_email + '\')\" style=\"background:none;border:none;color:var(--t3);cursor:pointer;font-size:.75rem\">Remove</button>'
|
||||
+ '</div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
window.addFamilyMember = async function() {
|
||||
var email = document.getElementById('child-email').value.trim();
|
||||
var year = document.getElementById('child-dob-year').value;
|
||||
var attest = document.getElementById('family-attest').checked;
|
||||
var msg = document.getElementById('family-msg');
|
||||
if (!email || !year || !attest) {
|
||||
msg.style.display='block'; msg.style.color='#c44'; msg.textContent='Please fill in all fields and confirm the attestation.'; return;
|
||||
}
|
||||
if (parseInt(year) < 2008) {
|
||||
msg.style.display='block'; msg.style.color='#c44'; msg.textContent='Child must be under 18. Birth year must be 2008 or later.'; return;
|
||||
}
|
||||
var session = await sb.auth.getSession();
|
||||
var parentEmail = session.data.session && session.data.session.user ? session.data.session.user.email : '';
|
||||
var r = await fetch('/api/family/invite', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({parent_email: parentEmail, child_email: email, child_dob_year: parseInt(year), attested: true})
|
||||
});
|
||||
var d = await r.json();
|
||||
msg.style.display = 'block';
|
||||
if (d.ok) {
|
||||
msg.style.color = 'var(--navy)';
|
||||
msg.textContent = 'Invitation sent to ' + email + '. They will receive an email to set up their account.';
|
||||
document.getElementById('child-email').value = '';
|
||||
document.getElementById('child-dob-year').value = '';
|
||||
document.getElementById('family-attest').checked = false;
|
||||
loadFamilyMembers(parentEmail);
|
||||
} else {
|
||||
msg.style.color = '#c44';
|
||||
msg.textContent = d.error || 'Something went wrong.';
|
||||
}
|
||||
};
|
||||
|
||||
window.removeFamilyMember = async function(childEmail) {
|
||||
var session = await sb.auth.getSession();
|
||||
var parentEmail = session.data.session && session.data.session.user ? session.data.session.user.email : '';
|
||||
await fetch('/api/family/remove', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({parent_email: parentEmail, child_email: childEmail})
|
||||
});
|
||||
loadFamilyMembers(parentEmail);
|
||||
};
|
||||
|
||||
function renderUserChip(user) {
|
||||
var email = user.email || '';
|
||||
var avatarEl = document.getElementById('user-avatar-el');
|
||||
var emailEl = document.getElementById('user-email-el');
|
||||
var headerEmailEl = document.getElementById('acct-header-email');
|
||||
if (emailEl) emailEl.textContent = email;
|
||||
if (headerEmailEl) headerEmailEl.textContent = email;
|
||||
var avatarUrl = user.user_metadata && user.user_metadata.avatar_url;
|
||||
if (avatarEl) {
|
||||
if (avatarUrl) {
|
||||
avatarEl.innerHTML = '<img src=\"' + avatarUrl + '\" alt=\"\" referrerpolicy=\"no-referrer\">';
|
||||
} else {
|
||||
avatarEl.textContent = email ? email.charAt(0).toUpperCase() : '?';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showNoPlan() {
|
||||
var el = document.getElementById('plan-card');
|
||||
if (!el) return;
|
||||
el.innerHTML = '<div class=\"card-label\">Your plan</div>'
|
||||
+ '<p style=\"font-family:var(--body);font-weight:500;font-size:1.125rem;color:var(--t1);margin-bottom:.75rem\">No active plan</p>'
|
||||
+ '<p style=\"font-family:var(--body);font-weight:300;font-size:.9rem;color:var(--t2);line-height:1.7;margin-bottom:1.5rem\">You have an account but no plan selected yet. Pick one below to preorder.</p>'
|
||||
+ '<div style=\"display:flex;gap:1rem;flex-wrap:wrap\">'
|
||||
+ '<a href=\"/checkout?plan=founding\" class=\"btn-primary\" style=\"padding:.75rem 1.5rem\">Founding Member - $199 →</a>'
|
||||
+ '<a href=\"/checkout?plan=professional\" class=\"btn-ghost\" style=\"padding:.75rem 1.5rem\">Professional - $19/mo</a>'
|
||||
+ '<a href=\"/checkout?plan=free\" class=\"btn-ghost\" style=\"padding:.75rem 1.5rem\">Free tier</a>'
|
||||
+ '</div>';
|
||||
}
|
||||
|
||||
async function loadWaitlistData() {
|
||||
try {
|
||||
var sess = await sb.auth.getSession();
|
||||
var token = sess.data && sess.data.session ? sess.data.session.access_token : '';
|
||||
if (!token) { showNoPlan(); return; }
|
||||
var r = await fetch('/api/my-plan', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({access_token: token})
|
||||
});
|
||||
var row = await r.json();
|
||||
if (!row || !row.plan) { showNoPlan(); return; }
|
||||
renderPlanCard(row);
|
||||
} catch (e) {
|
||||
showNoPlan();
|
||||
}
|
||||
}
|
||||
|
||||
function showDashboard(user) {
|
||||
hide('signin-section');
|
||||
show('dashboard-section');
|
||||
renderUserChip(user);
|
||||
loadWaitlistData();
|
||||
}
|
||||
|
||||
async function init() {
|
||||
var result = await sb.auth.getSession();
|
||||
var session = result.data && result.data.session;
|
||||
if (session && session.user) {
|
||||
showDashboard(session.user);
|
||||
} else {
|
||||
show('signin-section');
|
||||
hide('dashboard-section');
|
||||
}
|
||||
sb.auth.onAuthStateChange(function(event, session) {
|
||||
if (session && session.user) {
|
||||
showDashboard(session.user);
|
||||
} else {
|
||||
show('signin-section');
|
||||
hide('dashboard-section');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
})()")
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
// chat-widget.el -- Neuron demo chat widget with Turnstile, session persistence,
|
||||
// local engram graph, and share-pill.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
//
|
||||
// Exposed globals: neuronDemoToggle(), neuronDemoSend(), neuronDemoReset()
|
||||
// Required CDN: marked.js, Cloudflare Turnstile
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
if (typeof marked !== 'undefined') { marked.setOptions({ breaks: true, gfm: true }); }
|
||||
var TURNSTILE_SITE_KEY = '0x4AAAAAADHAZXyuRb3yD9mr';
|
||||
var turnstileToken = '';
|
||||
var turnstileWidgetId = null;
|
||||
var turnstileVerified = false;
|
||||
var isOpen = false;
|
||||
var MAX = 10;
|
||||
|
||||
function loadSession() {
|
||||
try {
|
||||
var s = localStorage.getItem('neuron_demo_session');
|
||||
return s ? JSON.parse(s) : { messages: [], count: 0, context: '' };
|
||||
} catch(e) { return { messages: [], count: 0, context: '' }; }
|
||||
}
|
||||
function saveSession(session) {
|
||||
try { localStorage.setItem('neuron_demo_session', JSON.stringify(session)); } catch(e) {}
|
||||
}
|
||||
function clearSession() {
|
||||
try { localStorage.removeItem('neuron_demo_session'); } catch(e) {}
|
||||
}
|
||||
|
||||
function _mg(s) { return s._m || { nodes: [], edges: [] }; }
|
||||
|
||||
function _um(s, nn, ne) {
|
||||
if (!nn || !nn.length) return;
|
||||
var g = _mg(s), nm = {}, ek = function(e) { return e.from + '->' + e.to; }, em = {};
|
||||
g.nodes.forEach(function(n) { nm[n.id] = n; });
|
||||
(nn || []).forEach(function(n) {
|
||||
if (nm[n.id]) { nm[n.id].w = Math.min(1.0, (nm[n.id].w || 0.5) + 0.08); }
|
||||
else { nm[n.id] = n; }
|
||||
});
|
||||
g.nodes = Object.values(nm);
|
||||
g.edges.forEach(function(e) { em[ek(e)] = e; });
|
||||
(ne || []).forEach(function(e) {
|
||||
var k = ek(e);
|
||||
if (em[k]) { em[k].weight = Math.min(1.0, (em[k].weight || 0.5) + 0.05); }
|
||||
else { em[k] = e; }
|
||||
});
|
||||
g.edges = Object.values(em);
|
||||
s._m = g; saveSession(s);
|
||||
}
|
||||
|
||||
function _ra(g, q) {
|
||||
if (!g || !g.nodes || !g.nodes.length) return [];
|
||||
var words = q.toLowerCase().split(/\s+/).filter(function(w) { return w.length > 3; });
|
||||
var sc = {};
|
||||
g.nodes.forEach(function(n) {
|
||||
var t = (n.content || '').toLowerCase();
|
||||
sc[n.id] = words.filter(function(w) { return t.indexOf(w) !== -1; }).length * 0.6 + (n.w || 0.5) * 0.4;
|
||||
});
|
||||
(g.edges || []).forEach(function(e) {
|
||||
if (sc[e.from] > 0.1) sc[e.to] = (sc[e.to] || 0) + sc[e.from] * (e.weight || 0.5) * 0.4;
|
||||
});
|
||||
return g.nodes.filter(function(n) { return sc[n.id] > 0.2; })
|
||||
.sort(function(a, b) { return sc[b.id] - sc[a.id]; }).slice(0, 5)
|
||||
.map(function(n) { return { id: n.id, content: n.content, score: sc[n.id] }; });
|
||||
}
|
||||
|
||||
if (window.location.search.indexOf('reset=1') !== -1) {
|
||||
clearSession();
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
}
|
||||
|
||||
var session = loadSession();
|
||||
if (!session.uid) {
|
||||
session.uid = 'u' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
|
||||
saveSession(session);
|
||||
}
|
||||
var msgCount = session.count || 0;
|
||||
|
||||
function updateCountdown() {
|
||||
var el = document.getElementById('neuron-demo-countdown');
|
||||
if (!el) return;
|
||||
var remaining = MAX - msgCount;
|
||||
el.textContent = remaining + ' question' + (remaining === 1 ? '' : 's') + ' left';
|
||||
el.style.color = '#ffffff';
|
||||
el.style.fontWeight = '700';
|
||||
}
|
||||
|
||||
window.neuronDemoReset = function() {
|
||||
clearSession();
|
||||
session = { messages: [], count: 0, context: '' };
|
||||
msgCount = 0;
|
||||
var msgs = document.getElementById('neuron-demo-messages');
|
||||
if (msgs) msgs.innerHTML = '';
|
||||
var input = document.getElementById('neuron-demo-text');
|
||||
if (input) { input.disabled = false; input.placeholder = 'Ask me anything...'; }
|
||||
var btn = document.getElementById('neuron-demo-send');
|
||||
if (btn) btn.disabled = false;
|
||||
addMsg('ai', 'Hey. What is on your mind?', true);
|
||||
};
|
||||
|
||||
window.neuronDemoToggle = function() {
|
||||
isOpen = !isOpen;
|
||||
var panel = document.getElementById('neuron-demo-panel');
|
||||
if (panel) panel.style.display = isOpen ? 'flex' : 'none';
|
||||
var btn = document.getElementById('neuron-demo-btn');
|
||||
if (btn) btn.style.display = isOpen ? 'none' : '';
|
||||
var msgs = document.getElementById('neuron-demo-messages');
|
||||
if (isOpen && turnstileVerified && msgs && msgs.style.display !== 'none' && msgs.children.length === 0) {
|
||||
if (session.messages && session.messages.length > 0) {
|
||||
session.messages.forEach(function(m) { addMsg(m.role, m.text, true); });
|
||||
var remaining = MAX - msgCount;
|
||||
if (remaining <= 0) {
|
||||
var input = document.getElementById('neuron-demo-text');
|
||||
if (input) { input.disabled = true; input.placeholder = 'Interaction limit reached'; }
|
||||
}
|
||||
} else if (!session.greeted) {
|
||||
addMsg('ai', 'Hey. What is on your mind?', true);
|
||||
session.greeted = true;
|
||||
saveSession(session);
|
||||
}
|
||||
}
|
||||
var input = document.getElementById('neuron-demo-text');
|
||||
if (isOpen && input && !input.disabled) input.focus();
|
||||
updateCountdown();
|
||||
if (isOpen && !turnstileWidgetId && typeof turnstile !== 'undefined') {
|
||||
var container = document.getElementById('neuron-demo-turnstile');
|
||||
if (container) {
|
||||
turnstileWidgetId = turnstile.render(container, {
|
||||
sitekey: TURNSTILE_SITE_KEY,
|
||||
size: 'compact',
|
||||
callback: function(token) {
|
||||
turnstileToken = token;
|
||||
turnstileVerified = true;
|
||||
if (typeof turnstile !== 'undefined' && turnstileWidgetId !== null) {
|
||||
try { turnstile.remove(turnstileWidgetId); } catch(e) {}
|
||||
turnstileWidgetId = null;
|
||||
}
|
||||
var gate = document.getElementById('neuron-demo-gate');
|
||||
var msgs = document.getElementById('neuron-demo-messages');
|
||||
var inputRow = document.getElementById('neuron-demo-input-row');
|
||||
if (gate) gate.style.display = 'none';
|
||||
if (msgs) msgs.style.display = 'flex';
|
||||
if (inputRow) inputRow.style.display = 'flex';
|
||||
addMsg('ai', 'Hey. What is on your mind?', true);
|
||||
updateCountdown();
|
||||
var inp = document.getElementById('neuron-demo-text');
|
||||
if (inp) inp.focus();
|
||||
},
|
||||
'expired-callback': function() {
|
||||
turnstileToken = '';
|
||||
turnstileVerified = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function addMsg(role, text, skipSave) {
|
||||
var msgs = document.getElementById('neuron-demo-messages');
|
||||
if (!msgs) return null;
|
||||
var el = document.createElement('div');
|
||||
el.className = 'demo-msg demo-msg-' + role;
|
||||
var avatar = document.createElement('div');
|
||||
avatar.className = 'demo-msg-avatar';
|
||||
if (role === 'ai') {
|
||||
var img = document.createElement('img');
|
||||
img.src = '/assets/brand/neuron-brain.png'; img.alt = 'Neuron';
|
||||
avatar.appendChild(img);
|
||||
} else {
|
||||
var svgNS = 'http://www.w3.org/2000/svg';
|
||||
var svg = document.createElementNS(svgNS, 'svg');
|
||||
svg.setAttribute('width', '14'); svg.setAttribute('height', '14');
|
||||
svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('fill', 'none');
|
||||
svg.setAttribute('stroke', 'currentColor'); svg.setAttribute('stroke-width', '2');
|
||||
var p1 = document.createElementNS(svgNS, 'path');
|
||||
p1.setAttribute('d', 'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2');
|
||||
var c1 = document.createElementNS(svgNS, 'circle');
|
||||
c1.setAttribute('cx', '12'); c1.setAttribute('cy', '7'); c1.setAttribute('r', '4');
|
||||
svg.appendChild(p1); svg.appendChild(c1); avatar.appendChild(svg);
|
||||
}
|
||||
var bubble = document.createElement('div');
|
||||
bubble.className = 'demo-msg-bubble';
|
||||
if (role === 'ai' && typeof marked !== 'undefined') {
|
||||
try { bubble.innerHTML = marked.parse(text); } catch(e) { bubble.textContent = text; }
|
||||
} else {
|
||||
bubble.textContent = text;
|
||||
}
|
||||
if (role === 'ai') {
|
||||
var bodyWrap = document.createElement('div');
|
||||
bodyWrap.className = 'demo-msg-ai-body';
|
||||
bodyWrap.appendChild(bubble);
|
||||
if (!skipSave) {
|
||||
var shareBtn = document.createElement('button');
|
||||
shareBtn.className = 'demo-share-pill'; shareBtn.title = 'Share this response'; shareBtn.textContent = 'Share ↗';
|
||||
shareBtn.onclick = async function() {
|
||||
var prevUser = '';
|
||||
if (session.messages) {
|
||||
for (var i = session.messages.length - 1; i >= 0; i--) {
|
||||
if (session.messages[i].role === 'user') { prevUser = session.messages[i].text; break; }
|
||||
}
|
||||
}
|
||||
shareBtn.style.opacity = '0.4';
|
||||
try {
|
||||
var r = await fetch('/api/share', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ question: prevUser, answer: text })
|
||||
});
|
||||
var d = await r.json();
|
||||
if (d.id) window.open('/share/' + d.id, '_blank');
|
||||
} catch(e) {}
|
||||
shareBtn.style.opacity = '1';
|
||||
};
|
||||
bodyWrap.appendChild(shareBtn);
|
||||
}
|
||||
el.appendChild(avatar); el.appendChild(bodyWrap);
|
||||
} else {
|
||||
el.appendChild(avatar); el.appendChild(bubble);
|
||||
}
|
||||
msgs.appendChild(el);
|
||||
msgs.scrollTop = msgs.scrollHeight;
|
||||
if (!skipSave && role !== 'thinking') {
|
||||
session.messages = session.messages || [];
|
||||
session.messages.push({ role: role, text: text });
|
||||
if (session.messages.length > 40) session.messages = session.messages.slice(-40);
|
||||
saveSession(session);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
window.neuronDemoSend = async function() {
|
||||
if (msgCount >= MAX) return;
|
||||
var input = document.getElementById('neuron-demo-text');
|
||||
var btn = document.getElementById('neuron-demo-send');
|
||||
if (!input || btn.disabled) return;
|
||||
var msg = input.value.trim();
|
||||
if (!msg) return;
|
||||
input.value = '';
|
||||
btn.disabled = true;
|
||||
addMsg('user', msg);
|
||||
|
||||
var thinking = document.createElement('div');
|
||||
thinking.className = 'demo-msg demo-msg-thinking';
|
||||
var thAvatar = document.createElement('div');
|
||||
thAvatar.className = 'demo-msg-avatar';
|
||||
var thImg = document.createElement('img');
|
||||
thImg.src = '/assets/brand/neuron-brain.png'; thImg.alt = 'Neuron';
|
||||
thAvatar.appendChild(thImg); thinking.appendChild(thAvatar);
|
||||
var thDots = document.createElement('span');
|
||||
thDots.className = 'demo-msg-thinking-dots';
|
||||
thDots.innerHTML = '<span></span><span></span><span></span>';
|
||||
thinking.appendChild(thDots);
|
||||
var thMsgsEl = document.getElementById('neuron-demo-messages');
|
||||
if (thMsgsEl) { thMsgsEl.appendChild(thinking); thMsgsEl.scrollTop = thMsgsEl.scrollHeight; }
|
||||
|
||||
try {
|
||||
var hist = (session.messages || []).slice(-20).filter(function(m) { return m.role !== 'thinking'; }).map(function(m) {
|
||||
return { role: m.role === 'ai' ? 'assistant' : 'user', content: m.text };
|
||||
});
|
||||
var activated_nodes = _ra(session._m, msg);
|
||||
var questionsRemaining = Math.max(0, (MAX - msgCount) - 1);
|
||||
var r = await fetch('/api/demo', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
message: msg,
|
||||
history: hist,
|
||||
cf_token: turnstileVerified && !session._cfSent ? turnstileToken : '',
|
||||
uid: session.uid || '',
|
||||
activated_nodes: activated_nodes,
|
||||
engram_node_count: (session._m && session._m.nodes) ? session._m.nodes.length : 0,
|
||||
questions_remaining: questionsRemaining,
|
||||
is_last_question: questionsRemaining === 0
|
||||
})
|
||||
});
|
||||
var d = await r.json();
|
||||
if (thinking) thinking.remove();
|
||||
_um(session, d.sn, d.se);
|
||||
var reply = d.response || d.reply || d.message || '';
|
||||
var isError = !reply || reply === 'Stepped out for a moment. Try again.';
|
||||
if (!isError) {
|
||||
msgCount++;
|
||||
session.count = msgCount;
|
||||
saveSession(session);
|
||||
updateCountdown();
|
||||
if (msgCount >= MAX && input) { input.disabled = true; input.placeholder = 'Interaction limit reached'; }
|
||||
}
|
||||
addMsg('ai', reply || 'Stepped out for a moment. Try again.');
|
||||
} catch(e) {
|
||||
if (thinking) thinking.remove();
|
||||
addMsg('ai', 'Stepped out for a moment. Try again.');
|
||||
}
|
||||
if (msgCount < MAX && btn) btn.disabled = false;
|
||||
if (input) input.focus();
|
||||
};
|
||||
|
||||
var inp = document.getElementById('neuron-demo-text');
|
||||
if (inp) {
|
||||
inp.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); window.neuronDemoSend(); }
|
||||
});
|
||||
}
|
||||
})()")
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// checkout-auth.el -- Checkout Supabase auth: OAuth, email sign-in/sign-up.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
//
|
||||
// Exposed globals: signInWith(provider), signUpWithEmail(), signInWithEmail(),
|
||||
// showSignIn(), showSignUp(), resetPassword()
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
var supabaseClient;
|
||||
|
||||
function initSupabase(cb) {
|
||||
if (supabaseClient) { cb(); return; }
|
||||
fetch('/api/supabase-config')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(cfg) {
|
||||
supabaseClient = window.supabase.createClient(cfg.url, cfg.anon_key, {
|
||||
auth: { flowType: 'implicit' }
|
||||
});
|
||||
cb();
|
||||
})
|
||||
.catch(function(err) {});
|
||||
}
|
||||
|
||||
function showAuthMessage(msg, isError) {
|
||||
var el = document.getElementById('auth-message');
|
||||
if (!el) return;
|
||||
el.textContent = msg;
|
||||
el.style.display = 'block';
|
||||
el.style.color = isError ? '#c0392b' : '#2ecc71';
|
||||
}
|
||||
|
||||
function revealPaymentForm(user) {
|
||||
if (user && user.id) { window._neuronSupaId = user.id; }
|
||||
var auth = document.getElementById('auth-section');
|
||||
if (auth) auth.style.display = 'none';
|
||||
var payment = document.getElementById('payment-section');
|
||||
if (payment) payment.style.display = '';
|
||||
|
||||
if (user) {
|
||||
var badge = document.getElementById('auth-badge');
|
||||
var name = user.user_metadata && user.user_metadata.full_name
|
||||
? user.user_metadata.full_name : user.email || '';
|
||||
if (badge) {
|
||||
badge.innerHTML = '<div class=\"checkout-auth-badge\">'
|
||||
+ '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M20 6L9 17l-5-5\" stroke=\"#0052A0\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>'
|
||||
+ 'Signed in as <strong>' + name + '</strong></div>';
|
||||
badge.style.display = '';
|
||||
}
|
||||
var prompt = document.getElementById('signin-prompt');
|
||||
if (prompt) prompt.style.display = 'none';
|
||||
}
|
||||
|
||||
if (user && user.email) {
|
||||
var emailEl = document.getElementById('buyer-email');
|
||||
if (emailEl) emailEl.value = user.email;
|
||||
}
|
||||
|
||||
var userEmail = user ? (user.email || '') : '';
|
||||
var userName = user ? ((user.user_metadata && user.user_metadata.full_name) || '') : '';
|
||||
if (typeof initStripe === 'function') initStripe(userEmail, userName);
|
||||
}
|
||||
|
||||
function checkExistingSession() {
|
||||
initSupabase(function() {
|
||||
supabaseClient.auth.getUser().then(function(res) {
|
||||
if (res.data && res.data.user) { revealPaymentForm(res.data.user); }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleAuthRedirect() {
|
||||
initSupabase(function() {
|
||||
supabaseClient.auth.onAuthStateChange(function(event, session) {
|
||||
if ((event === 'SIGNED_IN' || event === 'INITIAL_SESSION') && session && session.user) {
|
||||
revealPaymentForm(session.user);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.signInWith = function(provider) {
|
||||
var btns = document.querySelectorAll('.checkout-social-btn');
|
||||
btns.forEach(function(b) { b.disabled = true; });
|
||||
initSupabase(function() {
|
||||
supabaseClient.auth.signInWithOAuth({
|
||||
provider: provider,
|
||||
options: { redirectTo: window.location.href }
|
||||
}).then(function(result) {
|
||||
if (result.error) {
|
||||
showAuthMessage(result.error.message || 'Sign-in failed. Please try again.', true);
|
||||
btns.forEach(function(b) { b.disabled = false; });
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.signUpWithEmail = function() {
|
||||
var email = document.getElementById('auth-email').value.trim();
|
||||
var password = document.getElementById('auth-password').value;
|
||||
if (!email || !password) { showAuthMessage('Please enter your email and a password.', true); return; }
|
||||
if (password.length < 8) { showAuthMessage('Password must be at least 8 characters.', true); return; }
|
||||
initSupabase(function() {
|
||||
supabaseClient.auth.signUp({ email: email, password: password }).then(function(result) {
|
||||
if (result.error) { showAuthMessage(result.error.message, true); return; }
|
||||
if (result.data && result.data.session) {
|
||||
revealPaymentForm(result.data.session.user);
|
||||
} else {
|
||||
showAuthMessage('Check your email to confirm your account, then come back to complete your purchase.', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.showSignIn = function() {
|
||||
var form = document.getElementById('email-auth-form');
|
||||
if (!form) return;
|
||||
var btn = form.querySelector('.checkout-email-btn');
|
||||
if (btn) { btn.textContent = 'Sign in →'; btn.onclick = window.signInWithEmail; }
|
||||
};
|
||||
|
||||
window.showSignUp = function() {
|
||||
var form = document.getElementById('email-auth-form');
|
||||
if (!form) return;
|
||||
var btn = form.querySelector('.checkout-email-btn');
|
||||
if (btn) { btn.textContent = 'Create account →'; btn.onclick = window.signUpWithEmail; }
|
||||
};
|
||||
|
||||
window.signInWithEmail = function() {
|
||||
var email = document.getElementById('auth-email').value.trim();
|
||||
var password = document.getElementById('auth-password').value;
|
||||
if (!email || !password) { showAuthMessage('Please enter your email and password.', true); return; }
|
||||
initSupabase(function() {
|
||||
supabaseClient.auth.signInWithPassword({ email: email, password: password }).then(function(result) {
|
||||
if (result.error) { showAuthMessage(result.error.message, true); return; }
|
||||
revealPaymentForm(result.data.session.user);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.resetPassword = function() {
|
||||
var email = document.getElementById('auth-email').value.trim();
|
||||
if (!email) { showAuthMessage('Enter your email address above first.', true); return; }
|
||||
initSupabase(function() {
|
||||
supabaseClient.auth.resetPasswordForEmail(email, {
|
||||
redirectTo: window.location.origin + '/checkout?plan=' + (new URLSearchParams(window.location.search).get('plan') || 'professional')
|
||||
}).then(function(result) {
|
||||
if (result.error) { showAuthMessage(result.error.message, true); }
|
||||
else { showAuthMessage('Password reset email sent. Check your inbox.', false); }
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
handleAuthRedirect();
|
||||
checkExistingSession();
|
||||
})()")
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// checkout-free.el -- Free plan: reveal payment section after auth completes.
|
||||
// Watches the auth-badge element; when it becomes visible, shows payment-section.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
var pay = document.getElementById('payment-section');
|
||||
if (!pay) return;
|
||||
var timer = setInterval(function() {
|
||||
var badge = document.getElementById('auth-badge');
|
||||
if (badge && badge.offsetParent !== null) {
|
||||
pay.style.display = '';
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 150);
|
||||
})()")
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
// checkout-stripe.el -- Stripe Payment Element setup and form submission.
|
||||
// Reads NEURON_CFG.plan and NEURON_CFG.pub_key from window.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
//
|
||||
// Required globals: window.NEURON_CFG.plan, window.NEURON_CFG.pub_key
|
||||
// Required CDN: Stripe.js loaded before this script
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
var cfg = window.NEURON_CFG || {};
|
||||
var PLAN = cfg.plan || '';
|
||||
var STRIPE_PK = cfg.pub_key || '';
|
||||
var stripe, elements;
|
||||
|
||||
function waitForStripe(cb) {
|
||||
if (window.Stripe) { cb(); return; }
|
||||
setTimeout(function() { waitForStripe(cb); }, 50);
|
||||
}
|
||||
|
||||
function showMessage(msg) {
|
||||
var el = document.getElementById('payment-message');
|
||||
if (el) { el.textContent = msg; el.style.display = 'block'; }
|
||||
}
|
||||
|
||||
function setLoading(loading) {
|
||||
var btn = document.getElementById('submit-btn');
|
||||
var label = document.getElementById('submit-label');
|
||||
var spinner = document.getElementById('submit-spinner');
|
||||
if (btn) btn.disabled = loading;
|
||||
if (label) label.style.display = loading ? 'none' : '';
|
||||
if (spinner) spinner.style.display = loading ? '' : 'none';
|
||||
}
|
||||
|
||||
window._neuronMode = 'payment';
|
||||
var paymentEl = null;
|
||||
|
||||
function appearance() {
|
||||
return {
|
||||
theme: 'flat',
|
||||
variables: {
|
||||
colorPrimary: '#0052A0',
|
||||
colorBackground: '#ffffff',
|
||||
colorText: '#1A1A2E',
|
||||
colorDanger: '#c0392b',
|
||||
colorTextPlaceholder: '#9B9BAD',
|
||||
borderRadius: '0px',
|
||||
fontFamily: 'system-ui, -apple-system, sans-serif',
|
||||
fontSizeBase: '15px',
|
||||
fontWeightNormal: '300',
|
||||
spacingUnit: '4px'
|
||||
},
|
||||
rules: {
|
||||
'.Input': { border: '1px solid rgba(0,82,160,.22)', boxShadow: 'none', padding: '10px 14px' },
|
||||
'.Input:focus': { border: '1px solid rgba(0,82,160,.6)', boxShadow: '0 0 0 3px rgba(0,82,160,.08)', outline: 'none' },
|
||||
'.Label': { fontSize: '11px', fontWeight: '500', letterSpacing: '.06em', textTransform: 'uppercase', color: '#6B6B7E', marginBottom: '6px' },
|
||||
'.Tab': { border: '1px solid rgba(0,82,160,.18)', boxShadow: 'none' },
|
||||
'.Tab--selected':{ border: '1px solid rgba(0,82,160,.5)', boxShadow: '0 0 0 2px rgba(0,82,160,.12)' },
|
||||
'.Error': { color: '#c0392b' }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function currentTiming() {
|
||||
var later = document.getElementById('timing-later');
|
||||
return (later && later.checked) ? 'later' : 'now';
|
||||
}
|
||||
|
||||
function fetchAndMount() {
|
||||
var submitBtn = document.getElementById('submit-btn');
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
if (paymentEl) { try { paymentEl.unmount(); } catch(e) {} paymentEl = null; }
|
||||
var hostEl = document.getElementById('payment-element');
|
||||
if (hostEl && !document.querySelector('.checkout-element-loading')) {
|
||||
var d = document.createElement('div');
|
||||
d.className = 'checkout-element-loading';
|
||||
d.textContent = 'Loading payment form...';
|
||||
hostEl.appendChild(d);
|
||||
}
|
||||
var timing = currentTiming();
|
||||
return fetch('/api/payment-intent', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ plan: PLAN, timing: timing })
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.error === 'sold_out') {
|
||||
showMessage('All 1,000 Founding Member spots have been claimed. Thank you for your interest - please consider the Professional plan.');
|
||||
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Sold out'; }
|
||||
return;
|
||||
}
|
||||
if (!data.client_secret) { showMessage('Unable to initialise payment. Please try again.'); return; }
|
||||
window._neuronMode = data.setup_mode ? 'setup' : 'payment';
|
||||
window._neuronPiId = data.id || (data.client_secret ? data.client_secret.split('_secret_')[0] : '');
|
||||
var submitLabel = document.getElementById('submit-label');
|
||||
if (submitLabel) {
|
||||
submitLabel.textContent = window._neuronMode === 'setup'
|
||||
? 'Save my card - no charge today →'
|
||||
: 'Complete purchase →';
|
||||
}
|
||||
waitForStripe(function() {
|
||||
if (!stripe) stripe = Stripe(STRIPE_PK);
|
||||
elements = stripe.elements({ clientSecret: data.client_secret, appearance: appearance() });
|
||||
paymentEl = elements.create('payment', {
|
||||
fields: { billingDetails: { name: 'never', email: 'never' } }
|
||||
});
|
||||
paymentEl.mount('#payment-element');
|
||||
paymentEl.on('ready', function() {
|
||||
var ld = document.querySelector('.checkout-element-loading');
|
||||
if (ld) ld.remove();
|
||||
if (submitBtn) submitBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function() {
|
||||
showMessage('Unable to connect. Please check your connection and try again.');
|
||||
});
|
||||
}
|
||||
|
||||
fetchAndMount();
|
||||
var tNow = document.getElementById('timing-now');
|
||||
var tLater = document.getElementById('timing-later');
|
||||
if (tNow) tNow.addEventListener('change', fetchAndMount);
|
||||
if (tLater) tLater.addEventListener('change', fetchAndMount);
|
||||
|
||||
var form = document.getElementById('payment-form');
|
||||
if (form) form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
if (!stripe || !elements) return;
|
||||
|
||||
var attestCb = document.getElementById('founding-attest-cb');
|
||||
if (attestCb && !attestCb.checked) {
|
||||
var warn = document.getElementById('attest-warn');
|
||||
if (warn) warn.style.display = 'block';
|
||||
attestCb.closest('label').scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
return;
|
||||
}
|
||||
|
||||
var name = document.getElementById('buyer-name').value.trim();
|
||||
var email = document.getElementById('buyer-email').value.trim();
|
||||
if (!name || !email) { showMessage('Please enter your name and email.'); return; }
|
||||
|
||||
if (attestCb) {
|
||||
try {
|
||||
await fetch('/api/attest', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
plan: PLAN, name: name, email: email,
|
||||
timestamp: new Date().toISOString(),
|
||||
attestation: 'I am joining as a genuine early user, not to extract proprietary information about Neuron technology, architecture, or roadmap. I will engage in good faith. I understand that if this is not my intent, a different plan is a better fit.',
|
||||
user_agent: navigator.userAgent
|
||||
})
|
||||
});
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
var pmsg = document.getElementById('payment-message');
|
||||
if (pmsg) pmsg.style.display = 'none';
|
||||
|
||||
if (window._neuronPiId) {
|
||||
try {
|
||||
await fetch('/api/link-customer', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
pi_id: window._neuronPiId,
|
||||
email: email,
|
||||
name: name,
|
||||
plan: PLAN,
|
||||
timing: currentTiming(),
|
||||
mode: window._neuronMode || 'payment',
|
||||
supabase_user_id: window._neuronSupaId || ''
|
||||
})
|
||||
});
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
var confirmParams = {
|
||||
return_url: window.location.origin + '/account?welcome=1',
|
||||
payment_method_data: { billing_details: { name: name, email: email } }
|
||||
};
|
||||
var result;
|
||||
if (window._neuronMode === 'setup') {
|
||||
result = await stripe.confirmSetup({ elements: elements, confirmParams: confirmParams });
|
||||
} else {
|
||||
confirmParams.receipt_email = email;
|
||||
result = await stripe.confirmPayment({ elements: elements, confirmParams: confirmParams });
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
showMessage(result.error.message || (window._neuronMode === 'setup'
|
||||
? 'Could not save your card. Please try again.'
|
||||
: 'Payment failed. Please try again.'));
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
})()")
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// enterprise.el -- Enterprise inquiry form submission and headcount filter.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
//
|
||||
// Exposed globals: entHeadcountChange(val)
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
var form = document.getElementById('enterprise-form');
|
||||
var submitBtn = document.getElementById('ent-submit');
|
||||
var successDiv = document.getElementById('enterprise-success');
|
||||
var errorDiv = document.getElementById('ent-form-error');
|
||||
|
||||
if (!form) return;
|
||||
|
||||
window.entHeadcountChange = function(val) {
|
||||
var msgSecondary = document.getElementById('ent-filter-msg-secondary');
|
||||
var msgYes = document.getElementById('ent-filter-msg-yes');
|
||||
if (msgSecondary) msgSecondary.style.display = val === 'secondary' ? 'block' : 'none';
|
||||
if (msgYes) msgYes.style.display = val === 'yes' ? 'block' : 'none';
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = val === 'yes';
|
||||
submitBtn.style.opacity = val === 'yes' ? '0.35' : '1';
|
||||
submitBtn.style.cursor = val === 'yes' ? 'not-allowed' : 'pointer';
|
||||
}
|
||||
};
|
||||
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
var headcount = document.getElementById('ent-headcount').value;
|
||||
if (headcount === 'yes') {
|
||||
var msgYes = document.getElementById('ent-filter-msg-yes');
|
||||
if (msgYes) msgYes.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
var name = document.getElementById('ent-name').value.trim();
|
||||
var email = document.getElementById('ent-email').value.trim();
|
||||
var company = document.getElementById('ent-company').value.trim();
|
||||
var size = document.getElementById('ent-size').value;
|
||||
var useCase = document.getElementById('ent-use').value.trim();
|
||||
|
||||
if (!name || !email || !company || !size || !useCase || !headcount) {
|
||||
if (errorDiv) { errorDiv.textContent = 'Please fill out all fields.'; errorDiv.style.display = 'block'; }
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorDiv) errorDiv.style.display = 'none';
|
||||
if (submitBtn) { submitBtn.textContent = 'Sending...'; submitBtn.disabled = true; }
|
||||
|
||||
fetch('/api/enterprise-inquiry', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ name: name, email: email, company: company, size: size, use_case: useCase, headcount: headcount })
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function() {
|
||||
if (form) form.style.display = 'none';
|
||||
if (successDiv) successDiv.style.display = 'block';
|
||||
})
|
||||
.catch(function() {
|
||||
if (submitBtn) { submitBtn.textContent = 'Send inquiry →'; submitBtn.disabled = false; }
|
||||
if (errorDiv) { errorDiv.textContent = 'Something went wrong. Email enterprise@neurontechnologies.ai directly.'; errorDiv.style.display = 'block'; }
|
||||
});
|
||||
});
|
||||
})()")
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// environmental.el -- Environmental page efficiency calculator slider.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
var slider = document.getElementById('calc-slider');
|
||||
var spendEl = document.getElementById('calc-spend');
|
||||
var savingsEl = document.getElementById('calc-savings');
|
||||
if (!slider) return;
|
||||
function update() {
|
||||
var monthly = parseInt(slider.value, 10);
|
||||
var annual = Math.round(monthly * 0.35 * 12);
|
||||
if (spendEl) spendEl.textContent = '$' + monthly;
|
||||
if (savingsEl) savingsEl.textContent = '$' + annual;
|
||||
}
|
||||
slider.addEventListener('input', update);
|
||||
update();
|
||||
})()")
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
// gallery.el -- Gallery page: search/filter, sort, nav scroll, Supabase voting.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
//
|
||||
// Required globals: window.NEURON_CFG.supabase_url, window.NEURON_CFG.supabase_anon_key
|
||||
// Required CDN: supabase-js@2
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
// Nav scroll effect
|
||||
var nav = document.getElementById('nav');
|
||||
if (nav) window.addEventListener('scroll', function() {
|
||||
nav.classList.toggle('scrolled', window.scrollY > 10);
|
||||
}, { passive: true });
|
||||
|
||||
// Hamburger
|
||||
var btn = document.getElementById('nav-hamburger');
|
||||
var menu = document.getElementById('nav-mobile');
|
||||
if (btn && menu) {
|
||||
function navClose() { menu.classList.remove('open'); btn.setAttribute('aria-expanded','false'); }
|
||||
function navOpen() { menu.classList.add('open'); btn.setAttribute('aria-expanded','true'); }
|
||||
btn.addEventListener('click', function(e) { e.stopPropagation(); menu.classList.contains('open') ? navClose() : navOpen(); });
|
||||
menu.querySelectorAll('a').forEach(function(a) { a.addEventListener('click', navClose); });
|
||||
document.addEventListener('click', function(e) { if (!nav.contains(e.target)) navClose(); });
|
||||
document.addEventListener('keydown', function(e) { if (e.key === 'Escape') navClose(); });
|
||||
window.addEventListener('resize', function() { if (window.innerWidth > 1060) navClose(); });
|
||||
}
|
||||
|
||||
// Dropdown
|
||||
var ddBtn = document.querySelector('.nav-dropdown-btn');
|
||||
var dd = document.querySelector('.nav-dropdown');
|
||||
if (ddBtn && dd) {
|
||||
ddBtn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
var isOpen = dd.classList.contains('open');
|
||||
dd.classList.toggle('open');
|
||||
ddBtn.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
|
||||
});
|
||||
dd.querySelectorAll('.nav-dropdown-item').forEach(function(a) { a.addEventListener('click', function() { dd.classList.remove('open'); }); });
|
||||
document.addEventListener('click', function() { dd.classList.remove('open'); });
|
||||
}
|
||||
|
||||
// Search
|
||||
var searchEl = document.getElementById('gal-search');
|
||||
var grid = document.getElementById('gallery-grid');
|
||||
var noResults = document.getElementById('no-results');
|
||||
|
||||
function filterCards() {
|
||||
var q = (searchEl ? searchEl.value : '').toLowerCase().trim();
|
||||
var cards = grid ? grid.querySelectorAll('.gal-card') : [];
|
||||
var visible = 0;
|
||||
cards.forEach(function(c) {
|
||||
var match = !q || c.textContent.toLowerCase().indexOf(q) !== -1;
|
||||
c.classList.toggle('hidden', !match);
|
||||
if (match) visible++;
|
||||
});
|
||||
if (noResults) noResults.style.display = visible === 0 && q ? 'block' : 'none';
|
||||
}
|
||||
|
||||
if (searchEl) searchEl.addEventListener('input', filterCards);
|
||||
|
||||
// Sort
|
||||
window.setSort = function(mode, btn) {
|
||||
document.querySelectorAll('.sort-btn').forEach(function(b) { b.classList.remove('active'); });
|
||||
btn.classList.add('active');
|
||||
if (!grid) return;
|
||||
var cards = Array.from(grid.querySelectorAll('.gal-card'));
|
||||
cards.sort(function(a, b) {
|
||||
if (mode === 'top') {
|
||||
return parseInt(b.getAttribute('data-score') || '0') - parseInt(a.getAttribute('data-score') || '0');
|
||||
} else {
|
||||
return parseInt(b.getAttribute('data-ts') || '0') - parseInt(a.getAttribute('data-ts') || '0');
|
||||
}
|
||||
});
|
||||
cards.forEach(function(c) { grid.appendChild(c); });
|
||||
};
|
||||
|
||||
// Voting via Supabase
|
||||
var cfg = window.NEURON_CFG || {};
|
||||
var sbUrl = cfg.supabase_url;
|
||||
var sbKey = cfg.supabase_anon_key;
|
||||
if (!sbUrl || !sbKey) return;
|
||||
var sb = window.supabase.createClient(sbUrl, sbKey);
|
||||
var token = null;
|
||||
var votes = {};
|
||||
|
||||
function getCtrl(sid) {
|
||||
var found = null;
|
||||
document.querySelectorAll('.vote-controls').forEach(function(c) {
|
||||
if (c.getAttribute('data-share-id') === sid) found = c;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
function applyState(ctrl, state) {
|
||||
var sid = ctrl.getAttribute('data-share-id');
|
||||
votes[sid] = state.user_vote || 'none';
|
||||
var scoreEl = ctrl.querySelector('.vote-score');
|
||||
if (scoreEl && state.score != null) scoreEl.textContent = state.score;
|
||||
var upBtn = ctrl.querySelector('.vote-btn.vote-up');
|
||||
var dnBtn = ctrl.querySelector('.vote-btn.vote-down');
|
||||
if (upBtn) { upBtn.disabled = false; upBtn.classList.toggle('is-active', state.user_vote === 'up'); }
|
||||
if (dnBtn) { dnBtn.disabled = false; dnBtn.classList.toggle('is-active', state.user_vote === 'down'); }
|
||||
}
|
||||
|
||||
function loadVoteState(sid) {
|
||||
var url = '/api/vote-state/' + sid;
|
||||
if (token) url += '?access_token=' + encodeURIComponent(token);
|
||||
fetch(url).then(function(r) { return r.json(); }).then(function(d) {
|
||||
var ctrl = getCtrl(sid);
|
||||
if (ctrl) applyState(ctrl, d);
|
||||
}).catch(function() {});
|
||||
}
|
||||
|
||||
function loadAll() {
|
||||
document.querySelectorAll('.vote-controls').forEach(function(ctrl) {
|
||||
var id = ctrl.getAttribute('data-share-id');
|
||||
if (id) loadVoteState(id);
|
||||
});
|
||||
}
|
||||
|
||||
function castVote(sid, direction) {
|
||||
if (!token) { showSignIn(); return; }
|
||||
var ctrl = getCtrl(sid);
|
||||
var btns = ctrl ? ctrl.querySelectorAll('.vote-btn') : [];
|
||||
btns.forEach(function(b) { b.disabled = true; b.classList.add('is-loading'); });
|
||||
fetch('/api/vote', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({access_token: token, id: sid, direction: direction})
|
||||
}).then(function(r) { return r.json(); }).then(function(d) {
|
||||
if (ctrl && d.ok) applyState(ctrl, d);
|
||||
btns.forEach(function(b) { b.disabled = false; b.classList.remove('is-loading'); });
|
||||
}).catch(function() {
|
||||
btns.forEach(function(b) { b.disabled = false; b.classList.remove('is-loading'); });
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('.vote-controls').forEach(function(ctrl) {
|
||||
ctrl.querySelectorAll('.vote-btn').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var sid = ctrl.getAttribute('data-share-id');
|
||||
if (!token) { showSignIn(); return; }
|
||||
var dir = btn.getAttribute('data-direction');
|
||||
var cur = votes[sid] || 'none';
|
||||
castVote(sid, cur === dir ? 'none' : dir);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var modal = document.getElementById('signin-modal');
|
||||
var cancelEl = document.getElementById('signin-cancel');
|
||||
var sendEl = document.getElementById('signin-send');
|
||||
var emailEl = document.getElementById('signin-email');
|
||||
var msgEl = document.getElementById('signin-msg');
|
||||
|
||||
function showSignIn() { if (modal) modal.classList.add('open'); if (emailEl) emailEl.focus(); }
|
||||
|
||||
if (cancelEl) cancelEl.addEventListener('click', function() { modal.classList.remove('open'); });
|
||||
if (modal) modal.addEventListener('click', function(e) { if (e.target === modal) modal.classList.remove('open'); });
|
||||
if (sendEl) sendEl.addEventListener('click', function() {
|
||||
var email = emailEl ? emailEl.value.trim() : '';
|
||||
if (!email) { if (msgEl) msgEl.textContent = 'Please enter your email.'; return; }
|
||||
sendEl.disabled = true;
|
||||
if (msgEl) msgEl.textContent = 'Sending...';
|
||||
sb.auth.signInWithOtp({ email: email, options: { emailRedirectTo: window.location.href } })
|
||||
.then(function(r) {
|
||||
sendEl.disabled = false;
|
||||
if (msgEl) msgEl.textContent = r.error
|
||||
? (r.error.message || 'Error. Try again.')
|
||||
: 'Check your email for a sign-in link.';
|
||||
});
|
||||
});
|
||||
if (emailEl) emailEl.addEventListener('keydown', function(e) { if (e.key === 'Enter' && sendEl) sendEl.click(); });
|
||||
|
||||
sb.auth.onAuthStateChange(function(event, session) {
|
||||
token = session ? session.access_token : null;
|
||||
if (token && modal) modal.classList.remove('open');
|
||||
loadAll();
|
||||
});
|
||||
|
||||
sb.auth.getSession().then(function(r) {
|
||||
token = r.data && r.data.session ? r.data.session.access_token : null;
|
||||
loadAll();
|
||||
});
|
||||
})()")
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// main.el -- Share page voting, copy-for-platform social sharing.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
//
|
||||
// Required globals: window.NEURON_CFG.id, window.NEURON_CFG.card_url
|
||||
// Exposed globals: copyForPlatform(platform, btn)
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
var cfg = window.NEURON_CFG || {};
|
||||
var shareId = cfg.id || '';
|
||||
var cardUrl = cfg.card_url || '';
|
||||
|
||||
// Copy-for-platform: format and copy share text for different social platforms
|
||||
window.copyForPlatform = function(platform, btn) {
|
||||
var text = '';
|
||||
var url = window.location.href;
|
||||
if (platform === 'tiktok') {
|
||||
text = 'Neuron said something interesting. Watch this. ' + url;
|
||||
} else if (platform === 'snapchat') {
|
||||
text = url;
|
||||
} else {
|
||||
text = url;
|
||||
}
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
if (btn) {
|
||||
btn.classList.add('copied');
|
||||
setTimeout(function() { btn.classList.remove('copied'); }, 1500);
|
||||
}
|
||||
}).catch(function() {});
|
||||
};
|
||||
|
||||
// Vote buttons on share page
|
||||
var voteUp = document.getElementById('vote-up');
|
||||
var voteDown = document.getElementById('vote-down');
|
||||
var voteScore = document.getElementById('vote-score');
|
||||
var voted = null;
|
||||
|
||||
function updateVoteUI(direction, score) {
|
||||
if (voteUp) voteUp.classList.toggle('voted-up', direction === 'up');
|
||||
if (voteDown) voteDown.classList.toggle('voted-down', direction === 'down');
|
||||
if (voteScore && score != null) voteScore.textContent = score;
|
||||
}
|
||||
|
||||
function castVote(direction) {
|
||||
var next = voted === direction ? 'none' : direction;
|
||||
voted = next === 'none' ? null : next;
|
||||
fetch('/api/vote', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ id: shareId, direction: next })
|
||||
}).then(function(r) { return r.json(); }).then(function(d) {
|
||||
if (d.ok) updateVoteUI(next === 'none' ? null : next, d.score);
|
||||
}).catch(function() {});
|
||||
}
|
||||
|
||||
if (voteUp) voteUp.addEventListener('click', function() { castVote('up'); });
|
||||
if (voteDown) voteDown.addEventListener('click', function() { castVote('down'); });
|
||||
|
||||
// Load initial vote state
|
||||
if (shareId) {
|
||||
fetch('/api/vote-state/' + shareId)
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) { updateVoteUI(d.user_vote || null, d.score); })
|
||||
.catch(function() {});
|
||||
}
|
||||
})()")
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// marketplace.el -- Marketplace developer interest form submission.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
var form = document.getElementById('dev-form');
|
||||
if (!form) return;
|
||||
form.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
var msg = document.getElementById('dev-msg');
|
||||
var btn = form.querySelector('button[type=submit]');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Sending...';
|
||||
try {
|
||||
var r = await fetch('/api/developer-interest', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
name: document.getElementById('dev-name').value,
|
||||
email: document.getElementById('dev-email').value,
|
||||
idea: document.getElementById('dev-idea').value
|
||||
})
|
||||
});
|
||||
msg.style.display = 'block';
|
||||
if (r.ok) {
|
||||
msg.textContent = 'Got it. Will review it personally and reach out.';
|
||||
msg.style.color = 'var(--navy)';
|
||||
form.reset();
|
||||
} else {
|
||||
msg.textContent = 'Something went wrong. Email developers@neurontechnologies.ai directly.';
|
||||
msg.style.color = '#c44';
|
||||
}
|
||||
} catch(err) {
|
||||
msg.style.display = 'block';
|
||||
msg.textContent = 'Connection error. Email developers@neurontechnologies.ai directly.';
|
||||
msg.style.color = '#c44';
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Send interest →';
|
||||
});
|
||||
})()")
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// nav.el -- Navigation hamburger menu and Mission dropdown.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
var btn = document.getElementById('nav-hamburger');
|
||||
var menu = document.getElementById('nav-mobile');
|
||||
var nav = document.getElementById('nav');
|
||||
if (!btn || !menu) return;
|
||||
|
||||
function close() {
|
||||
menu.classList.remove('open');
|
||||
btn.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
function open() {
|
||||
menu.classList.add('open');
|
||||
btn.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
function toggle() {
|
||||
if (menu.classList.contains('open')) { close(); } else { open(); }
|
||||
}
|
||||
|
||||
btn.addEventListener('click', function(e) { e.stopPropagation(); toggle(); });
|
||||
|
||||
var ddBtn = document.querySelector('.nav-dropdown-btn');
|
||||
var dd = document.querySelector('.nav-dropdown');
|
||||
if (ddBtn && dd) {
|
||||
ddBtn.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
var isOpen = dd.classList.contains('open');
|
||||
dd.classList.toggle('open');
|
||||
ddBtn.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
|
||||
});
|
||||
dd.querySelectorAll('.nav-dropdown-item').forEach(function(a) {
|
||||
a.addEventListener('click', function() { dd.classList.remove('open'); });
|
||||
});
|
||||
document.addEventListener('click', function() { dd.classList.remove('open'); });
|
||||
}
|
||||
|
||||
menu.querySelectorAll('a').forEach(function(a) {
|
||||
a.addEventListener('click', close);
|
||||
});
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!nav.contains(e.target)) { close(); }
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') { close(); }
|
||||
});
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
if (window.innerWidth > 1060) { close(); }
|
||||
});
|
||||
})()")
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// styles.el -- Landing page JS: nav scroll, scroll-reveal, founding counter polling,
|
||||
// checkout button routing.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
|
||||
fn main() -> Void {
|
||||
native_js("(function() {
|
||||
'use strict';
|
||||
|
||||
// Nav scroll effect
|
||||
var nav = document.getElementById('nav');
|
||||
if (nav) {
|
||||
window.addEventListener('scroll', function() {
|
||||
if (window.scrollY > 40) {
|
||||
nav.classList.add('scrolled');
|
||||
} else {
|
||||
nav.classList.remove('scrolled');
|
||||
}
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
// Auto-open chat if ?open=chat in URL
|
||||
if (typeof URLSearchParams !== 'undefined' && new URLSearchParams(window.location.search).get('open') === 'chat') {
|
||||
setTimeout(function() { if (typeof neuronDemoToggle === 'function') neuronDemoToggle(); }, 600);
|
||||
}
|
||||
|
||||
// Scroll reveal via IntersectionObserver
|
||||
var revealEls = document.querySelectorAll('.reveal');
|
||||
if ('IntersectionObserver' in window) {
|
||||
var observer = new IntersectionObserver(function(entries) {
|
||||
entries.forEach(function(entry) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('visible');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' });
|
||||
revealEls.forEach(function(el) { observer.observe(el); });
|
||||
} else {
|
||||
revealEls.forEach(function(el) { el.classList.add('visible'); });
|
||||
}
|
||||
|
||||
// Founding counter polling
|
||||
var prevSold = null;
|
||||
function updateFoundingUI(data) {
|
||||
var remaining = data.remaining;
|
||||
var sold = data.sold;
|
||||
var total = data.total;
|
||||
var pct = Math.round((sold / total) * 100);
|
||||
var flash = prevSold !== null && sold > prevSold;
|
||||
prevSold = sold;
|
||||
|
||||
var spotLabel = document.querySelector('.founding-spots-label');
|
||||
if (spotLabel) spotLabel.textContent = 'Only ' + remaining + ' left';
|
||||
var spotFill = document.querySelector('.founding-spots-fill');
|
||||
if (spotFill) spotFill.style.width = pct + '%';
|
||||
var spotSub = document.querySelector('.founding-spots-sub');
|
||||
if (spotSub) spotSub.textContent = sold + ' of ' + total + ' claimed';
|
||||
|
||||
var bannerCount = document.querySelector('.founding-banner-count');
|
||||
if (bannerCount) {
|
||||
bannerCount.textContent = remaining;
|
||||
if (flash) {
|
||||
bannerCount.style.color = '#0078D4';
|
||||
setTimeout(function() { bannerCount.style.color = ''; }, 1200);
|
||||
}
|
||||
}
|
||||
var bannerFill = document.querySelector('.founding-banner-fill');
|
||||
if (bannerFill) bannerFill.style.width = pct + '%';
|
||||
}
|
||||
|
||||
function pollFoundingCount() {
|
||||
fetch('/api/founding-count')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) { updateFoundingUI(data); })
|
||||
.catch(function() {});
|
||||
}
|
||||
pollFoundingCount();
|
||||
setInterval(pollFoundingCount, 90000);
|
||||
|
||||
// Hide chat widget on non-marketing pages
|
||||
if (window.location.pathname.indexOf('/checkout') === 0 ||
|
||||
window.location.pathname.indexOf('/account') === 0 ||
|
||||
window.location.pathname.indexOf('/legal') === 0 ||
|
||||
window.location.pathname.indexOf('/marketplace/success') === 0) {
|
||||
var demoBtn = document.getElementById('neuron-demo-btn');
|
||||
var demoPanel = document.getElementById('neuron-demo-panel');
|
||||
if (demoBtn) demoBtn.style.display = 'none';
|
||||
if (demoPanel) demoPanel.style.display = 'none';
|
||||
}
|
||||
|
||||
// Checkout buttons
|
||||
var checkoutBtns = document.querySelectorAll('[data-checkout]');
|
||||
checkoutBtns.forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
window.location.href = '/checkout?plan=' + btn.getAttribute('data-checkout');
|
||||
});
|
||||
});
|
||||
})()")
|
||||
}
|
||||
Reference in New Issue
Block a user