Compare commits

..

13 Commits

Author SHA1 Message Date
will.anderson 7be2b49300 Free plan: use $0 PaymentIntent instead of SetupIntent for age verification
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m45s
All plans now use the same payment intent flow. Free creates a $0 PI
with payment_method_types[]=card and setup_future_usage=off_session.
No charge, card saved. Removes setup_mode=true for free plan.

Fix submit button label: show 'Verify age & get started' for free
instead of 'Complete purchase'. Retire checkout-free.el.
2026-05-11 19:45:39 -05:00
will.anderson e5c05cbece Merge branch 'dev' of git.neuralplatform.ai:neuron-technologies/neuron-web into dev 2026-05-11 19:16:18 -05:00
will.anderson c7f4d0248c Merge pull request 'fix: free checkout Stripe SetupIntent + remove no-card-required copy' (#123) from fix/free-checkout-stripe into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m14s
2026-05-12 00:16:12 +00:00
will.anderson 4c5d67c321 fix: free checkout requires Stripe SetupIntent for age verification; update copy
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m43s
2026-05-11 19:15:57 -05:00
will.anderson 9feb9e24b6 Merge branch 'dev' of git.neuralplatform.ai:neuron-technologies/neuron-web into dev 2026-05-11 18:56:43 -05:00
will.anderson 941faccb3f Merge pull request 'fix: force full build when no diff or stage-latest missing' (#121) from fix/stage-full-build into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m16s
2026-05-11 23:56:35 +00:00
will.anderson 6a040afcc5 fix: force full build when no diff or stage-latest missing
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m43s
2026-05-11 18:56:18 -05:00
will.anderson a346a2197e Merge branch 'dev' of git.neuralplatform.ai:neuron-technologies/neuron-web into dev 2026-05-11 18:46:33 -05:00
will.anderson e268b424f5 Merge pull request 'ci: touch dist to trigger stage rebuild' (#119) from ci/touch-dist into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m2s
2026-05-11 23:46:17 +00:00
will.anderson 20029d36df ci: touch dist to trigger stage rebuild
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m35s
2026-05-11 18:45:57 -05:00
will.anderson 54d48ed679 ci: trigger rebuild after registry cleanup 2026-05-11 17:33:53 -05:00
will.anderson 28f9ecd1a3 Merge pull request 'fix: heading and button elements pass children unescaped' (#113) from fix/force-full-rebuild into dev
Dev — Build & local smoke test / build-smoke (push) Successful in 2m0s
2026-05-11 22:21:41 +00:00
will.anderson b6bb25e79e fix: heading and button elements pass children unescaped
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 1m32s
el_h1/h2/h3/h4 and el_button were calling el_escape() on their
content, converting any HTML children (e.g. <span class="gold">)
into literal entity text on screen.

These functions accept composed HTML children, not raw text — they
should pass the argument through like el_div/el_p/el_span do.
el_text, el_attr, el_title, el_textarea, and el_img keep escaping
(they handle actual text/attribute values, not HTML children).
2026-05-11 17:21:19 -05:00
11 changed files with 64 additions and 70 deletions
+6 -1
View File
@@ -82,7 +82,12 @@ jobs:
echo "Changed files:" echo "Changed files:"
echo "$CHANGED" echo "$CHANGED"
NON_ASSET=$(echo "$CHANGED" | grep -v '^src/assets/' | grep -v '^src/shares/' | grep -v '^src/index\.html' | grep -v '^src/about\.html' | grep -v '^src/terms\.html' | grep -v '^src/enterprise-terms\.html' | grep -v '^src/llms\.txt' | grep -v '^migrations/' | grep -v '^scripts/' | grep -v '^tests/' | grep -v '^\.gitea/' | grep -v '^$' || true) NON_ASSET=$(echo "$CHANGED" | grep -v '^src/assets/' | grep -v '^src/shares/' | grep -v '^src/index\.html' | grep -v '^src/about\.html' | grep -v '^src/terms\.html' | grep -v '^src/enterprise-terms\.html' | grep -v '^src/llms\.txt' | grep -v '^migrations/' | grep -v '^scripts/' | grep -v '^tests/' | grep -v '^\.gitea/' | grep -v '^$' || true)
if [ -z "$NON_ASSET" ] && [ "$CHANGED" != "unknown" ]; then if [ -z "$CHANGED" ] || [ "$CHANGED" = "unknown" ]; then
# No diff (workflow_dispatch with no new commits, or git error).
# Registry may not have a stage-latest base image, so force full build.
echo "asset_only=false" >> "$GITHUB_OUTPUT"
echo "=> No changed files detected (workflow_dispatch?), forcing full build"
elif [ -z "$NON_ASSET" ]; then
echo "asset_only=true" >> "$GITHUB_OUTPUT" echo "asset_only=true" >> "$GITHUB_OUTPUT"
echo "=> Asset-only change detected, will use fast path" echo "=> Asset-only change detected, will use fast path"
else else
+1
View File
@@ -9,6 +9,7 @@
# #
# CI pre-build steps (in stage.yaml): # CI pre-build steps (in stage.yaml):
# - neuron-web: built by `elb build` → dist/neuron-landing # - neuron-web: built by `elb build` → dist/neuron-landing
# Last rebuilt: 2026-05-11
FROM ubuntu:24.04 FROM ubuntu:24.04
+21 -20
View File
@@ -1,3 +1,4 @@
// elhtml_impl.c — El HTML element stubs.
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include "el_runtime.h" #include "el_runtime.h"
@@ -19,11 +20,11 @@ el_val_t el_li(el_val_t attrs, el_val_t children);
el_val_t el_p(el_val_t attrs, el_val_t children); el_val_t el_p(el_val_t attrs, el_val_t children);
el_val_t el_span(el_val_t attrs, el_val_t children); el_val_t el_span(el_val_t attrs, el_val_t children);
el_val_t el_form(el_val_t attrs, el_val_t children); el_val_t el_form(el_val_t attrs, el_val_t children);
el_val_t el_h1(el_val_t attrs, el_val_t text); el_val_t el_h1(el_val_t attrs, el_val_t children);
el_val_t el_h2(el_val_t attrs, el_val_t text); el_val_t el_h2(el_val_t attrs, el_val_t children);
el_val_t el_h3(el_val_t attrs, el_val_t text); el_val_t el_h3(el_val_t attrs, el_val_t children);
el_val_t el_h4(el_val_t attrs, el_val_t text); el_val_t el_h4(el_val_t attrs, el_val_t children);
el_val_t el_button(el_val_t attrs, el_val_t label); el_val_t el_button(el_val_t attrs, el_val_t children);
el_val_t el_a(el_val_t href, el_val_t attrs, el_val_t children); el_val_t el_a(el_val_t href, el_val_t attrs, el_val_t children);
el_val_t el_input(el_val_t type_attr, el_val_t attrs); el_val_t el_input(el_val_t type_attr, el_val_t attrs);
el_val_t el_textarea(el_val_t attrs, el_val_t value); el_val_t el_textarea(el_val_t attrs, el_val_t value);
@@ -176,43 +177,43 @@ el_val_t el_form(el_val_t attrs, el_val_t children) {
return 0; return 0;
} }
el_val_t el_h1(el_val_t attrs, el_val_t text) { el_val_t el_h1(el_val_t attrs, el_val_t children) {
if (str_eq(attrs, EL_STR(""))) { if (str_eq(attrs, EL_STR(""))) {
return el_str_concat(el_str_concat(EL_STR("<h1>"), el_escape(text)), EL_STR("</h1>")); return el_str_concat(el_str_concat(EL_STR("<h1>"), children), EL_STR("</h1>"));
} }
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h1 "), attrs), EL_STR(">")), el_escape(text)), EL_STR("</h1>")); return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h1 "), attrs), EL_STR(">")), children), EL_STR("</h1>"));
return 0; return 0;
} }
el_val_t el_h2(el_val_t attrs, el_val_t text) { el_val_t el_h2(el_val_t attrs, el_val_t children) {
if (str_eq(attrs, EL_STR(""))) { if (str_eq(attrs, EL_STR(""))) {
return el_str_concat(el_str_concat(EL_STR("<h2>"), el_escape(text)), EL_STR("</h2>")); return el_str_concat(el_str_concat(EL_STR("<h2>"), children), EL_STR("</h2>"));
} }
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h2 "), attrs), EL_STR(">")), el_escape(text)), EL_STR("</h2>")); return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h2 "), attrs), EL_STR(">")), children), EL_STR("</h2>"));
return 0; return 0;
} }
el_val_t el_h3(el_val_t attrs, el_val_t text) { el_val_t el_h3(el_val_t attrs, el_val_t children) {
if (str_eq(attrs, EL_STR(""))) { if (str_eq(attrs, EL_STR(""))) {
return el_str_concat(el_str_concat(EL_STR("<h3>"), el_escape(text)), EL_STR("</h3>")); return el_str_concat(el_str_concat(EL_STR("<h3>"), children), EL_STR("</h3>"));
} }
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h3 "), attrs), EL_STR(">")), el_escape(text)), EL_STR("</h3>")); return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h3 "), attrs), EL_STR(">")), children), EL_STR("</h3>"));
return 0; return 0;
} }
el_val_t el_h4(el_val_t attrs, el_val_t text) { el_val_t el_h4(el_val_t attrs, el_val_t children) {
if (str_eq(attrs, EL_STR(""))) { if (str_eq(attrs, EL_STR(""))) {
return el_str_concat(el_str_concat(EL_STR("<h4>"), el_escape(text)), EL_STR("</h4>")); return el_str_concat(el_str_concat(EL_STR("<h4>"), children), EL_STR("</h4>"));
} }
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h4 "), attrs), EL_STR(">")), el_escape(text)), EL_STR("</h4>")); return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<h4 "), attrs), EL_STR(">")), children), EL_STR("</h4>"));
return 0; return 0;
} }
el_val_t el_button(el_val_t attrs, el_val_t label) { el_val_t el_button(el_val_t attrs, el_val_t children) {
if (str_eq(attrs, EL_STR(""))) { if (str_eq(attrs, EL_STR(""))) {
return el_str_concat(el_str_concat(EL_STR("<button type=\"button\">"), el_escape(label)), EL_STR("</button>")); return el_str_concat(el_str_concat(EL_STR("<button type=\"button\">"), children), EL_STR("</button>"));
} }
return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<button type=\"button\" "), attrs), EL_STR(">")), el_escape(label)), EL_STR("</button>")); return el_str_concat(el_str_concat(el_str_concat(el_str_concat(EL_STR("<button type=\"button\" "), attrs), EL_STR(">")), children), EL_STR("</button>"));
return 0; return 0;
} }
+1 -1
View File
@@ -98,7 +98,7 @@ fn checkout_page(plan: String, pub_key: String) -> String {
el_li("", "Persistent memory - never resets") el_li("", "Persistent memory - never resets")
+ el_li("", "Local inference via Ollama (coming)") + el_li("", "Local inference via Ollama (coming)")
+ el_li("", "Bring your own API keys") + el_li("", "Bring your own API keys")
+ el_li("", "Plugin marketplace access (coming soon)") + el_li("", "3 marketplace plugins included")
+ el_li("", "Core built-in capabilities") + el_li("", "Core built-in capabilities")
+ el_li("", "2 devices included") + el_li("", "2 devices included")
} else { } else {
+3 -17
View File
@@ -1,20 +1,6 @@
// checkout-free.el -- Free plan: show success panel after auth completes. // checkout-free.el -- RETIRED. Free plan now uses the standard Stripe
// Watches the auth-badge element; when it becomes visible, hides the auth // payment flow (checkout-stripe.el) with a $0 PaymentIntent for age
// section and shows the free-success panel. No card required for free tier. // verification. This file is no longer compiled or loaded.
// Compiled with: elc --target=js --bundle --minify --obfuscate
fn main() -> Void { fn main() -> Void {
native_js("(function() {
var success = document.getElementById('free-success');
var auth = document.getElementById('auth-section');
if (!success) return;
var timer = setInterval(function() {
var badge = document.getElementById('auth-badge');
if (badge && badge.offsetParent !== null) {
if (auth) auth.style.display = 'none';
success.style.display = '';
clearInterval(timer);
}
}, 150);
})()")
} }
+3 -3
View File
@@ -31,8 +31,8 @@ fn main() -> Void {
if (spinner) spinner.style.display = loading ? '' : 'none'; if (spinner) spinner.style.display = loading ? '' : 'none';
} }
// Free plan has no payment form — bail out entirely. // Free plan: Stripe SetupIntent for age verification (card saved, never charged).
if (str_eq(PLAN, 'free')) return; // Falls through to the same Stripe init path — server returns setup_mode=true for free.
window._neuronMode = 'payment'; window._neuronMode = 'payment';
var paymentEl = null; var paymentEl = null;
@@ -101,7 +101,7 @@ fn main() -> Void {
if (submitLabel) { if (submitLabel) {
submitLabel.textContent = window._neuronMode === 'setup' submitLabel.textContent = window._neuronMode === 'setup'
? 'Save my card - no charge today →' ? 'Save my card - no charge today →'
: 'Complete purchase →'; : PLAN === 'free' ? 'Verify age & get started →' : 'Complete purchase →';
} }
waitForStripe(function() { waitForStripe(function() {
if (!stripe) stripe = Stripe(STRIPE_PK); if (!stripe) stripe = Stripe(STRIPE_PK);
+12 -14
View File
@@ -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." "Secure your Founding Member spot. Pay once, $199 lifetime — Neuron inference included at launch, priced below the major APIs. First 1,000 only."
} else { } else {
if str_eq(plan, "free") { 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." "Create your free Neuron account. A card verifies you're 18+ — you won't be charged. Your AI that remembers you, runs on your machine, never resets."
} else { } else {
"Subscribe to Neuron Professional for $19/month. The AI that remembers you — persistent memory, runs locally, bring your own API keys." "Subscribe to Neuron Professional for $19/month. The AI that remembers you — persistent memory, runs locally, bring your own API keys."
} }
@@ -697,23 +697,21 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
} }
} }
// Free tier: creates a SetupIntent for age verification (18+ requirement). // Free tier: $0 PaymentIntent for age verification (18+ requirement).
// No charge but the user must provide a valid payment method. // Card is verified and saved (setup_future_usage=off_session). No charge.
if str_eq(plan, "free") { if str_eq(plan, "free") {
let free_si_body: String = "automatic_payment_methods[enabled]=true" let free_pi_body: String = "amount=0"
+ "&usage=off_session" + "&currency=usd"
+ "&payment_method_types[]=card"
+ "&setup_future_usage=off_session"
+ "&metadata[plan]=free" + "&metadata[plan]=free"
+ "&metadata[purpose]=age_verification" + "&metadata[purpose]=age_verification"
let free_si_body = if !str_eq(pi_cus_id, "") { free_si_body + "&customer=" + pi_cus_id } else { free_si_body } let free_pi_body = if !str_eq(pi_cus_id, "") { free_pi_body + "&customer=" + pi_cus_id } else { free_pi_body }
let free_si_resp: String = http_post_form_auth( let free_pi_resp: String = http_post_form_auth(
"https://api.stripe.com/v1/setup_intents", "https://api.stripe.com/v1/payment_intents",
free_si_body, free_pi_body,
auth_header) auth_header)
if str_starts_with(free_si_resp, "{") { return free_pi_resp
let inner: String = str_slice(free_si_resp, 1, str_len(free_si_resp))
return "{\"setup_mode\":true,\"plan\":\"free\"," + inner
}
return free_si_resp
} }
// Setup-mode path: save payment method, do not charge. Only valid // Setup-mode path: save payment method, do not charge. Only valid
+1 -1
View File
@@ -77,7 +77,7 @@ fn marketplace_categories() -> String {
el_div( el_div(
"class=\"marketplace-categories reveal\" style=\"transition-delay:320ms\"", "class=\"marketplace-categories reveal\" style=\"transition-delay:320ms\"",
marketplace_tags_block("Connectors (rolling out)", connectors) + marketplace_tags_block("Connectors - day one", connectors) +
marketplace_tags_block("Following launch", following) + marketplace_tags_block("Following launch", following) +
el_div( el_div(
"", "",
+2 -2
View File
@@ -36,7 +36,7 @@ fn pricing_free_features() -> String {
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Local inference via Ollama (coming)")) + el_li("", el_span("class=\"dash\"", "-") + el_span("", "Local inference via Ollama (coming)")) +
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Neuron Inference included when it launches - Q3 2026")) + el_li("", el_span("class=\"dash\"", "-") + el_span("", "Neuron Inference included when it launches - Q3 2026")) +
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Unlimited projects")) + el_li("", el_span("class=\"dash\"", "-") + el_span("", "Unlimited projects")) +
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Plugin marketplace access (coming soon)")) + el_li("", el_span("class=\"dash\"", "-") + el_span("", "3 marketplace plugins included")) +
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Core built-in capabilities")) el_li("", el_span("class=\"dash\"", "-") + el_span("", "Core built-in capabilities"))
} }
@@ -91,7 +91,7 @@ fn pricing(sold: Int, total: Int) -> String {
el_span("class=\"pricing-price\"", "$0") + el_span("class=\"pricing-price\"", "$0") +
el_span("class=\"pricing-cadence\"", "forever") el_span("class=\"pricing-cadence\"", "forever")
) + ) +
el_p("class=\"pricing-tagline\"", "Start building your memory. No card required.") + el_p("class=\"pricing-tagline\"", "Start building your memory. Card required for age verification — you won't be charged.") +
el_ul("class=\"pricing-features\"", pricing_free_features()) + el_ul("class=\"pricing-features\"", pricing_free_features()) +
el_div("style=\"flex:1\"", "") + el_div("style=\"flex:1\"", "") +
el_div( el_div(
+13 -10
View File
@@ -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-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&#39;t intercept") + 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&#39;t intercept") +
el_p("style=\"font-family:var(--body);font-weight:300;font-size:0.875rem;color:var(--t2);line-height:1.7\"", 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&#39;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( let card2: String = el_div(
"class=\"reveal card-dark\" style=\"transition-delay:150ms;" + card_style + "\"", "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-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\"", "The help surfaces first - not a notification") + 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\"", 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( let card4: String = el_div(
"class=\"reveal card-dark\" style=\"transition-delay:250ms;" + card_style + "\"", "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-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\"", "Detection never leaves the machine") + 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\"", 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( 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\"", "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\"", 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&#39;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&#39;s life can&#39;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\"", el_p("style=\"font-family:var(--body);font-weight:700;font-size:1.125rem;color:var(--navy);line-height:1.4\"",
"People first, always." "People first, always."
@@ -99,7 +99,7 @@ fn safety_statements() -> String {
let stat_card1: String = el_div( let stat_card1: String = el_div(
"class=\"reveal card-dark\" style=\"padding:2rem 2.5rem;border-left:3px solid rgba(0,82,160,.30)\"", "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\"", 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." "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&#39;t matter to me." "I recognize I will lose business over this. That doesn&#39;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\"", 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&#39;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&#39;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\"", 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&#39;t have someone to designate, you can use 988 - the Suicide &amp; Crisis Lifeline - as your Hard Bell contact. The system will accept it. The point is that no one goes in without a line out." "If you don&#39;t have someone to designate, you can use 988 - the Suicide &amp; 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&#39;m also establishing a Neuron crisis line - free, 24/7, staffed. Because I recognize that some people don&#39;t have anyone. The technology shouldn&#39;t make that worse. It should be the thing that catches you when nothing else does."
) )
) )
+1 -1
View File
@@ -78,7 +78,7 @@ fn page_head() -> String {
return page_head_base() return page_head_base()
+ page_seo_block( + page_seo_block(
"Neuron — The AI That Remembers You", "Neuron — The AI That Remembers You",
"Every AI resets when you close the tab. Neuron doesn&#39;t. Runs on your machine. Remembers everything. Start free — no credit card required.", "Every AI resets when you close the tab. Neuron doesn&#39;t. Runs on your machine. Remembers everything. Start free.",
"/", "/",
"Every other AI forgets you. Neuron doesn&#39;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&#39;t. Runs on your machine, builds a persistent memory over time, and gets sharper the longer you use it. Free tier available."
) )