Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bb419dc98c | |||
| b87b0bed24 | |||
| ef352d2b8a | |||
| bb690c4690 | |||
| e148e6987d | |||
| 5885826316 | |||
| 9554430b7e | |||
| 9685a42c7d | |||
| cac986c5e1 | |||
| 9650dad951 | |||
| c3aec8947a | |||
| 441d6d7cb5 | |||
| de9bf25437 | |||
| a59fdf4baa | |||
| ae633d3f71 | |||
| 43b5286fd5 | |||
| 04641ed1a3 | |||
| f4a202e220 | |||
| 3482e7e0f5 | |||
| beee0f99a7 | |||
| 4b70e8c186 | |||
| f9a5f93070 | |||
| 8e2deab5cb | |||
| ddeca2250e | |||
| d228701828 | |||
| 41f27e83aa | |||
| 533436e2c2 | |||
| aeea037e6f | |||
| 41bad94368 | |||
| 3020b4e902 | |||
| e82425a829 | |||
| c4cdb31529 | |||
| a1c0cc090d | |||
| 7df96a2273 | |||
| d3b890b739 | |||
| 3f069eeb79 | |||
| 8676751ed6 | |||
| a4f5312069 | |||
| c76e5a19eb | |||
| 58b7b32cdd | |||
| 0fdabcce86 | |||
| 79de47de2c | |||
| 45963154d9 | |||
| aabaa2ffb0 | |||
| d5dcb08ec6 | |||
| 20a36eeb9e | |||
| 32a179c24a | |||
| 6bc026de19 | |||
| 0ae526b72e | |||
| 8221aef605 | |||
| f8487c43a0 | |||
| 36b99dd9e2 | |||
| cd93af38fb |
@@ -82,12 +82,7 @@ jobs:
|
||||
echo "Changed files:"
|
||||
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)
|
||||
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
|
||||
if [ -z "$NON_ASSET" ] && [ "$CHANGED" != "unknown" ]; then
|
||||
echo "asset_only=true" >> "$GITHUB_OUTPUT"
|
||||
echo "=> Asset-only change detected, will use fast path"
|
||||
else
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#
|
||||
# CI pre-build steps (in stage.yaml):
|
||||
# - neuron-web: built by `elb build` → dist/neuron-landing
|
||||
# Last rebuilt: 2026-05-11
|
||||
|
||||
FROM ubuntu:24.04
|
||||
|
||||
|
||||
Vendored
+20
-21
@@ -1,4 +1,3 @@
|
||||
// elhtml_impl.c — El HTML element stubs.
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
@@ -20,11 +19,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_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_h1(el_val_t attrs, el_val_t children);
|
||||
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 children);
|
||||
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 children);
|
||||
el_val_t el_h1(el_val_t attrs, el_val_t text);
|
||||
el_val_t el_h2(el_val_t attrs, el_val_t text);
|
||||
el_val_t el_h3(el_val_t attrs, el_val_t text);
|
||||
el_val_t el_h4(el_val_t attrs, el_val_t text);
|
||||
el_val_t el_button(el_val_t attrs, el_val_t label);
|
||||
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_textarea(el_val_t attrs, el_val_t value);
|
||||
@@ -177,43 +176,43 @@ el_val_t el_form(el_val_t attrs, el_val_t children) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t el_h1(el_val_t attrs, el_val_t children) {
|
||||
el_val_t el_h1(el_val_t attrs, el_val_t text) {
|
||||
if (str_eq(attrs, EL_STR(""))) {
|
||||
return el_str_concat(el_str_concat(EL_STR("<h1>"), children), EL_STR("</h1>"));
|
||||
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_concat(el_str_concat(EL_STR("<h1 "), attrs), EL_STR(">")), 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 0;
|
||||
}
|
||||
|
||||
el_val_t el_h2(el_val_t attrs, el_val_t children) {
|
||||
el_val_t el_h2(el_val_t attrs, el_val_t text) {
|
||||
if (str_eq(attrs, EL_STR(""))) {
|
||||
return el_str_concat(el_str_concat(EL_STR("<h2>"), children), EL_STR("</h2>"));
|
||||
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_concat(el_str_concat(EL_STR("<h2 "), attrs), EL_STR(">")), 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 0;
|
||||
}
|
||||
|
||||
el_val_t el_h3(el_val_t attrs, el_val_t children) {
|
||||
el_val_t el_h3(el_val_t attrs, el_val_t text) {
|
||||
if (str_eq(attrs, EL_STR(""))) {
|
||||
return el_str_concat(el_str_concat(EL_STR("<h3>"), children), EL_STR("</h3>"));
|
||||
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_concat(el_str_concat(EL_STR("<h3 "), attrs), EL_STR(">")), 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 0;
|
||||
}
|
||||
|
||||
el_val_t el_h4(el_val_t attrs, el_val_t children) {
|
||||
el_val_t el_h4(el_val_t attrs, el_val_t text) {
|
||||
if (str_eq(attrs, EL_STR(""))) {
|
||||
return el_str_concat(el_str_concat(EL_STR("<h4>"), children), EL_STR("</h4>"));
|
||||
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_concat(el_str_concat(EL_STR("<h4 "), attrs), EL_STR(">")), 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 0;
|
||||
}
|
||||
|
||||
el_val_t el_button(el_val_t attrs, el_val_t children) {
|
||||
el_val_t el_button(el_val_t attrs, el_val_t label) {
|
||||
if (str_eq(attrs, EL_STR(""))) {
|
||||
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("<button type=\"button\">"), 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 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 0;
|
||||
}
|
||||
|
||||
|
||||
+2
-63
@@ -77,23 +77,6 @@ static _Thread_local int _tl_arena_active = 0;
|
||||
* Allows serving PNGs and other binary files without strlen truncation. */
|
||||
static _Thread_local size_t _tl_fs_read_len = 0;
|
||||
|
||||
/* Binary body side-channel for http_response().
|
||||
*
|
||||
* http_response() normally JSON-encodes the body via jb_emit_escaped(), which
|
||||
* stops at the first null byte (C-string semantics). Binary files like PNGs
|
||||
* contain null bytes as early as byte 8 (IHDR chunk length), causing truncation.
|
||||
*
|
||||
* When _tl_fs_read_len > 0 at the time http_response() is called, we skip
|
||||
* JSON-encoding and instead:
|
||||
* 1. malloc-copy the raw bytes here
|
||||
* 2. write the sentinel string "__el_binary__" into the envelope body field
|
||||
* 3. In http_send_response(), detect the sentinel and use these raw bytes
|
||||
*
|
||||
* Thread-local so each worker thread has independent storage.
|
||||
* Lifecycle: set by http_response(), consumed (and freed) by http_send_response(). */
|
||||
static _Thread_local char* _tl_binary_body = NULL;
|
||||
static _Thread_local size_t _tl_binary_size = 0;
|
||||
|
||||
static void el_arena_track(char* p) {
|
||||
if (!_tl_arena_active || !p) return;
|
||||
if (_tl_arena.count >= _tl_arena.cap) {
|
||||
@@ -1553,22 +1536,10 @@ static void http_send_response(int fd, const char* body) {
|
||||
}
|
||||
|
||||
const char* eff_body = is_envelope ? env_body : body;
|
||||
int binary_side_channel = 0;
|
||||
|
||||
/* Binary side-channel: if the envelope body is the sentinel "__el_binary__",
|
||||
* http_response() stored the real bytes in _tl_binary_body/_tl_binary_size.
|
||||
* Substitute them here so http_send_all() sends the correct binary payload. */
|
||||
if (is_envelope && env_body && strcmp(env_body, "__el_binary__") == 0
|
||||
&& _tl_binary_body && _tl_binary_size > 0) {
|
||||
eff_body = _tl_binary_body;
|
||||
binary_side_channel = 1;
|
||||
}
|
||||
|
||||
/* Use the real byte count from fs_read if available (handles binary files
|
||||
* with embedded null bytes — PNG, WOFF2, etc.). Fall back to strlen for
|
||||
* normal text/JSON responses where _tl_fs_read_len is 0. */
|
||||
size_t blen = binary_side_channel ? _tl_binary_size
|
||||
: (_tl_fs_read_len > 0) ? _tl_fs_read_len : strlen(eff_body);
|
||||
size_t blen = (_tl_fs_read_len > 0) ? _tl_fs_read_len : strlen(eff_body);
|
||||
_tl_fs_read_len = 0; /* consume — one-shot per response */
|
||||
int head_only = _tl_http_head_only;
|
||||
|
||||
@@ -1616,13 +1587,6 @@ static void http_send_response(int fd, const char* body) {
|
||||
if (env_parsed_root) el_release(env_parsed_root);
|
||||
free(env_body);
|
||||
free(hdrs.buf);
|
||||
|
||||
/* Release binary side-channel if it was used (or left over from an error). */
|
||||
if (_tl_binary_body) {
|
||||
free(_tl_binary_body);
|
||||
_tl_binary_body = NULL;
|
||||
_tl_binary_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
@@ -1997,14 +1961,6 @@ el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body) {
|
||||
const char* b = EL_CSTR(body);
|
||||
if (!b) b = "";
|
||||
|
||||
/* Capture binary length BEFORE clearing _tl_fs_read_len.
|
||||
* If the body came from fs_read(), _tl_fs_read_len holds the real byte
|
||||
* count. jb_emit_escaped() stops at the first NUL byte, so we cannot
|
||||
* JSON-encode binary data directly. Instead we copy it to a thread-local
|
||||
* side-channel and write the sentinel "__el_binary__" into the envelope.
|
||||
* http_send_response() detects the sentinel and uses the side-channel. */
|
||||
size_t binary_len = _tl_fs_read_len;
|
||||
|
||||
/* Clear the fs_read binary-length hint: the envelope we're about to build
|
||||
* is a fresh JSON string, not the raw file bytes. Without this reset,
|
||||
* http_worker would use the stale _tl_fs_read_len (= original file size)
|
||||
@@ -2012,18 +1968,6 @@ el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body) {
|
||||
* http_send_response and http_parse_envelope. */
|
||||
_tl_fs_read_len = 0;
|
||||
|
||||
if (binary_len > 0) {
|
||||
/* Binary body path: store raw bytes in thread-local, emit sentinel. */
|
||||
free(_tl_binary_body); /* discard any stale binary from a prior error path */
|
||||
_tl_binary_body = malloc(binary_len);
|
||||
if (_tl_binary_body) {
|
||||
memcpy(_tl_binary_body, b, binary_len);
|
||||
_tl_binary_size = binary_len;
|
||||
} else {
|
||||
_tl_binary_size = 0; /* malloc failed — fall through to empty body */
|
||||
}
|
||||
}
|
||||
|
||||
JsonBuf out; jb_init(&out);
|
||||
jb_puts(&out, EL_HTTP_RESPONSE_TAG); /* {"el_http_response":1 */
|
||||
jb_puts(&out, ",\"status\":");
|
||||
@@ -2033,12 +1977,7 @@ el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body) {
|
||||
jb_puts(&out, ",\"headers\":");
|
||||
jb_puts(&out, hj);
|
||||
jb_puts(&out, ",\"body\":");
|
||||
if (binary_len > 0 && _tl_binary_body) {
|
||||
/* Sentinel: http_send_response() will substitute the real bytes. */
|
||||
jb_puts(&out, "\"__el_binary__\"");
|
||||
} else {
|
||||
jb_emit_escaped(&out, b);
|
||||
}
|
||||
jb_emit_escaped(&out, b);
|
||||
jb_putc(&out, '}');
|
||||
return el_wrap_str(out.buf);
|
||||
}
|
||||
|
||||
+2
-26
@@ -98,7 +98,7 @@ fn checkout_page(plan: String, pub_key: String) -> String {
|
||||
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("", "Plugin marketplace access (coming soon)")
|
||||
+ el_li("", "Core built-in capabilities")
|
||||
+ el_li("", "2 devices included")
|
||||
} else {
|
||||
@@ -345,31 +345,7 @@ fn checkout_page(plan: String, pub_key: String) -> String {
|
||||
}
|
||||
|
||||
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 {
|
||||
let css: String = ".checkout-plan-name {
|
||||
font-family: var(--head);
|
||||
font-size: clamp(1.5rem, 3vw, 2rem);
|
||||
font-weight: 600;
|
||||
|
||||
+17
-3
@@ -1,6 +1,20 @@
|
||||
// checkout-free.el -- RETIRED. Free plan now uses the standard Stripe
|
||||
// payment flow (checkout-stripe.el) with a $0 PaymentIntent for age
|
||||
// verification. This file is no longer compiled or loaded.
|
||||
// checkout-free.el -- Free plan: show success panel after auth completes.
|
||||
// Watches the auth-badge element; when it becomes visible, hides the auth
|
||||
// section and shows the free-success panel. No card required for free tier.
|
||||
// Compiled with: elc --target=js --bundle --minify --obfuscate
|
||||
|
||||
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);
|
||||
})()")
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ fn main() -> Void {
|
||||
if (spinner) spinner.style.display = loading ? '' : 'none';
|
||||
}
|
||||
|
||||
// Free plan: Stripe SetupIntent for age verification (card saved, never charged).
|
||||
// Falls through to the same Stripe init path — server returns setup_mode=true for free.
|
||||
// Free plan has no payment form — bail out entirely.
|
||||
if (str_eq(PLAN, 'free')) return;
|
||||
|
||||
window._neuronMode = 'payment';
|
||||
var paymentEl = null;
|
||||
@@ -101,7 +101,7 @@ fn main() -> Void {
|
||||
if (submitLabel) {
|
||||
submitLabel.textContent = window._neuronMode === 'setup'
|
||||
? 'Save my card - no charge today →'
|
||||
: PLAN === 'free' ? 'Verify age & get started →' : 'Complete purchase →';
|
||||
: 'Complete purchase →';
|
||||
}
|
||||
waitForStripe(function() {
|
||||
if (!stripe) stripe = Stripe(STRIPE_PK);
|
||||
|
||||
+14
-12
@@ -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. A card verifies you're 18+ — you won't be charged. Your AI that remembers you, runs on your machine, never resets."
|
||||
"Create your free Neuron account. 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."
|
||||
}
|
||||
@@ -697,21 +697,23 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
|
||||
}
|
||||
}
|
||||
|
||||
// Free tier: $0 PaymentIntent for age verification (18+ requirement).
|
||||
// Verifies card is valid. No charge, no capture.
|
||||
// Note: setup_future_usage cannot be used with amount=0.
|
||||
// Free tier: creates a SetupIntent for age verification (18+ requirement).
|
||||
// No charge — but the user must provide a valid payment method.
|
||||
if str_eq(plan, "free") {
|
||||
let free_pi_body: String = "amount=0"
|
||||
+ "¤cy=usd"
|
||||
+ "&payment_method_types[]=card"
|
||||
let free_si_body: String = "automatic_payment_methods[enabled]=true"
|
||||
+ "&usage=off_session"
|
||||
+ "&metadata[plan]=free"
|
||||
+ "&metadata[purpose]=age_verification"
|
||||
let free_pi_body = if !str_eq(pi_cus_id, "") { free_pi_body + "&customer=" + pi_cus_id } else { free_pi_body }
|
||||
let free_pi_resp: String = http_post_form_auth(
|
||||
"https://api.stripe.com/v1/payment_intents",
|
||||
free_pi_body,
|
||||
let free_si_body = if !str_eq(pi_cus_id, "") { free_si_body + "&customer=" + pi_cus_id } else { free_si_body }
|
||||
let free_si_resp: String = http_post_form_auth(
|
||||
"https://api.stripe.com/v1/setup_intents",
|
||||
free_si_body,
|
||||
auth_header)
|
||||
return free_pi_resp
|
||||
if str_starts_with(free_si_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
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ fn marketplace_categories() -> String {
|
||||
|
||||
el_div(
|
||||
"class=\"marketplace-categories reveal\" style=\"transition-delay:320ms\"",
|
||||
marketplace_tags_block("Connectors - day one", connectors) +
|
||||
marketplace_tags_block("Connectors (rolling out)", connectors) +
|
||||
marketplace_tags_block("Following launch", following) +
|
||||
el_div(
|
||||
"",
|
||||
|
||||
+2
-2
@@ -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("", "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("", "3 marketplace plugins included")) +
|
||||
el_li("", el_span("class=\"dash\"", "-") + el_span("", "Plugin marketplace access (coming soon)")) +
|
||||
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-cadence\"", "forever")
|
||||
) +
|
||||
el_p("class=\"pricing-tagline\"", "Start building your memory. Card required for age verification — you won't be charged.") +
|
||||
el_p("class=\"pricing-tagline\"", "Start building your memory. No card required.") +
|
||||
el_ul("class=\"pricing-features\"", pricing_free_features()) +
|
||||
el_div("style=\"flex:1\"", "") +
|
||||
el_div(
|
||||
|
||||
+10
-13
@@ -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. When a signal warrants it, that contact is reached directly. 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. It's stored on your device only, never uploaded to our servers. 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\"", "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-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-weight:300;font-size:0.875rem;color:var(--t2);line-height:1.7\"",
|
||||
"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."
|
||||
"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."
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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\"", "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-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-weight:300;font-size:0.875rem;color:var(--t2);line-height:1.7\"",
|
||||
"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."
|
||||
"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."
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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 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."
|
||||
"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."
|
||||
) +
|
||||
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 the routing works this way") +
|
||||
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-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, 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."
|
||||
"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."
|
||||
)
|
||||
)
|
||||
|
||||
@@ -133,9 +133,6 @@ 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."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ 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.",
|
||||
"Every AI resets when you close the tab. Neuron doesn't. Runs on your machine. Remembers everything. Start free — 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."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user