Commit Graph

58 Commits

Author SHA1 Message Date
Will Anderson 5cb13d67f7 feat: convert web El source to native HTML template syntax
Replace all return "..." HTML string literals with native El templates —
removes all \" escapes, converts + interpolations to {expr}/{raw(expr)},
and replaces conditional string concatenation with {#if}/{#else}/{/if}.
No functional changes; output is identical.
2026-05-05 05:21:19 -05:00
Will Anderson 70820cf078 feat(chat): IP-keyed daily rate limit (10/day), live reset countdown, web_demo Anthropic key 2026-05-05 04:10:22 -05:00
Will Anderson 260ea4edaa Remove letter.html 2026-05-05 03:51:30 -05:00
Will Anderson 566cd568b7 Add Google Ads conversion tracking (AW-18140150015) 2026-05-05 03:49:14 -05:00
Will Anderson 94f6e749a0 Add El source files for all client-side JS
Recovers original JS from git history and ports it into proper El source
files under src/js/. Each file wraps the original JS in a native_js call
inside a main() function, making it valid El that compiles to a
self-contained IIFE via elc --target=js --bundle.

Files added:
  src/js/account-auth.el       - Supabase OTP magic-link (sendMagicLink)
  src/js/account-dashboard.el  - Account dashboard: session, plan card, family
  src/js/chat-widget.el        - Demo chat widget (neuronDemoToggle/Send/Reset)
  src/js/checkout-auth.el      - Checkout auth: OAuth, email sign-in/up
  src/js/checkout-free.el      - Free plan: auth-badge watch -> payment reveal
  src/js/checkout-stripe.el    - Stripe Payment Element (reads NEURON_CFG)
  src/js/enterprise.el         - Enterprise inquiry form + headcount filter
  src/js/environmental.el      - Efficiency calculator slider
  src/js/gallery.el            - Gallery nav, search/sort, Supabase voting
  src/js/main.el               - Share page voting + copyForPlatform
  src/js/marketplace.el        - Developer interest form
  src/js/nav.el                - Nav hamburger + Mission dropdown
  src/js/styles.el             - Landing: nav scroll, reveal, founding counter
2026-05-04 11:23:21 -05:00
Will Anderson 246a5f0967 Fix gallery HTML structure bug and replace email auth with OTP flow
Gallery: remove <a> from share allowlist. Gallery cards wrap content in
<a class="gal-link">; allowing <a> in sanitized answer HTML causes nested
anchors that the HTML5 adoption agency algorithm resolves by restructuring
the DOM, producing mismatched </div> tags that leave gallery-grid open and
pull sibling elements into the grid as spurious grid columns.

Account: replace email+password sign-up/sign-in with magic-link OTP.
supabase.auth.signInWithOtp handles both new and existing users in one
flow. Existing onAuthStateChange listener (dadeb8ddb9a8.js) retained for
post-redirect dashboard display. sendMagicLink added to extract-js
RESERVED_GLOBALS so the obfuscator does not mangle the onclick reference.
2026-05-04 10:04:22 -05:00
Will Anderson 42f0786f97 feat(account): add email/password sign-up to account page
Dev — Build & local smoke test / build-smoke (push) Successful in 2m34s
The sign-in form only offered social auth and a link to /checkout.
Users wanting to create an account directly had no path.

Changes:
- "No account? Create one" toggle replaces the old "Choose a plan" link
- switchToSignUp() / switchToSignIn() toggle button label, placeholder,
  and autocomplete between sign-in and sign-up modes
- Explicit signUpWithEmail() calls signUp() directly; with autoconfirm
  enabled it returns a session immediately and reloads into the dashboard
- signInWithEmail() simplified: no silent sign-up fallback, clean errors
- Re-extract account JS (6dafc1586705 -> dadeb8ddb9a8)
- Re-extract styles chat JS (de72b8b61d75 -> 02ecc8cf6542) as side effect
  of extract-js.py run
2026-05-04 08:16:20 -05:00
Will Anderson 0508cd77fd fix(chat): raise history cap and add 30s frontend timeout
Deploy marketing to Cloud Run / deploy (push) Successful in 3m43s
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 3m26s
Dev — Build & local smoke test / build-smoke (push) Successful in 2m29s
The demo chat was silently dropping conversation context past 40 turns
and leaving the thinking bubble spinning forever when the soul backend
hung — visitors saw a frozen UI with no way to know what went wrong.

Changes:
  - Stored history cap raised from 40 → 200 messages so longer
    conversations actually persist across page refreshes.
  - History sent to backend per turn raised from 20 → 50 messages.
  - 30s AbortController timeout on the /api/demo fetch — surfaces a
    distinct "Took too long to respond" error instead of hanging.
  - Restore script (restore-chat-js-with-preview.py) is now correctly
    idempotent in both directions: detects when modal HTML is inlined
    but chat JS got extracted to an asset, and re-injects fresh source
    so extract-js picks up changes on the next build.
2026-05-04 01:57:38 -05:00
Will Anderson 4c5d4b3c84 fix(chat): restore history on Turnstile verification
Turnstile callback unconditionally showed the greeting message, wiping
session history for returning visitors who hadn't verified yet.

Callback now uses the same restoreOrGreet pattern as neuronDemoToggle:
replays prior messages if session.messages is non-empty, else shows the
greeting once and marks session.greeted.

Also extracts the gallery voting inline script (a49ca0a129e8.js) as a
side effect of re-running extract-js.py. Chat JS rebuilds to de72b8b61d75.js.
2026-05-03 19:51:59 -05:00
Will Anderson 047be5ae02 Remove chat with Neuron links from gallery page
Deploy marketing to Cloud Run / deploy (push) Successful in 3m57s
Dev — Build & local smoke test / build-smoke (push) Successful in 2m19s
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 2m34s
2026-05-03 19:12:35 -05:00
Will Anderson c75d8a9563 ci: add gitflow — dev/stage/main branches with CI workflows
Dev — Build & local smoke test / build-smoke (push) Successful in 2m51s
Stage — Build, push & deploy to marketing-stage / deploy-stage (push) Successful in 2m52s
Deploy marketing to Cloud Run / deploy (push) Successful in 3m37s
- dev.yaml: build + local docker smoke test only (no push, no deploy)
- stage.yaml: build + push + deploy to marketing-stage + smoke test (stops here)
- deploy.yaml: add HTML placeholder touch step before docker build

Proper human gate between stage and prod: the stage→main merge decision.
2026-05-03 11:28:43 -05:00
Will Anderson 102343c8fe fix(gallery): implement voting JS + fix change-vote server path
Voting was completely broken: gallery.el referenced d8251f5e5aa1.js
which was never written. Buttons rendered disabled with no JS to
enable them, load vote state, or call /api/vote.

Fix 1 — client: inline the voting script directly in gallery.el.
Initializes Supabase client from window.NEURON_CFG, calls
/api/vote-state/<id> on load (with JWT if signed in) to populate
scores and active states, wires vote buttons with toggle logic
(same direction = undo/none), handles sign-in modal with magic-link
flow, re-loads all vote states on auth state change.

Fix 2 — server: replace supabase_upsert_user (upsert via user JWT)
with delete-then-insert. Upsert requires both INSERT + UPDATE RLS
policies; the UPDATE policy is typically absent on share_votes.
Delete (user JWT, RLS-safe) + insert (service key, user already
auth-validated) is reliable for both new votes and vote changes.
2026-05-03 11:19:09 -05:00
Will Anderson 254afd2fb2 fix(ci+chat): drop docker buildx flags + style chat code blocks
Deploy marketing to Cloud Run / deploy (push) Failing after 2m27s
CI: gitea runner ships docker without the buildx plugin, so
`docker buildx build --platform linux/amd64 --load` exits 125
("unknown flag: --platform") in both the full build (build-stage.sh)
and the asset-only fast path (deploy.yaml). Runner host is already
linux/amd64, so explicit --platform is redundant. Switch both call
sites to plain `docker build`. This unblocks the pipeline — every
run since the workflow was added (~26 runs) has failed at this
exact step.

Chat: the live chat bubble renders marked.parse() output via
innerHTML, but .demo-msg-ai .demo-msg-bubble only had CSS rules
for p/ul/ol/li/strong. Fenced code blocks rendered as <pre><code>
with no styling — they appeared as wrapped plaintext, hard to
distinguish from prose. Add rules for code (inline and block),
pre, blockquote, em, h1-h4, and a, mirroring the share-card
styling (which always had them) so chat code blocks finally get
the monospace + tinted-background treatment users expect.
2026-05-03 10:15:24 -05:00
Will Anderson 8cd07b9107 fix: copilot svg - strip xml declaration so runtime detects image/svg+xml
Deploy marketing to Cloud Run / deploy (push) Failing after 11s
2026-05-03 04:03:02 -05:00
Will Anderson 22d2dab19b fix: copilot logo - use svg (already deployed) instead of missing png
Deploy marketing to Cloud Run / deploy (push) Failing after 37s
2026-05-03 03:57:02 -05:00
Will Anderson d9eeed9767 fix: replace broken copilot logo with correct PNG
Deploy marketing to Cloud Run / deploy (push) Failing after 18s
2026-05-03 03:52:54 -05:00
Will Anderson 8704f7cdfc update el runtime to v1.2.0, refactor checkout script to external asset
Deploy marketing to Cloud Run / deploy (push) Has been cancelled
2026-05-03 01:13:25 -05:00
Will Anderson 88ee3d53de fix: free tier shows payment form only after account created (auth-badge reveal)
Deploy marketing to Cloud Run / deploy (push) Has been cancelled
2026-05-02 18:48:38 -05:00
Will Anderson 6702fb7f9a fix: free tier checkout - full Stripe form with SetupIntent (card saved, no charge)
Deploy marketing to Cloud Run / deploy (push) Has been cancelled
2026-05-02 18:38:51 -05:00
Will Anderson d2908099c4 fix: add sign-up form to free tier checkout - was showing text with no form
Deploy marketing to Cloud Run / deploy (push) Has been cancelled
2026-05-02 18:23:28 -05:00
Will Anderson 2e73fce3b3 assets: use official Copilot PNG in comparison table
Deploy marketing to Cloud Run / deploy (push) Has been cancelled
2026-05-02 16:43:11 -05:00
Will Anderson 15105b665a assets: add Copilot icon to social assets
Deploy marketing to Cloud Run / deploy (push) Has been cancelled
2026-05-02 16:42:34 -05:00
Will Anderson 609a123012 assets: serve social icons locally, remove simpleicons CDN dependency
LinkedIn, X, Facebook, WhatsApp, TikTok, Snapchat icons now served from
/assets/social/ instead of cdn.simpleicons.org. LinkedIn uses the official
brand PNG from Downloads; remaining icons are scraped SVGs.
2026-05-02 15:11:38 -05:00
Will Anderson 46f93fd6eb security: replace denylist sanitize_share_html with allowlist el_html_sanitize
Deploy marketing to Cloud Run / deploy (push) Failing after 5s
A real attacker probed /api/share earlier today with <script>alert(1),
<iframe src=evil>, <img onerror>, <a href="javascript:...">, and a
<form action="/steal"> payload. Nothing executed because the chat
bubble at /share/<id> renders the served HTML inside marked.js's
already-escaped output, but the prior denylist sanitizer was fragile:

  - It comment-wrapped dangerous tags ("<!--script>...-->") which a
    literal "-->" inside an attacker-supplied attribute value can close
    early, re-exposing the original payload.
  - It renamed on*= attributes to data-x-on*= which left attack
    indicators visible in the served HTML.
  - It was a denylist; every new attack vector required a code change.
  - It didn't validate <a href> URL schemes properly.

The replacement is a runtime-level state-machine allowlist parser
(foundation/el af480f6: el_html_sanitize). The product just specifies
the JSON allowlist of allowed tags + attributes; the runtime drops
everything else, validates href/src URL schemes (http/https/mailto/
fragment/relative only), and drops whole subtrees of script/style/
iframe/object/embed/form regardless of the allowlist.

Phase 4 of bl-dc55ae07: deletes sanitize_share_html (main.el) and
gal_sanitize_html (gallery.el); replaces 3 call sites with
el_html_sanitize(html, allowlist). Defines default_share_allowlist
in main.el and the identical gallery_share_allowlist in gallery.el
(separate bindings to avoid a forward-reference at build-concat
order — gallery is concatenated before main).

Phase 5: migrations/20260502185500_backfill_resanitize_share_cards.sql
nulls answer_html for any share_cards row older than 1 hour. Applied
via the Supabase Management API; 0 rows in scope (the column was
added today and existing rows pre-date its first write).

Also fixes an orthogonal duplicate-symbol bug: unix_timestamp() was
defined in both dist/web_stubs.c and the runtime (the latter is a
recent runtime addition picked up by the runtime sync). Removed the
stub.

Backlog: bl-dc55ae07
2026-05-02 12:56:33 -05:00
Will Anderson ff054b9980 fix(gallery): render answer_html so card previews match the share page
Gallery was reading the plain answer field and HTML-escaping it,
showing literal `&lt;ol&gt;...` text where the actual share page rendered
the markdown. Now selects answer_html alongside, runs it through
the same sanitizer as the share-card render, and falls back to
escaped plaintext for legacy rows.
2026-05-02 12:38:48 -05:00
Will Anderson 42f8602457 fix: resend email send path - http_post_auth was dropping silently
The wrapper now logs the response and returns a structured ok/error
shape. Four call sites converge on a single send_email helper.
Resend deliveries verified end to end against
will.anderson@neurontechnologies.ai (delivery IDs 492fa066, 74258223,
69a3d9ab, f6d1c889).

Root cause: http_post_auth in dist/web_stubs.c only set the
Authorization: Bearer header. Resend rejects requests without
Content-Type: application/json with HTTP 422 missing_required_field
because it parses the body as form-urlencoded. The 422 response was
being captured by the El handler but not parsed, so callers logged
the error body and returned ok-200 to the client. Two endpoints also
built malformed JSON by interpolating the raw request body unquoted
into the text field.

Fix:
- Added http_post_auth_json (Bearer + Content-Type: application/json)
  alongside http_post_auth in dist/web_stubs.c. Stripe form-POST
  callers stay on http_post_auth, JSON callers (Resend now, others
  later) move to the json variant.
- New send_email(from_addr, to, subject, html, text) wrapper in
  src/main.el. JSON-escapes all user-provided fields, parses the
  Resend response into a structured ok/error envelope, and println's
  the outcome ([email] sent id=<id>) for Cloud Run log surfaces.
- Refactored four call sites onto the wrapper: /api/enterprise-inquiry,
  /api/developer-interest, /api/waitlist, /api/attest, the family
  invite branch in /api/family/invite, and both DocuSeal completion
  branches in /api/docuseal/webhook/<token>.
- Untracked dist/ source files (web_stubs.c, vessel_stubs.c,
  soul-demo.c, entrypoint.sh, engram-snapshot.json) are now committed
  - generated artifacts (main.c, binaries) stay ignored. Without this
  the next CI rebuild would regress the fix.
2026-05-02 12:37:54 -05:00
Will Anderson 79cd461b83 feat: phase 1 of runtime config store (neuron_config table, chat.model)
New Supabase table neuron_config keyed on (key, scope) with jsonb
value column. Web tier reads chat.model via /api/demo with 60s TTL
caching, passes to soul via dharma envelope payload.model. No more
revision-rollout-per-model-swap.

Admin read endpoint at /api/admin/config gated by NEURON_ADMIN_TOKEN.
Write surface and Realtime subscription land in Phase 2.

Backlog: bl-6eb51893
2026-05-02 12:24:00 -05:00
Will Anderson e121038382 fix(gallery): proper auth-gated voting with persistence, undo, and change
Replaces the broken counter-bump RPC with a per-user share_votes table
(PK share_id+user_id, RLS-enforced ownership). One vote per user per
card, change direction or undo any time. Auth required for write;
read is public. share_cards.upvotes/downvotes/score stay in sync via
recalc trigger. New endpoints: POST /api/vote (auth-gated), GET
/api/vote-state/:id (auth-aware).
2026-05-02 12:14:31 -05:00
Will Anderson ccbe243eab share: render markdown + preview-before-publish + soul-history probe
Share card now displays the AI bubble's marked-rendered HTML (after
basic tag allowlist sanitization) instead of escaped plaintext.
Markdown bold, lists, code, headers all show.

Share click in chat now opens a preview modal. Publishing to the
gallery only happens when the user explicitly clicks Publish in the
modal - removes the click-and-immediately-public surprise.
2026-05-02 12:11:12 -05:00
Will Anderson 3a18c37f7d fix: shadow-let soul_url and neuron_origin so the default actually stores
The native elc silently drops bare reassignment inside an if body
(`name = expr` compiles to an orphan expr statement with no store).
Both soul_url and neuron_origin defaulted to empty, breaking every
http_post call to the in-container soul-demo. Switch to let-shadowed
if-expressions until the compiler is fixed.

See: products/web/src/main.el `/api/soul-health` route output.
2026-05-02 11:47:23 -05:00
Will Anderson 8107c4c0e8 add /api/soul-health diagnostic route 2026-05-02 11:41:54 -05:00
Will Anderson b76c0c995a swap simpleicons silhouettes for actual brand marks on comparison table 2026-05-02 11:30:14 -05:00
Will Anderson 640813e42e migrate stage build to native elc; chat restores from localStorage on return
Build pipeline
- build-stage.sh replaces the old in-Dockerfile bootstrap.py path. Host
  pre-compiles src/*.el into dist/main.c via the canonical native elc at
  foundation/el/dist/platform/elc and applies the stub-decl sed before
  docker buildx runs.
- Dockerfile.stage drops bootstrap.py + python3 from the builder stage
  and just runs cc on the host-supplied dist/main.c.
- Pre-rendered HTML shells under /srv/landing/ are now chowned to the
  landing user so the El page-builder's fs_write at startup can rewrite
  them — without that, post-COPY edits never reach the served HTML and
  the served page stays as the stale build-time fallback.

Chat restore
- session.verified + session.verifiedAt persist through localStorage so
  a return visit within 24h skips the Turnstile gate and lands directly
  in the restored conversation.
- restoreOrGreet() is the single source of truth for what shows up in
  the message pane after the gate clears: replays prior messages with
  skipSave, else drops the canned hello once and remembers it.
- applyVerifiedDom() hides the gate / reveals the chat row, called both
  from the verified-on-load path (DOMContentLoaded if loading, else
  immediate) and from the Turnstile callback.
- neuronDemoReset clears verified + verifiedAt so the gate returns next
  open.

Extracted JS assets (src/assets/js/*.js + manifest.json) and the
extract-js.py helper land here too — they match what the new build-stage
flow produces and removes the inline <script> blobs from the served HTML.
2026-05-02 11:15:09 -05:00
Will Anderson cae5028130 auto-signup webhook + chat polish (10q, bold-white, last-q awareness)
Webhook handler now reacts to payment_intent.succeeded and
setup_intent.succeeded (in addition to the legacy
checkout.session.completed) and auto-provisions a Supabase account
for every successful purchase via /auth/v1/invite. The buyer gets a
magic-link email; landing on /account?welcome=1 with that link signs
them in and the plan card renders.

Stripe Customer is updated with metadata.supabase_user_id so the
cross-reference is durable. New stub: supabase_admin_invite() in
web_stubs.c (POST {project_url}/auth/v1/invite with service-key
bearer auth + apikey header).

Chat widget:
  * MAX 5 → 10 questions per session
  * countdown is now bold white at all times; the red threshold
    at <=5 was loud and made the chat feel rationed
  * greeted-once flag in localStorage so reopening the panel
    doesn't replay the canned hello (only first open greets)
  * questions_remaining + is_last_question travel to the soul on
    each turn so it can close in voice on the final turn instead
    of leaving the visitor at a hard cap

Soul side of the last-turn handshake is still a TODO - the wrapper
plumbs the fields through but soul-demo.el has to be updated to
read and act on them.
2026-05-02 10:10:36 -05:00
Will Anderson 40691c05c8 nav order matches page; pricing moved last; setup-intent attach fix
Page section order is now: hero → pillars → how-it-works → inference →
efficiency → comparison → enterprise → mission → local-first → safety →
environmental → marketplace → viral → pricing → footer. Pricing is the
last content block so visitors scroll the whole story before being asked
to commit. Nav (desktop + mobile + dropdown) reordered to mirror the
page: How it works → Enterprise → Mission ▾ → Marketplace → Pricing →
About / Gallery / Account.

Stripe attach bug: /api/link-customer was hitting
POST /v1/payment_intents/{id} for every Intent regardless of type, so
SetupIntents (seti_*) returned 404 from Stripe. Branched on the id
prefix - SetupIntents go to /v1/setup_intents/{id} and skip the
receipt_email param (PaymentIntent-only field). Caught from a live
422 in the dashboard the moment Will tested timing=later.
2026-05-02 00:52:59 -05:00
Will Anderson 1349450b14 checkout: hold-until-launch radio actually works (SetupIntent path)
The Professional plan's "Hold until product launches" radio was wired
into the markup but ignored by both /api/payment-intent and the JS
submit handler. Buyers who picked it would still get charged
immediately because the server always created a PaymentIntent and
the client always called stripe.confirmPayment.

Fix:
  * /api/payment-intent reads body.timing. When plan=professional and
    timing=later, it creates a SetupIntent (usage=off_session) instead
    of a PaymentIntent and returns {setup_mode:true, client_secret:...}.
    Founding stays unconditional (lifetime, charge now).
  * checkout JS now reads the radio (currentTiming()), passes timing
    to the server, and re-fetches a new client_secret on radio change
    so the buyer's choice is honored even if they toggle after first
    mount.
  * window._neuronMode tracks 'payment' vs 'setup'. The submit handler
    branches: stripe.confirmSetup for save-card, stripe.confirmPayment
    for charge-now. The submit button label updates to "Save my card -
    no charge today" when in setup mode so the buyer sees the
    intent before they hit submit.
  * /api/link-customer receives timing + mode so the server can
    differentiate at attach time.

A future webhook on setup_intent.succeeded will create the actual
Subscription with trial_end at launch (Q3 2026 / 2026-09-01) - that
piece is queued via metadata[hold_until]=launch on the SetupIntent.
For now, the saved payment method sits in Stripe untouched.

The point: a buyer who picks "Hold until launch" is NOT charged. The
flow has to be airtight - no surprise charges.
2026-05-02 00:45:44 -05:00
Will Anderson 04f3afea09 account: fix duplicate billing line + always show badge for founding members
Duplicate "Lifetime · Never billed again" was caused by
insertAdjacentHTML('afterend') appending another <p> on every
renderPlanCard call - and renderPlanCard runs both in init() and on
the onAuthStateChange INITIAL_SESSION event, so the line stacks.
Replaced with a dedicated #plan-billing-note-el container that
setHtml() replaces in place.

Badge: collapsed the two-branch fetch into one path. Founding
members now always see the badge; if member_number is set we render
"#N", otherwise we fall through to "Your number awaits" via the
existing n=0 endpoint behaviour.
2026-05-01 23:51:13 -05:00
Will Anderson eea9ff8ff4 account: server-side plan lookup via /api/my-plan, scrub internal comments from JS
The /account "Loading..." spinner stayed on forever because the
browser-side waitlist read went through the anon key and didn't reach
the row. Replaced it with a POST /api/my-plan: the server verifies
the user's access_token via Supabase /auth/v1/user, then reads the
waitlist row with the service key. Bypasses RLS without exposing the
service key to the browser.

Stripped implementation comments from the served JS so the browser
doesn't broadcast how internals are shaped.

Build pipeline: declared the supabase_auth_user stub for both
build-local.sh and Dockerfile.stage so the bootstrap-injected forward
declarations match what's actually linked.
2026-05-01 23:46:26 -05:00
Will Anderson 4aa48538f6 ux: chat header + greeting + brain avatar + share button + terms + badge
Restores inline-JS styles.el (the obfuscated /assets/js/*.js path
broke the chat widget) and threads four UX fixes through it:

  - Chat header: drop the all-caps "NEURON / Live Demo" subtitle, keep
    just "Neuron" in bold white at 1rem.
  - Greeting: replace the canned "Hi. I am Neuron. You get 5 questions"
    with "Hey. What is on your mind?" in all three callsites (open,
    reset, turnstile-verified). The questions counter is already in
    the header, so we don't need to repeat it.
  - Avatar: switch chat avatar from neuron-icon.png to the brain mark
    (/assets/brand/neuron-brain.png). Thinking state now shows the
    pulsing brain plus three-dot animation instead of "thinking..."
    text.
  - Share button: from a muted t3 link to a navy pill with subtle
    background, uppercase letter-spacing, and active-press feedback.

main.el: drop the FOUNDING_SOLD = 47 floor and rewrite
fetch_founding_count_stripe to count succeeded PaymentIntents (the
live Stripe Elements path) AND legacy Checkout Sessions, excluding
refunded charges.

founding_badge.el: rewrite to the Option C "Tag" design picked from
/tmp/founding-badge-preview.html - tall card, navy top stroke, brain
mark prominent, "Neuron, LLC" footer.

terms.el: kill the "Neuron Inference - our own inference layer, live
now" claim. Inference launches Q3 2026 and the wording now says so.
2026-05-01 23:40:49 -05:00
Will Anderson d6731f7834 checkout: re-add link-customer + redirect to /account, fix success/account routes
Restores the /api/link-customer endpoint that was lost in the stash. It
runs right before stripe.confirmPayment() and:
  - searches Stripe for an existing Customer by email
  - creates one if missing (URL-encodes + and @ so Gmail aliases work)
  - attaches the Customer to the live PaymentIntent
  - upserts the Supabase waitlist row with stripe_customer_id + plan

Stripe locks customer on a Charge once set, so the webhook handler is
the second-line backstop for any race where confirmPayment beats the
link call.

Also: change return_url from /marketplace/success to /account?welcome=1
so buyers land where they need to be, and switch /marketplace/success
and /account from str_eq to str_starts_with so Stripe-appended query
strings (?payment_intent=...&redirect_status=succeeded) don't 404.
2026-05-01 23:34:52 -05:00
Will Anderson 702888d3aa checkout: drop auth wall so payment form mounts on page load
The auth-first flow blocked Stripe Elements from initialising for any
visitor without an existing Supabase session. Users hit the checkout
page, saw "Sign in to continue", and could not get to a card field at
all. Restored the inline-JS path (HEAD before extraction broke it),
flipped payment-section visible by default, kept the sign-in panel
behind an explicit "Already have an account? Sign in" link.

Build pipeline: added supabase_get stub injection and -lssl/-lcrypto
linker flags (web_stubs.c uses EVP for the AES-256-GCM transport).
Without those the Docker build aborts at link time.
2026-05-01 23:26:12 -05:00
Will Anderson 00f2323c98 v1.0 - launch: full nav on gallery, chat widget auto-open, comparison logos, checkout fixes 2026-05-01 18:13:06 -05:00
Will Anderson ff1f9577db Add marketplace section, OAuth checkout, social icons, 35% efficiency claim, preorder CTAs, enterprise Q1 2027, founding member limits, paragraph spacing in demo chat, no em dashes 2026-05-01 09:20:45 -05:00
Will Anderson 9f7996effe Fix origin hardcode; seed founding count from Stripe at startup
- NEURON_ORIGIN env var drives all Stripe redirect URLs (no more localhost)
- Load founding count from persist file or live Stripe PaymentIntents search
- Write count to founding_sold.txt on startup and each webhook increment
- Regenerate index.html with real count before serving
- Startup order: Stripe config → count → HTML → serve
2026-04-29 17:55:43 -05:00
Will Anderson 41e73b8c1a Polish: tighten styles cleanup 2026-04-29 17:44:17 -05:00
Will Anderson 4769613d10 Polish: nav hamburger, pillar alignment, wordmark, hero tightening
- Nav: responsive hamburger at ≤1060px, full mobile menu with close behaviors
- Nav: all section anchors are absolute (/#...) for correct cross-page routing
- Pillars: flex column cards with margin-top:auto on taglines — all three align
- About footer: image wordmark matches main page (was plain text "Neuron")
- Hero: "Six patents" → "Patented"; sub-copy trimmed to one clean sentence
- Environmental/efficiency/inference: remove all "memory graph" mentions, cite 40% token reduction
- Environmental: savings calculator (slider, live annual savings calculation)
2026-04-29 17:31:26 -05:00
Will Anderson 3c462b3bf8 Nav, safety, pillars updates
- Nav: all section links are absolute (/#section) - fixes about page back-nav
- Nav: add Environment link
- Safety: "I built" not "we built"; crisis hotline announcement; crisis line coming
- Pillars: rename Learns -> Sharpens with opaque copy that doesn't reveal mechanism
2026-04-29 17:24:55 -05:00
Will Anderson c84c66e5fc Safety contact required at onboarding; 988 as default option 2026-04-29 17:18:55 -05:00
Will Anderson e9209bccc1 Unify nav across all pages; broaden safety section
- about, terms, enterprise_terms now all import and use shared nav()
- Any future nav change propagates everywhere automatically
- Remove Docs from nav (no docs yet)
- Safety: Hard Bell is for everyone, not just children - reframe copy,
  update cards and bottom callout to reflect universal applicability
2026-04-29 17:16:49 -05:00
Will Anderson cc20f10d6d Remove Docs nav link 2026-04-29 17:15:17 -05:00