Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20198bf345 |
+1
-30
@@ -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 = "<label style=\"display:flex;gap:.5rem;align-items:flex-start;cursor:pointer;margin:.75rem 0\">" +
|
||||
"<input type=\"checkbox\" id=\"family-attest\" style=\"margin-top:.2rem;accent-color:var(--navy)\">" +
|
||||
"<span style=\"font-size:.8rem;color:var(--t2)\">I confirm this person is a minor under my care and is using this account under my supervision.</span>" +
|
||||
"</label>"
|
||||
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() +
|
||||
|
||||
+2
-2
@@ -129,8 +129,8 @@ fn comparison_rows() -> String {
|
||||
"<td style=\"text-align:center;padding:1rem;color:var(--t3)\"><span style=\"font-weight:500;color:var(--t2)\">$30/mo</span><br><span style=\"font-size:0.7rem\">Microsoft 365</span></td>" +
|
||||
"</tr>"
|
||||
let row10: String = "<tr>" +
|
||||
"<td style=\"padding:1rem 1.5rem 1rem 0;color:var(--t1);font-weight:500\">Free tier</td>" +
|
||||
"<td style=\"text-align:center;padding:1rem\"><span style=\"color:#008040;font-weight:700;font-size:1.1rem\">✓</span><br><span style=\"font-size:0.7rem;color:var(--t3);font-weight:400\">Full app, forever</span></td>" +
|
||||
"<td style=\"padding:1rem 1.5rem 1rem 0;color:var(--t1);font-weight:500\">Free trial</td>" +
|
||||
"<td style=\"text-align:center;padding:1rem\"><span style=\"color:#008040;font-weight:700;font-size:1.1rem\">✓</span><br><span style=\"font-size:0.7rem;color:var(--t3);font-weight:400\">Full app, 14 days</span></td>" +
|
||||
"<td style=\"text-align:center;padding:1rem;color:var(--t3)\"><span style=\"color:#D4A017\">●</span><br><span style=\"font-size:0.7rem\">GPT-3.5 only</span></td>" +
|
||||
"<td style=\"text-align:center;padding:1rem;color:var(--t3)\"><span style=\"color:#D4A017\">●</span><br><span style=\"font-size:0.7rem\">Limited</span></td>" +
|
||||
"<td style=\"text-align:center;padding:1rem;color:var(--t3)\"><span style=\"color:#D4A017\">●</span><br><span style=\"font-size:0.7rem\">Limited</span></td>" +
|
||||
|
||||
@@ -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 = '<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');
|
||||
|
||||
+1
-1
@@ -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\"",
|
||||
|
||||
+2
-162
@@ -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 <no-reply@neurontechnologies.ai>",
|
||||
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: "<jwt>" }
|
||||
@@ -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)
|
||||
|
||||
+4
-4
@@ -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 →")
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
+13
-10
@@ -47,16 +47,16 @@ fn safety_cards() -> String {
|
||||
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "Hard Bell") +
|
||||
el_p("style=\"font-family:var(--body);font-weight:400;font-size:0.9375rem;color:var(--t1);margin-bottom:0.5rem\"", "A trusted contact the threat can't intercept") +
|
||||
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.875rem;color:var(--t2);line-height:1.7\"",
|
||||
"Any user can designate a Hard Bell contact - a friend, a relative, a colleague - set up in a calm moment, independent of any shared account. It's stored on your device only, never uploaded to our servers. It works the same way for everyone: adult, teen, or child."
|
||||
"Any user can designate a Hard Bell contact - a friend, a relative, a colleague - set up in a calm moment, independent of any shared account. When a signal warrants it, that contact is reached directly. It works the same way for everyone: adult, teen, or child."
|
||||
)
|
||||
)
|
||||
|
||||
let card2: String = el_div(
|
||||
"class=\"reveal card-dark\" style=\"transition-delay:150ms;" + card_style + "\"",
|
||||
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "Crisis resources") +
|
||||
el_p("style=\"font-family:var(--body);font-weight:400;font-size:0.9375rem;color:var(--t1);margin-bottom:0.5rem\"", "The help surfaces first - not a notification") +
|
||||
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "Emergency routing") +
|
||||
el_p("style=\"font-family:var(--body);font-weight:400;font-size:0.9375rem;color:var(--t1);margin-bottom:0.5rem\"", "Emergency services first - not notification") +
|
||||
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.875rem;color:var(--t2);line-height:1.7\"",
|
||||
"When Neuron detects a physical-danger or crisis signal, it surfaces crisis-line and emergency resources to you directly - 988, the domestic-violence hotline, Crisis Text Line. It evaluates the content of the signal, not the account type. No one in your contact list can disable or redirect this."
|
||||
"Physical danger and crisis signals route to emergency services and crisis lines. Neuron evaluates the content of the signal, not the account type. No one in your contact list can disable or redirect this path."
|
||||
)
|
||||
)
|
||||
|
||||
@@ -74,10 +74,10 @@ fn safety_cards() -> String {
|
||||
|
||||
let card4: String = el_div(
|
||||
"class=\"reveal card-dark\" style=\"transition-delay:250ms;" + card_style + "\"",
|
||||
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "On your device") +
|
||||
el_p("style=\"font-family:var(--body);font-weight:400;font-size:0.9375rem;color:var(--t1);margin-bottom:0.5rem\"", "Detection never leaves the machine") +
|
||||
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "Mandatory reporting") +
|
||||
el_p("style=\"font-family:var(--body);font-weight:400;font-size:0.9375rem;color:var(--t1);margin-bottom:0.5rem\"", "If real harm is intended, authorities may be contacted") +
|
||||
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.875rem;color:var(--t2);line-height:1.7\"",
|
||||
"Crisis detection runs locally, on device, with zero added latency. Triggers are logged on your machine only and never transmitted. Neuron, LLC never sees the signal, the conversation, or that a bell fired."
|
||||
"If Neuron detects credible, specific indicators of intended harm - to the user or to someone else - emergency services or relevant authorities may be contacted. Every user agrees to this at account creation. You cannot opt out."
|
||||
)
|
||||
)
|
||||
|
||||
@@ -90,7 +90,7 @@ fn safety_statements() -> String {
|
||||
let people_first: String = el_div(
|
||||
"class=\"reveal card-dark\" style=\"padding:2rem 2.5rem;border-left:3px solid var(--navy);margin-bottom:1.5rem\"",
|
||||
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.9375rem;color:var(--t2);line-height:1.8;margin-bottom:1.25rem\"",
|
||||
"This holds for anyone, not just children. When something sounds like serious self-harm or danger, Neuron stops and leads with help - it acknowledges what it heard, tells you it's concerned, and puts crisis resources in front of you instead of just answering the surface question."
|
||||
"This applies to anyone planning mass harm, a shooting, or serious self-harm. Not just child protection - anyone. The same logic holds: a misunderstanding can be resolved. A person's life can't be given back to them."
|
||||
) +
|
||||
el_p("style=\"font-family:var(--body);font-weight:700;font-size:1.125rem;color:var(--navy);line-height:1.4\"",
|
||||
"People first, always."
|
||||
@@ -99,7 +99,7 @@ fn safety_statements() -> String {
|
||||
|
||||
let stat_card1: String = el_div(
|
||||
"class=\"reveal card-dark\" style=\"padding:2rem 2.5rem;border-left:3px solid rgba(0,82,160,.30)\"",
|
||||
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "Why we never point you at your own contacts") +
|
||||
el_p("style=\"font-family:var(--body);font-size:0.75rem;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;color:var(--navy);margin-bottom:0.75rem\"", "Why the routing works this way") +
|
||||
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.9375rem;color:var(--t2);line-height:1.8;margin-bottom:0.75rem\"",
|
||||
"According to federal child maltreatment data (HHS, Child Maltreatment 2023), approximately 89% of child abuse victims are maltreated by a parent or caregiver. The emergency contact on file is not always a safe contact."
|
||||
) +
|
||||
@@ -117,7 +117,7 @@ fn safety_statements() -> String {
|
||||
"I recognize I will lose business over this. That doesn't matter to me."
|
||||
) +
|
||||
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.9375rem;color:var(--t2);line-height:1.8\"",
|
||||
"This is still local. Neuron, LLC does not see your conversations. When a safety signal fires, everything happens on your device - the help is surfaced to you locally. Our servers are never in the loop. We cannot be. That's the architecture."
|
||||
"This is still local. Neuron, LLC does not see your conversations. When a safety signal fires, the contact is made by your local instance - not by our servers. We are not in the loop. We cannot be. That's the architecture."
|
||||
)
|
||||
)
|
||||
|
||||
@@ -133,6 +133,9 @@ fn safety_statements() -> String {
|
||||
) +
|
||||
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.9375rem;color:var(--t2);line-height:1.8;margin-bottom:1rem\"",
|
||||
"If you don't have someone to designate, you can use 988 - the Suicide & Crisis Lifeline - as your Hard Bell contact. The system will accept it. The point is that no one goes in without a line out."
|
||||
) +
|
||||
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.9375rem;color:var(--t2);line-height:1.8\"",
|
||||
"I'm also establishing a Neuron crisis line - free, 24/7, staffed. Because I recognize that some people don't have anyone. The technology shouldn't make that worse. It should be the thing that catches you when nothing else does."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
+2
-2
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user