feat(native-el-ui): rewrite checkout/main to use el-html vessel, fix page_close linker error
- checkout.el and main.el: replace raw import of el-html vessel with direct extern fn declarations; implementations come from dist/elhtml_impl.c (c_source) - Add src/elhtml.el as reference file (all el-html extern fn declarations; not imported) - dist/elhtml_impl.c: pre-compiled el-html vessel C (strips int main + sample global) - dist/page_close.c: pre-compiled page_close implementation; elc OOMs after emitting the ~71KB page_open body and silently drops page_close, so supply it as c_source - manifest.el: add elhtml_impl.c and page_close.c as c_source entries - .gitignore: un-ignore dist/elhtml_impl.c and dist/page_close.c
This commit is contained in:
+293
-185
@@ -11,15 +11,63 @@
|
||||
// 4. User fills name, email, card - submits
|
||||
// 5. stripe.confirmPayment() → redirects to /marketplace/success
|
||||
|
||||
// el-html vessel — extern declarations (implementations in dist/elhtml_impl.c)
|
||||
extern fn el_escape(s: String) -> String
|
||||
extern fn el_text(s: String) -> String
|
||||
extern fn el_attr(name: String, value: String) -> String
|
||||
extern fn el_div(attrs: String, children: String) -> String
|
||||
extern fn el_section(attrs: String, children: String) -> String
|
||||
extern fn el_article(attrs: String, children: String) -> String
|
||||
extern fn el_header(attrs: String, children: String) -> String
|
||||
extern fn el_footer(attrs: String, children: String) -> String
|
||||
extern fn el_main(attrs: String, children: String) -> String
|
||||
extern fn el_nav(attrs: String, children: String) -> String
|
||||
extern fn el_aside(attrs: String, children: String) -> String
|
||||
extern fn el_ul(attrs: String, children: String) -> String
|
||||
extern fn el_ol(attrs: String, children: String) -> String
|
||||
extern fn el_li(attrs: String, children: String) -> String
|
||||
extern fn el_p(attrs: String, children: String) -> String
|
||||
extern fn el_span(attrs: String, children: String) -> String
|
||||
extern fn el_form(attrs: String, children: String) -> String
|
||||
extern fn el_h1(attrs: String, text: String) -> String
|
||||
extern fn el_h2(attrs: String, text: String) -> String
|
||||
extern fn el_h3(attrs: String, text: String) -> String
|
||||
extern fn el_h4(attrs: String, text: String) -> String
|
||||
extern fn el_button(attrs: String, label: String) -> String
|
||||
extern fn el_a(href: String, attrs: String, children: String) -> String
|
||||
extern fn el_input(type_attr: String, attrs: String) -> String
|
||||
extern fn el_textarea(attrs: String, value: String) -> String
|
||||
extern fn el_label(for_id: String, attrs: String, children: String) -> String
|
||||
extern fn el_img(src: String, alt: String, attrs: String) -> String
|
||||
extern fn el_strong(children: String) -> String
|
||||
extern fn el_em(children: String) -> String
|
||||
extern fn el_code(children: String) -> String
|
||||
extern fn el_pre(attrs: String, children: String) -> String
|
||||
extern fn el_hr() -> String
|
||||
extern fn el_br() -> String
|
||||
extern fn el_html_doc(lang: String, head_html: String, body_html: String) -> String
|
||||
extern fn el_meta(name: String, content: String) -> String
|
||||
extern fn el_meta_charset(charset: String) -> String
|
||||
extern fn el_link_stylesheet(href: String) -> String
|
||||
extern fn el_script_src(src: String, defer_load: Bool) -> String
|
||||
extern fn el_script_inline(js: String) -> String
|
||||
extern fn el_title(text: String) -> String
|
||||
|
||||
fn el_style_block(css: String) -> String {
|
||||
"<style>" + css + "</style>"
|
||||
}
|
||||
|
||||
fn checkout_nav_html() -> String {
|
||||
<nav id="nav">
|
||||
<div class="nav-inner">
|
||||
<a href="/" class="nav-logo" aria-label="Neuron home"><img src="/assets/brand/neuron-wordmark-on-light.png" srcset="/assets/brand/neuron-wordmark-on-light@2x.png 2x" alt="Neuron" height="28"></a>
|
||||
<div class="nav-links">
|
||||
<a href="/" class="nav-link">← Back</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
let logo_img: String = el_img(
|
||||
"/assets/brand/neuron-wordmark-on-light.png",
|
||||
"Neuron",
|
||||
"srcset=\"/assets/brand/neuron-wordmark-on-light@2x.png 2x\" height=\"28\""
|
||||
)
|
||||
let logo_link: String = el_a("/", "class=\"nav-logo\" aria-label=\"Neuron home\"", logo_img)
|
||||
let back_link: String = el_a("/", "class=\"nav-link\"", "← Back")
|
||||
let nav_links: String = el_div("class=\"nav-links\"", back_link)
|
||||
let nav_inner: String = el_div("class=\"nav-inner\"", logo_link + nav_links)
|
||||
el_nav("id=\"nav\"", nav_inner)
|
||||
}
|
||||
|
||||
fn checkout_page(plan: String, pub_key: String) -> String {
|
||||
@@ -38,216 +86,276 @@ fn checkout_page(plan: String, pub_key: String) -> String {
|
||||
let plan_cadence: String = if is_founding { "one-time" } else { if is_free { "forever" } else { "billed monthly" } }
|
||||
|
||||
let features_html: String = if is_founding {
|
||||
"<li>Everything in Professional - forever</li>
|
||||
<li>Neuron Inference when it launches - priced below the major APIs, forever</li>
|
||||
<li>Never pay again - lifetime updates included</li>
|
||||
<li>Founding member badge in the app</li>
|
||||
<li>Private founding member community</li>
|
||||
<li>Shape the roadmap - your votes carry more weight</li>
|
||||
<li>Beta features before general release</li>
|
||||
<li>Name in the credits</li>"
|
||||
el_li("", "Everything in Professional - forever")
|
||||
+ el_li("", "Neuron Inference when it launches - priced below the major APIs, forever")
|
||||
+ el_li("", "Never pay again - lifetime updates included")
|
||||
+ el_li("", "Founding member badge in the app")
|
||||
+ el_li("", "Private founding member community")
|
||||
+ el_li("", "Shape the roadmap - your votes carry more weight")
|
||||
+ el_li("", "Beta features before general release")
|
||||
+ el_li("", "Name in the credits")
|
||||
} else { if is_free {
|
||||
"<li>Persistent memory - never resets</li>
|
||||
<li>Local inference via Ollama (coming)</li>
|
||||
<li>Bring your own API keys</li>
|
||||
<li>3 marketplace plugins included</li>
|
||||
<li>Core built-in capabilities</li>
|
||||
<li>2 devices included</li>"
|
||||
el_li("", "Persistent memory - never resets")
|
||||
+ el_li("", "Local inference via Ollama (coming)")
|
||||
+ el_li("", "Bring your own API keys")
|
||||
+ el_li("", "3 marketplace plugins included")
|
||||
+ el_li("", "Core built-in capabilities")
|
||||
+ el_li("", "2 devices included")
|
||||
} else {
|
||||
"<li>Persistent memory - never resets</li>
|
||||
<li>Bring your own API keys (OpenAI, Anthropic, Grok...)</li>
|
||||
<li>Neuron Inference when it launches - Q3 2026, priced below the major APIs</li>
|
||||
<li>Unlimited projects</li>
|
||||
<li>Full plugin marketplace</li>
|
||||
<li>IDE, Slack, and more integrations</li>
|
||||
<li>Early access to new features</li>
|
||||
<li>2 devices included</li>"
|
||||
el_li("", "Persistent memory - never resets")
|
||||
+ el_li("", "Bring your own API keys (OpenAI, Anthropic, Grok...)")
|
||||
+ el_li("", "Neuron Inference when it launches - Q3 2026, priced below the major APIs")
|
||||
+ el_li("", "Unlimited projects")
|
||||
+ el_li("", "Full plugin marketplace")
|
||||
+ el_li("", "IDE, Slack, and more integrations")
|
||||
+ el_li("", "Early access to new features")
|
||||
+ el_li("", "2 devices included")
|
||||
} }
|
||||
|
||||
let nav_html: String = checkout_nav_html()
|
||||
|
||||
let main_html: String = <main style="min-height: 100vh; padding: clamp(6rem, 14vh, 9rem) 2rem 4rem;">
|
||||
<div class="checkout-shell">
|
||||
// ── Order summary (left column) ───────────────────────────────────────────
|
||||
|
||||
<!-- Left: order summary -->
|
||||
<div class="checkout-summary">
|
||||
<p class="label" style="margin-bottom: 1.5rem; color: var(--navy);">Your order</p>
|
||||
let price_row: String = el_div(
|
||||
"style=\"display: flex; align-items: baseline; gap: .5rem; margin-top: 1.25rem;\"",
|
||||
el_span("class=\"checkout-price\"", el_text(plan_price))
|
||||
+ el_span("class=\"checkout-cadence\"", el_text(plan_cadence))
|
||||
)
|
||||
|
||||
<div style="margin-bottom: 2rem;">
|
||||
<p class="checkout-plan-name">{plan_name}</p>
|
||||
<p class="checkout-plan-desc">{plan_desc}</p>
|
||||
<div style="display: flex; align-items: baseline; gap: .5rem; margin-top: 1.25rem;">
|
||||
<span class="checkout-price">{plan_price}</span>
|
||||
<span class="checkout-cadence">{plan_cadence}</span>
|
||||
</div>
|
||||
</div>
|
||||
let plan_block: String = el_div(
|
||||
"style=\"margin-bottom: 2rem;\"",
|
||||
el_p("class=\"checkout-plan-name\"", el_text(plan_name))
|
||||
+ el_p("class=\"checkout-plan-desc\"", el_text(plan_desc))
|
||||
+ price_row
|
||||
)
|
||||
|
||||
<div class="navy-line-left" style="width: 3rem; margin-bottom: 1.75rem;"></div>
|
||||
let summary_col: String = el_div(
|
||||
"class=\"checkout-summary\"",
|
||||
el_p("class=\"label\" style=\"margin-bottom: 1.5rem; color: var(--navy);\"", "Your order")
|
||||
+ plan_block
|
||||
+ el_div("class=\"navy-line-left\" style=\"width: 3rem; margin-bottom: 1.75rem;\"", "")
|
||||
+ el_ul("class=\"checkout-features\"", features_html)
|
||||
+ el_p("class=\"checkout-guarantee\"", "Your data stays yours. Runs locally. No telemetry.")
|
||||
)
|
||||
|
||||
<ul class="checkout-features">
|
||||
{raw(features_html)}
|
||||
</ul>
|
||||
// ── Auth section ──────────────────────────────────────────────────────────
|
||||
|
||||
<p class="checkout-guarantee">
|
||||
Your data stays yours. Runs locally. No telemetry.
|
||||
</p>
|
||||
</div>
|
||||
let auth_heading: String = if is_free {
|
||||
el_p("class=\"label\" style=\"margin-bottom: 1.5rem; color: var(--navy);\"", "Create your account.")
|
||||
+ el_p("class=\"checkout-auth-hint\" style=\"margin-bottom: 2rem;\"", "No card required. Your account is free, forever.")
|
||||
} else {
|
||||
el_p("class=\"label\" style=\"margin-bottom: 1.25rem;\"", "Sign in (optional)")
|
||||
+ el_p("class=\"checkout-auth-hint\"", "Sign in to link this purchase to an existing account. Or skip and create one later - we'll match it to your email.")
|
||||
}
|
||||
|
||||
<!-- Right: either free thank-you or sign-in + payment form -->
|
||||
<div class="checkout-form-wrap">
|
||||
let google_svg: String = "<svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\" aria-hidden=\"true\">"
|
||||
+ "<path d=\"M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.716v2.259h2.908c1.702-1.567 2.684-3.875 2.684-6.615z\" fill=\"#4285F4\"/>"
|
||||
+ "<path d=\"M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 0 0 9 18z\" fill=\"#34A853\"/>"
|
||||
+ "<path d=\"M3.964 10.71A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.957 4.042l3.007-2.332z\" fill=\"#FBBC05\"/>"
|
||||
+ "<path d=\"M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 0 0 .957 4.958L3.964 6.29C4.672 4.163 6.656 3.58 9 3.58z\" fill=\"#EA4335\"/>"
|
||||
+ "</svg>"
|
||||
|
||||
<!-- Auth section: visible immediately for free, collapsed (optional) for paid plans -->
|
||||
<div id=\"auth-section\" " + (if is_free { "" } else { "style=\"display:none;\"" }) + ">
|
||||
" + (if is_free { "
|
||||
<p class=\"label\" style=\"margin-bottom: 1.5rem; color: var(--navy);\">Create your account.</p>
|
||||
<p class=\"checkout-auth-hint\" style=\"margin-bottom: 2rem;\">No card required. Your account is free, forever.</p>
|
||||
" } else { "
|
||||
<p class=\"label\" style=\"margin-bottom: 1.25rem;\">Sign in (optional)</p>
|
||||
<p class=\"checkout-auth-hint\">Sign in to link this purchase to an existing account. Or skip and create one later - we'll match it to your email.</p>
|
||||
" }) + "
|
||||
let github_svg: String = "<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">"
|
||||
+ "<path d=\"M12 0C5.37 0 0 5.37 0 12c0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.385-1.335-1.755-1.335-1.755-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.605-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 21.795 24 17.298 24 12c0-6.63-5.37-12-12-12z\"/>"
|
||||
+ "</svg>"
|
||||
|
||||
<div class="checkout-social-btns">
|
||||
<button type="button" class="checkout-social-btn" id="btn-google" onclick="signInWith('google')">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true">
|
||||
<path d="M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.716v2.259h2.908c1.702-1.567 2.684-3.875 2.684-6.615z" fill="#4285F4"/>
|
||||
<path d="M9 18c2.43 0 4.467-.806 5.956-2.18l-2.908-2.259c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332A8.997 8.997 0 0 0 9 18z" fill="#34A853"/>
|
||||
<path d="M3.964 10.71A5.41 5.41 0 0 1 3.682 9c0-.593.102-1.17.282-1.71V4.958H.957A8.996 8.996 0 0 0 0 9c0 1.452.348 2.827.957 4.042l3.007-2.332z" fill="#FBBC05"/>
|
||||
<path d="M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0A8.997 8.997 0 0 0 .957 4.958L3.964 6.29C4.672 4.163 6.656 3.58 9 3.58z" fill="#EA4335"/>
|
||||
</svg>
|
||||
Continue with Google
|
||||
</button>
|
||||
<button type="button" class="checkout-social-btn" id="btn-github" onclick="signInWith('github')">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.385-1.335-1.755-1.335-1.755-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.605-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 21.795 24 17.298 24 12c0-6.63-5.37-12-12-12z"/>
|
||||
</svg>
|
||||
Continue with GitHub
|
||||
</button>
|
||||
</div>
|
||||
let social_btns: String = el_div(
|
||||
"class=\"checkout-social-btns\"",
|
||||
"<button type=\"button\" class=\"checkout-social-btn\" id=\"btn-google\" onclick=\"signInWith('google')\">"
|
||||
+ google_svg + "Continue with Google</button>"
|
||||
+ "<button type=\"button\" class=\"checkout-social-btn\" id=\"btn-github\" onclick=\"signInWith('github')\">"
|
||||
+ github_svg + "Continue with GitHub</button>"
|
||||
)
|
||||
|
||||
<div id="auth-message" class="checkout-message" style="display:none;"></div>
|
||||
let auth_message: String = el_div("id=\"auth-message\" class=\"checkout-message\" style=\"display:none;\"", "")
|
||||
|
||||
<div class="checkout-auth-divider">
|
||||
<span id="auth-divider-label">or {#if is_free}create an account with email{#else}create an account{/if}</span>
|
||||
</div>
|
||||
let divider_label: String = if is_free {
|
||||
"or create an account with email"
|
||||
} else {
|
||||
"or create an account"
|
||||
}
|
||||
|
||||
<div id="email-auth-form">
|
||||
<input type="email" id="auth-email" class="checkout-input" placeholder="Email address" autocomplete="email" style="width:100%;display:block;margin-bottom:.75rem">
|
||||
<input type="password" id="auth-password" class="checkout-input" placeholder="Password - min 8 characters" autocomplete="new-password" style="width:100%;display:block;margin-bottom:.75rem">
|
||||
<button type="button" class="checkout-email-btn" onclick="signUpWithEmail()">{#if is_free}Create account →{#else}Create account →{/if}</button>
|
||||
<p class="checkout-auth-hint" style="margin-top:.75rem;text-align:center">Already have an account? <a href="#" onclick="showSignIn();return false;" style="color:var(--navy)">Sign in</a></p>
|
||||
</div>
|
||||
</div>
|
||||
let auth_divider: String = el_div(
|
||||
"class=\"checkout-auth-divider\"",
|
||||
el_span("id=\"auth-divider-label\"", divider_label)
|
||||
)
|
||||
|
||||
<!-- Free-tier success panel: shown after account creation, no card needed -->
|
||||
" + (if is_free { "
|
||||
<div id=\"free-success\" style=\"display:none; text-align:center; padding: 2.5rem 1rem;\">
|
||||
<div style=\"font-size:2.5rem; margin-bottom:1.25rem;\">✓</div>
|
||||
<p class=\"label\" style=\"margin-bottom:.75rem; color:var(--navy);\">You're in.</p>
|
||||
<p class=\"checkout-auth-hint\" style=\"margin-bottom:2rem;\">Your free account is ready. Download Neuron to get started.</p>
|
||||
<a href=\"/marketplace\" class=\"checkout-submit\" style=\"display:inline-block; text-decoration:none; padding:.875rem 2rem;\">Go to your account →</a>
|
||||
</div>
|
||||
" } else { "" }) + "
|
||||
let create_btn_label: String = if is_free { "Create account →" } else { "Create account →" }
|
||||
|
||||
<!-- Payment form (visible immediately - no auth wall) -->
|
||||
<div id="payment-section" {#if is_free}style="display:none;"{/if}>
|
||||
<div id="auth-badge" style="display:none; margin-bottom: 1.5rem;"></div>
|
||||
{#if is_free}{#else}
|
||||
<p class="checkout-auth-hint" id="signin-prompt" style="margin-bottom:1.25rem;font-size:.8125rem">Already have an account? <a href="#" onclick="document.getElementById('auth-section').style.display='';this.parentNode.style.display='none';return false;" style="color:var(--navy);text-decoration:underline">Sign in</a> to link your purchase.</p>
|
||||
{/if}
|
||||
let email_auth_form: String = el_div(
|
||||
"id=\"email-auth-form\"",
|
||||
el_input("email", "id=\"auth-email\" class=\"checkout-input\" placeholder=\"Email address\" autocomplete=\"email\" style=\"width:100%;display:block;margin-bottom:.75rem\"")
|
||||
+ el_input("password", "id=\"auth-password\" class=\"checkout-input\" placeholder=\"Password - min 8 characters\" autocomplete=\"new-password\" style=\"width:100%;display:block;margin-bottom:.75rem\"")
|
||||
+ "<button type=\"button\" class=\"checkout-email-btn\" onclick=\"signUpWithEmail()\">" + create_btn_label + "</button>"
|
||||
+ el_p("class=\"checkout-auth-hint\" style=\"margin-top:.75rem;text-align:center\"",
|
||||
"Already have an account? "
|
||||
+ el_a("#", "onclick=\"showSignIn();return false;\" style=\"color:var(--navy)\"", "Sign in")
|
||||
)
|
||||
)
|
||||
|
||||
<!-- Founding Member attestation (only shown for founding plan) -->
|
||||
{#if is_founding}
|
||||
<div id="founding-attestation" style="margin-bottom:2rem;padding:1.5rem;border:1px solid rgba(0,82,160,.2);background:rgba(0,82,160,.03)">
|
||||
<p style="font-family:var(--body);font-weight:500;font-size:.9rem;color:var(--t1);margin-bottom:.75rem">Before you continue</p>
|
||||
<p style="font-family:var(--body);font-weight:300;font-size:.8375rem;color:var(--t2);line-height:1.75;margin-bottom:1.25rem">Founding Member is not a ceremonial title. These are the people who will work directly with the team to shape what Neuron becomes. You will have a real voice in what gets built, and we will take it seriously. That requires you to show up in good faith.</p>
|
||||
<label style="display:flex;gap:.875rem;align-items:flex-start;cursor:pointer">
|
||||
<input type="checkbox" id="founding-attest-cb" style="margin-top:.2rem;width:16px;height:16px;flex-shrink:0;accent-color:var(--navy)">
|
||||
<span style="font-family:var(--body);font-weight:300;font-size:.8125rem;color:var(--t2);line-height:1.7">
|
||||
I am joining as a genuine early user, not to extract proprietary information about Neuron's 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.
|
||||
</span>
|
||||
</label>
|
||||
<p id="attest-warn" style="display:none;font-family:var(--body);font-size:.8rem;color:#c44;margin-top:.75rem">Please confirm the above before continuing.</p>
|
||||
</div>
|
||||
{/if}
|
||||
let auth_section_style: String = if is_free { "" } else { "display:none;" }
|
||||
let auth_section: String = el_div(
|
||||
"id=\"auth-section\" style=\"" + auth_section_style + "\"",
|
||||
auth_heading + social_btns + auth_message + auth_divider + email_auth_form
|
||||
)
|
||||
|
||||
<!-- Payment timing: Professional only (not Founding, not Free) -->
|
||||
{#if is_founding}{#else}{#if is_free}{#else}
|
||||
<div style="margin-bottom:1.75rem">
|
||||
<p class="label" style="margin-bottom:1rem">When would you like to be charged?</p>
|
||||
<div style="display:flex;flex-direction:column;gap:.625rem">
|
||||
<label class="checkout-timing-opt" id="timing-now-label">
|
||||
<input type="radio" name="charge-timing" value="now" id="timing-now" checked style="accent-color:var(--navy)">
|
||||
<div>
|
||||
<span style="font-family:var(--body);font-weight:500;font-size:.875rem;color:var(--t1)">Charge me now</span>
|
||||
<span style="font-family:var(--body);font-weight:300;font-size:.8rem;color:var(--t3);display:block;margin-top:.15rem">Paying now signals you're serious. It gives us real signal about what people actually want to use Neuron for - not just who's curious. That shapes what we build first.</span>
|
||||
</div>
|
||||
</label>
|
||||
<label class="checkout-timing-opt" id="timing-later-label">
|
||||
<input type="radio" name="charge-timing" value="later" id="timing-later" style="accent-color:var(--navy)">
|
||||
<div>
|
||||
<span style="font-family:var(--body);font-weight:500;font-size:.875rem;color:var(--t1)">Hold until product launches (Q3 2026)</span>
|
||||
<span style="font-family:var(--body);font-weight:300;font-size:.8rem;color:var(--t3);display:block;margin-top:.15rem">We save your payment method securely. Nothing charged until launch. Cancel anytime before then.</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}{/if}
|
||||
// ── Free-tier success panel ───────────────────────────────────────────────
|
||||
|
||||
<p class="label" style="margin-bottom: 1.75rem;">Payment</p>
|
||||
let free_success: String = if is_free {
|
||||
el_div(
|
||||
"id=\"free-success\" style=\"display:none; text-align:center; padding: 2.5rem 1rem;\"",
|
||||
el_div("style=\"font-size:2.5rem; margin-bottom:1.25rem;\"", "✓")
|
||||
+ el_p("class=\"label\" style=\"margin-bottom:.75rem; color:var(--navy);\"", "You're in.")
|
||||
+ el_p("class=\"checkout-auth-hint\" style=\"margin-bottom:2rem;\"", "Your free account is ready. Download Neuron to get started.")
|
||||
+ el_a("/marketplace", "class=\"checkout-submit\" style=\"display:inline-block; text-decoration:none; padding:.875rem 2rem;\"", "Go to your account →")
|
||||
)
|
||||
} else { "" }
|
||||
|
||||
<form id="payment-form" autocomplete="on">
|
||||
// ── Payment section ───────────────────────────────────────────────────────
|
||||
|
||||
<div class="checkout-field-group">
|
||||
<div class="checkout-field">
|
||||
<label for="buyer-name" class="checkout-label">Full name</label>
|
||||
<input id="buyer-name" name="name" type="text" autocomplete="name"
|
||||
class="checkout-input" placeholder="Full name" required>
|
||||
</div>
|
||||
<div class="checkout-field">
|
||||
<label for="buyer-email" class="checkout-label">Email</label>
|
||||
<input id="buyer-email" name="email" type="email" autocomplete="email"
|
||||
class="checkout-input" placeholder="you@example.com" required>
|
||||
</div>
|
||||
</div>
|
||||
let payment_section_style: String = if is_free { "display:none;" } else { "" }
|
||||
|
||||
<div class="checkout-payment-element-wrap">
|
||||
<div id="payment-element">
|
||||
<!-- Stripe Payment Element mounts here -->
|
||||
<div class="checkout-element-loading">Loading payment form…</div>
|
||||
</div>
|
||||
</div>
|
||||
let auth_badge: String = el_div("id=\"auth-badge\" style=\"display:none; margin-bottom: 1.5rem;\"", "")
|
||||
|
||||
<div id="payment-message" class="checkout-message" style="display:none;"></div>
|
||||
let signin_prompt: String = if is_free { "" } else {
|
||||
el_p(
|
||||
"class=\"checkout-auth-hint\" id=\"signin-prompt\" style=\"margin-bottom:1.25rem;font-size:.8125rem\"",
|
||||
"Already have an account? "
|
||||
+ el_a(
|
||||
"#",
|
||||
"onclick=\"document.getElementById('auth-section').style.display='';this.parentNode.style.display='none';return false;\" style=\"color:var(--navy);text-decoration:underline\"",
|
||||
"Sign in"
|
||||
)
|
||||
+ " to link your purchase."
|
||||
)
|
||||
}
|
||||
|
||||
<button id="submit-btn" class="checkout-submit" type="submit" disabled>
|
||||
<span id="submit-label">{#if is_free}Reserve free tier →{#else}Complete purchase →{/if}</span>
|
||||
<span id="submit-spinner" style="display:none;">Processing…</span>
|
||||
</button>
|
||||
let founding_attestation: String = if is_founding {
|
||||
el_div(
|
||||
"id=\"founding-attestation\" style=\"margin-bottom:2rem;padding:1.5rem;border:1px solid rgba(0,82,160,.2);background:rgba(0,82,160,.03)\"",
|
||||
el_p("style=\"font-family:var(--body);font-weight:500;font-size:.9rem;color:var(--t1);margin-bottom:.75rem\"", "Before you continue")
|
||||
+ el_p("style=\"font-family:var(--body);font-weight:300;font-size:.8375rem;color:var(--t2);line-height:1.75;margin-bottom:1.25rem\"",
|
||||
"Founding Member is not a ceremonial title. These are the people who will work directly with the team to shape what Neuron becomes. You will have a real voice in what gets built, and we will take it seriously. That requires you to show up in good faith."
|
||||
)
|
||||
+ "<label style=\"display:flex;gap:.875rem;align-items:flex-start;cursor:pointer\">"
|
||||
+ el_input("checkbox", "id=\"founding-attest-cb\" style=\"margin-top:.2rem;width:16px;height:16px;flex-shrink:0;accent-color:var(--navy)\"")
|
||||
+ el_span(
|
||||
"style=\"font-family:var(--body);font-weight:300;font-size:.8125rem;color:var(--t2);line-height:1.7\"",
|
||||
"I am joining as a genuine early user, not to extract proprietary information about Neuron's 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."
|
||||
)
|
||||
+ "</label>"
|
||||
+ el_p("id=\"attest-warn\" style=\"display:none;font-family:var(--body);font-size:.8rem;color:#c44;margin-top:.75rem\"", "Please confirm the above before continuing.")
|
||||
)
|
||||
} else { "" }
|
||||
|
||||
<p class="checkout-security">
|
||||
<svg aria-hidden="true" width="12" height="14" viewBox="0 0 12 14" fill="none">
|
||||
<path d="M6 0L0 2.5V7c0 3.3 2.5 6.3 6 7 3.5-.7 6-3.7 6-7V2.5L6 0z" fill="currentColor" opacity=".35"/>
|
||||
<path d="M4 7l1.5 1.5L8.5 5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
Secured by Stripe · 256-bit TLS · PCI DSS compliant
|
||||
</p>
|
||||
let charge_timing: String = if is_founding || is_free { "" } else {
|
||||
el_div(
|
||||
"style=\"margin-bottom:1.75rem\"",
|
||||
el_p("class=\"label\" style=\"margin-bottom:1rem\"", "When would you like to be charged?")
|
||||
+ el_div(
|
||||
"style=\"display:flex;flex-direction:column;gap:.625rem\"",
|
||||
"<label class=\"checkout-timing-opt\" id=\"timing-now-label\">"
|
||||
+ el_input("radio", "name=\"charge-timing\" value=\"now\" id=\"timing-now\" checked style=\"accent-color:var(--navy)\"")
|
||||
+ el_div("",
|
||||
el_span("style=\"font-family:var(--body);font-weight:500;font-size:.875rem;color:var(--t1)\"", "Charge me now")
|
||||
+ el_span("style=\"font-family:var(--body);font-weight:300;font-size:.8rem;color:var(--t3);display:block;margin-top:.15rem\"",
|
||||
"Paying now signals you're serious. It gives us real signal about what people actually want to use Neuron for - not just who's curious. That shapes what we build first."
|
||||
)
|
||||
)
|
||||
+ "</label>"
|
||||
+ "<label class=\"checkout-timing-opt\" id=\"timing-later-label\">"
|
||||
+ el_input("radio", "name=\"charge-timing\" value=\"later\" id=\"timing-later\" style=\"accent-color:var(--navy)\"")
|
||||
+ el_div("",
|
||||
el_span("style=\"font-family:var(--body);font-weight:500;font-size:.875rem;color:var(--t1)\"", "Hold until product launches (Q3 2026)")
|
||||
+ el_span("style=\"font-family:var(--body);font-weight:300;font-size:.8rem;color:var(--t3);display:block;margin-top:.15rem\"",
|
||||
"We save your payment method securely. Nothing charged until launch. Cancel anytime before then."
|
||||
)
|
||||
)
|
||||
+ "</label>"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
</form>
|
||||
</div><!-- /payment-section -->
|
||||
</div>
|
||||
let security_svg: String = "<svg aria-hidden=\"true\" width=\"12\" height=\"14\" viewBox=\"0 0 12 14\" fill=\"none\">"
|
||||
+ "<path d=\"M6 0L0 2.5V7c0 3.3 2.5 6.3 6 7 3.5-.7 6-3.7 6-7V2.5L6 0z\" fill=\"currentColor\" opacity=\".35\"/>"
|
||||
+ "<path d=\"M4 7l1.5 1.5L8.5 5\" stroke=\"currentColor\" stroke-width=\"1.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>"
|
||||
+ "</svg>"
|
||||
|
||||
</div>
|
||||
</main>
|
||||
let submit_label: String = if is_free { "Reserve free tier →" } else { "Complete purchase →" }
|
||||
|
||||
let payment_form: String = el_form(
|
||||
"id=\"payment-form\" autocomplete=\"on\"",
|
||||
el_div(
|
||||
"class=\"checkout-field-group\"",
|
||||
el_div("class=\"checkout-field\"",
|
||||
el_label("buyer-name", "class=\"checkout-label\"", "Full name")
|
||||
+ el_input("text", "id=\"buyer-name\" name=\"name\" autocomplete=\"name\" class=\"checkout-input\" placeholder=\"Full name\" required")
|
||||
)
|
||||
+ el_div("class=\"checkout-field\"",
|
||||
el_label("buyer-email", "class=\"checkout-label\"", "Email")
|
||||
+ el_input("email", "id=\"buyer-email\" name=\"email\" autocomplete=\"email\" class=\"checkout-input\" placeholder=\"you@example.com\" required")
|
||||
)
|
||||
)
|
||||
+ el_div(
|
||||
"class=\"checkout-payment-element-wrap\"",
|
||||
el_div("id=\"payment-element\"",
|
||||
el_div("class=\"checkout-element-loading\"", "Loading payment form…")
|
||||
)
|
||||
)
|
||||
+ el_div("id=\"payment-message\" class=\"checkout-message\" style=\"display:none;\"", "")
|
||||
+ "<button id=\"submit-btn\" class=\"checkout-submit\" type=\"submit\" disabled>"
|
||||
+ el_span("id=\"submit-label\"", submit_label)
|
||||
+ el_span("id=\"submit-spinner\" style=\"display:none;\"", "Processing…")
|
||||
+ "</button>"
|
||||
+ el_p("class=\"checkout-security\"",
|
||||
security_svg
|
||||
+ " Secured by Stripe · 256-bit TLS · PCI DSS compliant"
|
||||
)
|
||||
)
|
||||
|
||||
let payment_section: String = el_div(
|
||||
"id=\"payment-section\" style=\"" + payment_section_style + "\"",
|
||||
auth_badge
|
||||
+ signin_prompt
|
||||
+ founding_attestation
|
||||
+ charge_timing
|
||||
+ el_p("class=\"label\" style=\"margin-bottom: 1.75rem;\"", "Payment")
|
||||
+ payment_form
|
||||
)
|
||||
|
||||
// ── Right column ──────────────────────────────────────────────────────────
|
||||
|
||||
let form_wrap: String = el_div(
|
||||
"class=\"checkout-form-wrap\"",
|
||||
auth_section + free_success + payment_section
|
||||
)
|
||||
|
||||
// ── Full page shell ───────────────────────────────────────────────────────
|
||||
|
||||
let checkout_shell: String = el_div("class=\"checkout-shell\"", summary_col + form_wrap)
|
||||
|
||||
let main_html: String = el_main(
|
||||
"style=\"min-height: 100vh; padding: clamp(6rem, 14vh, 9rem) 2rem 4rem;\"",
|
||||
checkout_shell
|
||||
)
|
||||
|
||||
let style_html: String = checkout_style_html()
|
||||
|
||||
let free_init_script: String = if is_free { "<script>document.addEventListener('DOMContentLoaded',function(){window.neuronCheckoutFree&&window.neuronCheckoutFree()});</script>" } else { "" }
|
||||
return nav_html + main_html + "<!-- Supabase JS --><script src=\"https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/dist/umd/supabase.js\"></script><!-- Stripe.js --><script src=\"https://js.stripe.com/v3/\" async></script>" + style_html + "<script src=\"/js/checkout-auth.js\" defer></script><script>window.NEURON_CFG=window.NEURON_CFG||{};window.NEURON_CFG.plan=\"{plan}\";window.NEURON_CFG.pub_key=\"{pub_key}\";</script><script src=\"/js/checkout-stripe.js\" defer></script>" + free_init_script
|
||||
let supabase_script: String = el_script_src("https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/dist/umd/supabase.js", false)
|
||||
let stripe_script: String = "<script src=\"https://js.stripe.com/v3/\" async></script>"
|
||||
let auth_script: String = el_script_src("/js/checkout-auth.js", true)
|
||||
let cfg_js: String = "window.NEURON_CFG=window.NEURON_CFG||{};window.NEURON_CFG.plan=\"" + plan + "\";window.NEURON_CFG.pub_key=\"" + pub_key + "\";"
|
||||
let cfg_script: String = el_script_inline(cfg_js)
|
||||
let stripe_el_script: String = el_script_src("/js/checkout-stripe.js", true)
|
||||
let free_init_script: String = if is_free {
|
||||
el_script_inline("document.addEventListener('DOMContentLoaded',function(){window.neuronCheckoutFree&&window.neuronCheckoutFree()});")
|
||||
} else { "" }
|
||||
|
||||
return nav_html + main_html + supabase_script + stripe_script + style_html + auth_script + cfg_script + stripe_el_script + free_init_script
|
||||
}
|
||||
|
||||
fn checkout_style_html() -> String {
|
||||
<style>
|
||||
.checkout-plan-name {
|
||||
let css: String = ".checkout-plan-name {
|
||||
font-family: var(--head);
|
||||
font-size: clamp(1.5rem, 3vw, 2rem);
|
||||
font-weight: 600;
|
||||
@@ -294,7 +402,7 @@ fn checkout_style_html() -> String {
|
||||
position: relative;
|
||||
}
|
||||
.checkout-features li::before {
|
||||
content: '–';
|
||||
content: '-';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--navy);
|
||||
@@ -486,6 +594,6 @@ fn checkout_style_html() -> String {
|
||||
font-size: .8125rem;
|
||||
color: var(--t2);
|
||||
}
|
||||
.checkout-auth-badge strong { color: var(--navy); font-weight: 500; }
|
||||
</style>
|
||||
.checkout-auth-badge strong { color: var(--navy); font-weight: 500; }"
|
||||
el_style_block(css)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user