diff --git a/src/account.el b/src/account.el index e1268d1..5d38883 100644 --- a/src/account.el +++ b/src/account.el @@ -651,8 +651,7 @@ fn account_roadmap_phase2() -> String { el_li("", "Process and knowledge packets") + el_li("", "Local inference via Ollama") + el_li("", "Additional connectors — GitHub, Notion, Linear, more") + - el_li("", "Mobile companion app") + - el_li("", "Family accounts") + el_li("", "Mobile companion app") ) ) } @@ -692,33 +691,6 @@ fn account_roadmap_section() -> String { ) } -fn account_family_section() -> String { - let child_attest: String = "" - el_div( - "id=\"family-section\" style=\"display:none\"", - el_div( - "class=\"card-dark\"", - el_div( - "class=\"acct-section-header\"", - el_p("class=\"card-label\"", "Family members") + - el_p("style=\"font-size:.8125rem;font-weight:300;color:var(--t2);line-height:1.65\"", "Add up to 5 children to your Founding Member plan. $10/month per child.") - ) + - el_div("id=\"family-list\"", "") + - el_div( - "id=\"add-child-form\"", - el_input("email", "id=\"child-email\" placeholder=\"Child's email\" class=\"acct-input\"") + - el_input("number", "id=\"child-dob-year\" placeholder=\"Birth year (e.g. 2012)\" min=\"1990\" max=\"2007\" class=\"acct-input\"") + - child_attest + - el_button("class=\"btn-primary\" onclick=\"addFamilyMember()\" style=\"padding:.75rem 1.5rem\"", "Add family member") + - el_p("id=\"family-msg\" style=\"display:none;font-size:.8rem;margin-top:.5rem\"", "") - ) - ) - ) -} - fn account_badge_section() -> String { el_div( "id=\"badge-section\" style=\"display:none\"", @@ -952,7 +924,6 @@ fn account_dashboard_section() -> String { account_plan_card() + account_api_keys_section() + account_roadmap_section() + - account_family_section() + account_badge_section() + account_timeline_card() + account_download_card() + diff --git a/src/comparison.el b/src/comparison.el index ecfd487..b51f95d 100644 --- a/src/comparison.el +++ b/src/comparison.el @@ -129,8 +129,8 @@ fn comparison_rows() -> String { "$30/mo
Microsoft 365" + "" let row10: String = "" + - "Free tier" + - "
Full app, forever" + + "Free trial" + + "
Full app, 14 days" + "
GPT-3.5 only" + "
Limited" + "
Limited" + diff --git a/src/js/account-dashboard.el b/src/js/account-dashboard.el index af38ec1..807f0f8 100644 --- a/src/js/account-dashboard.el +++ b/src/js/account-dashboard.el @@ -1,5 +1,5 @@ -// account-dashboard.el -- Account dashboard: session check, plan card, family. -// Handles onAuthStateChange, plan card rendering, family member management. +// account-dashboard.el -- Account dashboard: session check, plan card. +// Handles onAuthStateChange and plan card rendering. // Compiled with: elc --target=js --bundle --minify --obfuscate // // Required globals: window.NEURON_CFG.supabase_url, window.NEURON_CFG.supabase_anon_key @@ -127,77 +127,8 @@ fn main() -> Void { 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 = '

No family members yet.

'; - return; - } - list.innerHTML = members.map(function(m) { - return '
' - + '

' + m.child_email + '

' - + '

' + m.status + '

' - + '' - + '
'; - }).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'); diff --git a/src/local_first.el b/src/local_first.el index 674740a..a62076b 100644 --- a/src/local_first.el +++ b/src/local_first.el @@ -55,7 +55,7 @@ fn local_first() -> String { let principle3: String = el_div( "class=\"lf-principle reveal\" style=\"transition-delay:240ms\"", el_p("class=\"lf-principle-label\"", "No ads. Ever.") + - el_p("class=\"lf-principle-body\"", "Not on the free tier. Not on paid. Not in any future version. Ads require surveillance. Surveillance requires your data. I'm not building that.") + el_p("class=\"lf-principle-body\"", "Not during your trial. Not on paid. Not in any future version. Ads require surveillance. Surveillance requires your data. I'm not building that.") ) let principle4: String = el_div( "class=\"lf-principle reveal\" style=\"transition-delay:360ms\"", diff --git a/src/main.el b/src/main.el index 0287238..4eeb0bc 100644 --- a/src/main.el +++ b/src/main.el @@ -628,7 +628,7 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String "Founding Member Checkout — Neuron" } else { if str_eq(plan, "free") { - "Get Started Free — Neuron" + "Start Your Free Trial — Neuron" } else { "Professional Plan Checkout — Neuron" } @@ -637,7 +637,7 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String "Secure your Founding Member spot. Pay once, $199 lifetime — Neuron inference included at launch, priced below the major APIs. First 1,000 only." } else { if str_eq(plan, "free") { - "Create your free Neuron account. No credit card required. Your AI that remembers you — runs on your machine, never resets." + "Start your 14-day free trial. No credit card required. Your AI that remembers you — runs on your machine, never resets." } else { "Subscribe to Neuron Professional for $19/month. The AI that remembers you — persistent memory, runs locally, bring your own API keys." } @@ -1991,164 +1991,6 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String return badge_css + badge_html } - // ── Family plan — POST /api/family/invite ──────────────────────────────── - if str_eq(path, "/api/family/invite") { - if !str_eq(method, "POST") { - return "{\"error\":\"POST required\"}" - } - let parent_email: String = json_get(body, "parent_email") - let child_email: String = json_get(body, "child_email") - let child_dob_year_str: String = json_get(body, "child_dob_year") - let attested_str: String = json_get(body, "attested") - if str_eq(parent_email, "") || str_eq(child_email, "") || str_eq(child_dob_year_str, "") { - return "{\"error\":\"parent_email, child_email, and child_dob_year are required\"}" - } - if !str_eq(attested_str, "true") { - return "{\"error\":\"attestation required\"}" - } - let child_dob_year: Int = str_to_int(child_dob_year_str) - if child_dob_year < 2008 { - return "{\"error\":\"Child must be under 18. Birth year must be 2008 or later.\"}" - } - // Check existing family member count - let fam_sb_url: String = state_get("__supabase_project_url__") - let fam_sb_key: String = state_get("__supabase_service_key__") - if str_eq(fam_sb_key, "") { - return "{\"error\":\"not configured\"}" - } - let pe_safe: String = str_replace(str_replace(parent_email, "\\", "\\\\"), "\"", "\\\"") - let ce_safe: String = str_replace(str_replace(child_email, "\\", "\\\\"), "\"", "\\\"") - let members_resp: String = http_get_auth( - fam_sb_url + "/rest/v1/family_members?parent_email=eq." + parent_email + "&status=neq.cancelled&select=child_email", - fam_sb_key - ) - let member_count: Int = json_array_len(members_resp) - if member_count >= 5 { - return "{\"error\":\"Maximum of 5 family members reached.\"}" - } - // Check child not already added - let dup_resp: String = http_get_auth( - fam_sb_url + "/rest/v1/family_members?parent_email=eq." + parent_email + "&child_email=eq." + child_email + "&select=child_email", - fam_sb_key - ) - let dup_count: Int = json_array_len(dup_resp) - if dup_count > 0 { - return "{\"error\":\"This child email is already in your family plan.\"}" - } - // Create Stripe customer for child - let fam_stripe_key: String = state_get("__stripe_secret_key__") - if str_eq(fam_stripe_key, "") { - return "{\"error\":\"Stripe not configured\"}" - } - let child_email_safe: String = str_replace(child_email, "@", "%40") - let customer_body: String = "email=" + child_email_safe - let customer_resp: String = http_post_auth("https://api.stripe.com/v1/customers", fam_stripe_key, customer_body) - let child_customer_id: String = json_get(customer_resp, "id") - if str_eq(child_customer_id, "") { - return "{\"error\":\"Failed to create Stripe customer\"}" - } - // Create Stripe subscription for child - let price_id: String = state_get("__stripe_price_family_child__") - if str_eq(price_id, "") { - return "{\"error\":\"Family child price not configured\"}" - } - let parent_email_safe: String = str_replace(parent_email, "@", "%40") - let sub_body: String = "customer=" + child_customer_id - + "&items[0][price]=" + price_id - + "&trial_period_days=7" - + "&metadata[parent_email]=" + parent_email_safe - + "&metadata[type]=family_child" - let sub_resp: String = http_post_auth("https://api.stripe.com/v1/subscriptions", fam_stripe_key, sub_body) - let sub_id: String = json_get(sub_resp, "id") - if str_eq(sub_id, "") { - return "{\"error\":\"Failed to create Stripe subscription\"}" - } - // Generate invite token - let ts_str: String = int_to_str(unix_timestamp()) - let email_prefix: String = str_slice(child_email, 0, 4) - let invite_token: String = ts_str + email_prefix - // Insert into family_members table - let fam_row: String = "{\"parent_email\":\"" + pe_safe + "\",\"parent_user_id\":\"\",\"child_email\":\"" + ce_safe + "\",\"child_dob_year\":" + child_dob_year_str + ",\"status\":\"invited\",\"stripe_subscription_id\":\"" + sub_id + "\",\"invite_token\":\"" + invite_token + "\"}" - let fam_insert_resp: String = supabase_insert(fam_sb_url, fam_sb_key, "family_members", fam_row) - println("[family/invite] insert -> " + fam_insert_resp) - // Send invite email - let invite_text: String = "You have been invited to join Neuron by " + parent_email - + ". Visit https://neurontechnologies.ai/account to set up your account." - let invite_send: String = send_email( - "Neuron ", - child_email, - "You have been invited to Neuron", - "", - invite_text - ) - println("[family/invite] " + invite_send) - return "{\"ok\":true,\"invite_token\":\"" + invite_token + "\"}" - } - - // ── Family plan — GET /api/family/members ──────────────────────────────── - if str_starts_with(path, "/api/family/members") { - let fml_sb_url: String = state_get("__supabase_project_url__") - let fml_sb_key: String = state_get("__supabase_service_key__") - if str_eq(fml_sb_key, "") { - return "[]" - } - // Extract parent_email from query string - let fml_parent_email: String = "" - let q_idx: Int = str_index_of(path, "parent_email=") - if q_idx >= 0 { - let fml_parent_email = str_slice(path, q_idx + 13, str_len(path)) - } - if str_eq(fml_parent_email, "") { - return "{\"error\":\"parent_email required\"}" - } - let fml_resp: String = http_get_auth( - fml_sb_url + "/rest/v1/family_members?parent_email=eq." + fml_parent_email + "&select=child_email,status,child_dob_year,created_at", - fml_sb_key - ) - if str_eq(fml_resp, "") { - return "[]" - } - return fml_resp - } - - // ── Family plan — POST /api/family/remove ──────────────────────────────── - if str_eq(path, "/api/family/remove") { - if !str_eq(method, "POST") { - return "{\"error\":\"POST required\"}" - } - let rem_parent_email: String = json_get(body, "parent_email") - let rem_child_email: String = json_get(body, "child_email") - if str_eq(rem_parent_email, "") || str_eq(rem_child_email, "") { - return "{\"error\":\"parent_email and child_email required\"}" - } - let rem_sb_url: String = state_get("__supabase_project_url__") - let rem_sb_key: String = state_get("__supabase_service_key__") - if str_eq(rem_sb_key, "") { - return "{\"error\":\"not configured\"}" - } - // Fetch the subscription ID - let sub_lookup: String = http_get_auth( - rem_sb_url + "/rest/v1/family_members?parent_email=eq." + rem_parent_email + "&child_email=eq." + rem_child_email + "&select=stripe_subscription_id", - rem_sb_key - ) - let rem_sub_id: String = json_get(sub_lookup, "0.stripe_subscription_id") - // Cancel Stripe subscription if present - if !str_eq(rem_sub_id, "") { - let rem_stripe_key: String = state_get("__stripe_secret_key__") - if !str_eq(rem_stripe_key, "") { - let cancel_resp: String = http_post_auth("https://api.stripe.com/v1/subscriptions/" + rem_sub_id + "/cancel", rem_stripe_key, "") - println("[family/remove] cancel sub " + rem_sub_id + " -> " + cancel_resp) - } - } - // Update status to cancelled - let rpe_safe: String = str_replace(str_replace(rem_parent_email, "\\", "\\\\"), "\"", "\\\"") - let rce_safe: String = str_replace(str_replace(rem_child_email, "\\", "\\\\"), "\"", "\\\"") - let cancel_row: String = "{\"status\":\"cancelled\"}" - supabase_insert(rem_sb_url, rem_sb_key, "family_members?parent_email=eq." + rem_parent_email + "&child_email=eq." + rem_child_email, cancel_row) - println("[family/remove] cancelled " + rce_safe + " from " + rpe_safe) - return "{\"ok\":true}" - } - // ── API key provisioning — POST /api/api-keys ──────────────────────────── // Returns user's stored provider keys (masked) for display on /account. // Body: { access_token: "" } @@ -2345,7 +2187,6 @@ let stripe_key: String = env("STRIPE_SECRET_KEY") let stripe_pub_key: String = env("STRIPE_PUBLISHABLE_KEY") let stripe_price_founding: String = env("STRIPE_PRICE_FOUNDING") let stripe_price_professional: String = env("STRIPE_PRICE_PROFESSIONAL") -let family_child_price: String = env("STRIPE_PRICE_FAMILY_CHILD") let stripe_webhook_secret: String = env("STRIPE_WEBHOOK_SECRET") let license_api_url: String = env("NEURON_LICENSE_API_URL") let resend_api_key: String = env("RESEND_API_KEY") @@ -2396,7 +2237,6 @@ state_set("__stripe_secret_key__", stripe_key) state_set("__stripe_publishable_key__", stripe_pub_key) state_set("__stripe_price_founding__", stripe_price_founding) state_set("__stripe_price_professional__", stripe_price_professional) -state_set("__stripe_price_family_child__", family_child_price) state_set("__license_api_url__", license_api_url) state_set("__resend_api_key__", resend_api_key) state_set("__supabase_anon_key__", supabase_anon_key) diff --git a/src/pricing.el b/src/pricing.el index b032802..37c8dd8 100644 --- a/src/pricing.el +++ b/src/pricing.el @@ -1,4 +1,4 @@ -// components/pricing.el - Three pricing cards: Free / Professional / Founding Member. +// components/pricing.el - Three pricing cards: Free trial / Professional / Founding Member. // Accepts the founding counter values as parameters so main.el can inject // server-side rendered counts without JS. @@ -89,14 +89,14 @@ fn pricing(sold: Int, total: Int) -> String { el_div( "class=\"pricing-price-row\"", el_span("class=\"pricing-price\"", "$0") + - el_span("class=\"pricing-cadence\"", "forever") + el_span("class=\"pricing-cadence\"", "for 14 days") ) + - el_p("class=\"pricing-tagline\"", "Start building your memory. No card required.") + + el_p("class=\"pricing-tagline\"", "Try everything free for 14 days. No card required.") + el_ul("class=\"pricing-features\"", pricing_free_features()) + el_div("style=\"flex:1\"", "") + el_div( "class=\"pricing-cta pricing-cta-ghost\"", - el_button("class=\"pricing-cta-ghost\" data-checkout=\"free\"", "Preorder free tier →") + el_button("class=\"pricing-cta-ghost\" data-checkout=\"free\"", "Start free trial →") ) ) diff --git a/src/styles.el b/src/styles.el index c54cfe8..f7bfa6c 100644 --- a/src/styles.el +++ b/src/styles.el @@ -78,9 +78,9 @@ fn page_head() -> String { return page_head_base() + page_seo_block( "Neuron — The AI That Remembers You", - "Every AI resets when you close the tab. Neuron doesn't. Runs on your machine. Remembers everything. Start free — no credit card required.", + "Every AI resets when you close the tab. Neuron doesn't. Runs on your machine. Remembers everything. Start with a 14-day free trial — no credit card required.", "/", - "Every other AI forgets you. Neuron doesn't. Runs on your machine, builds a persistent memory over time, and gets sharper the longer you use it. Free tier available." + "Every other AI forgets you. Neuron doesn't. Runs on your machine, builds a persistent memory over time, and gets sharper the longer you use it. 14-day free trial." ) + page_schema() }