c72127032e
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m18s
- checkout.el: swap stripe_el_script before auth_script so initStripe is defined when Supabase auth fires onAuthStateChange on page load - main.el: fix Stripe webhook email extraction for checkout.session.completed (subscription) events — customer_details is nested at data.object level, not at root; previous code only worked for payment_intent.succeeded - page_close.c: replace <input type="text"> with <textarea rows="1"> in the chat widget input row so long questions are visible as you type - page_css.c: update #neuron-demo-text CSS for textarea (resize:none, overflow:hidden, min/max-height, align-items:flex-end on row) - chat-widget.el: add auto-resize event listener (grows up to ~4 lines), reset height to auto on send
614 lines
26 KiB
EmacsLisp
614 lines
26 KiB
EmacsLisp
// checkout.el - Integrated Stripe checkout page.
|
|
//
|
|
// Uses Stripe Payment Element (not hosted checkout) so the entire
|
|
// purchase experience lives on neurontechnologies.ai. The page is
|
|
// designed to match the Neuron brand - navy, clean, no Stripe chrome.
|
|
//
|
|
// Flow:
|
|
// 1. GET /checkout?plan=founding|professional
|
|
// 2. Page JS calls POST /api/payment-intent → { client_secret, publishable_key, plan }
|
|
// 3. Stripe Payment Element mounts into #payment-element
|
|
// 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 {
|
|
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 {
|
|
let is_founding: Bool = str_eq(plan, "founding")
|
|
let is_free: Bool = str_eq(plan, "free")
|
|
|
|
let plan_name: String = if is_founding { "Founding Member" } else { if is_free { "Free" } else { "Professional" } }
|
|
let plan_price: String = if is_founding { "$199" } else { if is_free { "$0" } else { "$19 / month" } }
|
|
let plan_desc: String = if is_founding {
|
|
"Pay once. Neuron inference when it launches - priced below the major APIs. No subscription, ever."
|
|
} else { if is_free {
|
|
"Start building your memory. A card verifies you're 18+. You won't be charged."
|
|
} else {
|
|
"Full access. Bring your own API keys or use Neuron Inference when it launches - Q3 2026."
|
|
} }
|
|
let plan_cadence: String = if is_founding { "one-time" } else { if is_free { "forever" } else { "billed monthly" } }
|
|
|
|
let features_html: String = if is_founding {
|
|
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 {
|
|
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 {
|
|
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()
|
|
|
|
// ── Order summary (left column) ───────────────────────────────────────────
|
|
|
|
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))
|
|
)
|
|
|
|
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
|
|
)
|
|
|
|
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.")
|
|
)
|
|
|
|
// ── Auth section ──────────────────────────────────────────────────────────
|
|
|
|
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;\"", "Create your account. We'll ask for a card to verify your age - you won't be charged.")
|
|
} 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.")
|
|
}
|
|
|
|
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>"
|
|
|
|
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>"
|
|
|
|
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>"
|
|
)
|
|
|
|
let auth_message: String = el_div("id=\"auth-message\" class=\"checkout-message\" style=\"display:none;\"", "")
|
|
|
|
let divider_label: String = if is_free {
|
|
"or create an account with email"
|
|
} else {
|
|
"or create an account"
|
|
}
|
|
|
|
let auth_divider: String = el_div(
|
|
"class=\"checkout-auth-divider\"",
|
|
el_span("id=\"auth-divider-label\"", divider_label)
|
|
)
|
|
|
|
let create_btn_label: String = if is_free { "Create account →" } else { "Create account →" }
|
|
|
|
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")
|
|
)
|
|
)
|
|
|
|
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
|
|
)
|
|
|
|
// ── Free-tier success panel ───────────────────────────────────────────────
|
|
|
|
let free_success: String = ""
|
|
|
|
// ── Payment section ───────────────────────────────────────────────────────
|
|
|
|
let payment_section_style: String = if is_free { "display:none;" } else { "" }
|
|
|
|
let auth_badge: String = el_div("id=\"auth-badge\" style=\"display:none; margin-bottom: 1.5rem;\"", "")
|
|
|
|
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."
|
|
)
|
|
}
|
|
|
|
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 { "" }
|
|
|
|
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>"
|
|
)
|
|
)
|
|
}
|
|
|
|
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>"
|
|
|
|
let submit_label: String = if is_free { "Verify age & get started →" } 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 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 = ""
|
|
|
|
return nav_html + main_html + supabase_script + stripe_script + style_html + stripe_el_script + cfg_script + auth_script + free_init_script
|
|
}
|
|
|
|
fn checkout_style_html() -> String {
|
|
let css: String = ".checkout-shell {
|
|
max-width: 980px;
|
|
margin: 0 auto;
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 4rem;
|
|
align-items: start;
|
|
}
|
|
.checkout-summary {
|
|
position: sticky;
|
|
top: 2rem;
|
|
}
|
|
.checkout-form-wrap {
|
|
min-width: 0;
|
|
}
|
|
@media (max-width: 860px) {
|
|
.checkout-shell {
|
|
grid-template-columns: 1fr;
|
|
gap: 2.5rem;
|
|
}
|
|
.checkout-summary {
|
|
position: static;
|
|
}
|
|
}
|
|
.checkout-plan-name {
|
|
font-family: var(--head);
|
|
font-size: clamp(1.5rem, 3vw, 2rem);
|
|
font-weight: 600;
|
|
color: var(--t1);
|
|
letter-spacing: -0.02em;
|
|
margin: 0 0 .5rem;
|
|
}
|
|
.checkout-plan-desc {
|
|
font-family: var(--body);
|
|
font-weight: 300;
|
|
font-size: .9375rem;
|
|
color: var(--t2);
|
|
line-height: 1.65;
|
|
margin: 0;
|
|
}
|
|
.checkout-price {
|
|
font-family: var(--head);
|
|
font-size: 2.5rem;
|
|
font-weight: 700;
|
|
color: var(--t1);
|
|
letter-spacing: -0.03em;
|
|
line-height: 1;
|
|
}
|
|
.checkout-cadence {
|
|
font-family: var(--body);
|
|
font-size: .8125rem;
|
|
color: var(--t3);
|
|
letter-spacing: .04em;
|
|
}
|
|
.checkout-features {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0 0 2rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: .6rem;
|
|
}
|
|
.checkout-features li {
|
|
font-family: var(--body);
|
|
font-weight: 300;
|
|
font-size: .9rem;
|
|
color: var(--t2);
|
|
padding-left: 1.1rem;
|
|
position: relative;
|
|
}
|
|
.checkout-features li::before {
|
|
content: '-';
|
|
position: absolute;
|
|
left: 0;
|
|
color: var(--navy);
|
|
opacity: .6;
|
|
}
|
|
.checkout-guarantee {
|
|
font-family: var(--body);
|
|
font-weight: 300;
|
|
font-size: .8125rem;
|
|
color: var(--t3);
|
|
line-height: 1.6;
|
|
margin: 0;
|
|
}
|
|
.checkout-field-group {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 1rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
@media (max-width: 480px) { .checkout-field-group { grid-template-columns: 1fr; } }
|
|
.checkout-field { display: flex; flex-direction: column; gap: .4rem; }
|
|
.checkout-label {
|
|
font-family: var(--body);
|
|
font-size: .75rem;
|
|
font-weight: 500;
|
|
letter-spacing: .06em;
|
|
text-transform: uppercase;
|
|
color: var(--t2);
|
|
}
|
|
.checkout-input {
|
|
font-family: var(--body);
|
|
font-size: .9375rem;
|
|
font-weight: 300;
|
|
color: var(--t1);
|
|
background: #fff;
|
|
border: 1px solid rgba(0,82,160,.22);
|
|
padding: .75rem 1rem;
|
|
outline: none;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
transition: border-color .2s;
|
|
border-radius: 0;
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
}
|
|
.checkout-input::placeholder { color: var(--t3); }
|
|
.checkout-input:focus { border-color: rgba(0,82,160,.6); box-shadow: 0 0 0 3px rgba(0,82,160,.08); }
|
|
.checkout-payment-element-wrap {
|
|
margin-bottom: 1.5rem;
|
|
border: 1px solid rgba(0,82,160,.22);
|
|
padding: 1.25rem;
|
|
background: #fff;
|
|
min-height: 80px;
|
|
}
|
|
.checkout-element-loading {
|
|
font-family: var(--body);
|
|
font-size: .875rem;
|
|
color: var(--t3);
|
|
}
|
|
.checkout-message {
|
|
font-family: var(--body);
|
|
font-size: .875rem;
|
|
color: #c0392b;
|
|
margin-bottom: 1rem;
|
|
padding: .75rem 1rem;
|
|
background: rgba(192,57,43,.06);
|
|
border-left: 2px solid rgba(192,57,43,.4);
|
|
}
|
|
.checkout-submit {
|
|
width: 100%;
|
|
padding: 1.05rem 2rem;
|
|
background: var(--navy);
|
|
color: #fff;
|
|
font-family: var(--body);
|
|
font-size: .8125rem;
|
|
font-weight: 600;
|
|
letter-spacing: .12em;
|
|
text-transform: uppercase;
|
|
border: none;
|
|
cursor: pointer;
|
|
transition: background .2s, opacity .2s;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
.checkout-submit:hover:not(:disabled) { background: #0078D4; }
|
|
.checkout-submit:disabled { opacity: .5; cursor: not-allowed; }
|
|
.checkout-security {
|
|
font-family: var(--body);
|
|
font-size: .75rem;
|
|
color: var(--t3);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: .4rem;
|
|
margin: 0;
|
|
}
|
|
.checkout-auth-hint {
|
|
font-family: var(--body);
|
|
font-weight: 300;
|
|
font-size: .875rem;
|
|
color: var(--t2);
|
|
line-height: 1.6;
|
|
margin: 0 0 1.75rem;
|
|
}
|
|
.checkout-social-btns {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: .75rem;
|
|
margin-bottom: 1.75rem;
|
|
}
|
|
.checkout-social-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: .875rem;
|
|
width: 100%;
|
|
padding: .875rem 1.25rem;
|
|
background: #fff;
|
|
border: 1.5px solid rgba(0,82,160,.18);
|
|
font-family: var(--body);
|
|
font-size: .9375rem;
|
|
font-weight: 500;
|
|
color: var(--t1);
|
|
cursor: pointer;
|
|
transition: border-color .2s, background .2s, box-shadow .2s;
|
|
text-align: left;
|
|
border-radius: 0;
|
|
letter-spacing: .01em;
|
|
}
|
|
.checkout-social-btn:hover {
|
|
border-color: var(--navy);
|
|
background: rgba(0,82,160,.03);
|
|
box-shadow: 0 2px 12px rgba(0,82,160,.08);
|
|
}
|
|
.checkout-social-btn:disabled { opacity: .45; cursor: not-allowed; }
|
|
.checkout-auth-divider {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
margin: 1.75rem 0 1.25rem;
|
|
color: var(--t3);
|
|
font-family: var(--body);
|
|
font-size: .75rem;
|
|
font-weight: 500;
|
|
letter-spacing: .12em;
|
|
text-transform: uppercase;
|
|
}
|
|
.checkout-auth-divider::before,
|
|
.checkout-auth-divider::after {
|
|
content: '';
|
|
flex: 1;
|
|
height: 1px;
|
|
background: rgba(0,82,160,.12);
|
|
}
|
|
.checkout-email-btn {
|
|
display: block;
|
|
width: 100%;
|
|
padding: .875rem 1.5rem;
|
|
background: var(--navy);
|
|
color: #fff;
|
|
border: none;
|
|
font-family: var(--body);
|
|
font-size: .8125rem;
|
|
font-weight: 500;
|
|
letter-spacing: .12em;
|
|
text-transform: uppercase;
|
|
cursor: pointer;
|
|
margin-top: .75rem;
|
|
transition: background .2s, box-shadow .2s;
|
|
}
|
|
.checkout-email-btn:hover { background: #0078D4; box-shadow: 0 2px 16px rgba(0,82,160,.25); }
|
|
.checkout-skip-btn {
|
|
background: none;
|
|
border: none;
|
|
font-family: var(--body);
|
|
font-size: .8125rem;
|
|
color: var(--t3);
|
|
cursor: pointer;
|
|
text-decoration: underline;
|
|
text-underline-offset: 3px;
|
|
padding: 0;
|
|
}
|
|
.checkout-skip-btn:hover { color: var(--navy); }
|
|
.checkout-auth-badge {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: .6rem;
|
|
padding: .6rem .875rem;
|
|
background: rgba(0,82,160,.05);
|
|
border: 1px solid rgba(0,82,160,.18);
|
|
font-family: var(--body);
|
|
font-size: .8125rem;
|
|
color: var(--t2);
|
|
}
|
|
.checkout-auth-badge strong { color: var(--navy); font-weight: 500; }"
|
|
el_style_block(css)
|
|
}
|