Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c30e5903a4 | |||
| c526e76d3b | |||
| 6a7b8382ea | |||
| d2ae0b4b60 | |||
| 5c8987ef59 | |||
| bfcb325352 | |||
| 57e9cafc95 | |||
| 632d95000c | |||
| 77807d30af | |||
| 8f91a80be7 | |||
| d7bb92c37f | |||
| 4ca793ee2c | |||
| 4123f6d5f1 | |||
| 69f348d48b | |||
| 3d635505bc | |||
| 675c467a74 | |||
| 708dfd06cb | |||
| b6aecd7d89 | |||
| bb98f76179 | |||
| 0fdbba82e0 | |||
| 9e0451be41 |
+53
-52
@@ -8,41 +8,41 @@
|
|||||||
from nav import { nav }
|
from nav import { nav }
|
||||||
|
|
||||||
fn about_page() -> String {
|
fn about_page() -> String {
|
||||||
return {nav()}
|
return nav() + "
|
||||||
|
|
||||||
<main id="about" style="padding: clamp(7rem, 18vh, 11rem) 2.5rem clamp(5rem, 12vh, 8rem);">
|
<main id=\"about\" style=\"padding: clamp(7rem, 18vh, 11rem) 2.5rem clamp(5rem, 12vh, 8rem);\">
|
||||||
<div style="max-width: 700px; margin: 0 auto;">
|
<div style=\"max-width: 700px; margin: 0 auto;\">
|
||||||
|
|
||||||
<p class="label animate-up-1" style="margin-bottom: 2rem;">About</p>
|
<p class=\"label animate-up-1\" style=\"margin-bottom: 2rem;\">About</p>
|
||||||
<h1 class="display-lg animate-up-2" style="margin-bottom: 2.5rem; max-width: 22rem;">
|
<h1 class=\"display-lg animate-up-2\" style=\"margin-bottom: 2.5rem; max-width: 22rem;\">
|
||||||
Hi. I'm Will.
|
Hi. I'm Will.
|
||||||
</h1>
|
</h1>
|
||||||
<div class="navy-line-left animate-up-3" style="width: 4rem; margin-bottom: 3rem;"></div>
|
<div class=\"navy-line-left animate-up-3\" style=\"width: 4rem; margin-bottom: 3rem;\"></div>
|
||||||
|
|
||||||
<!-- Photo + opening -->
|
<!-- Photo + opening -->
|
||||||
<div class="reveal" style="display: flex; align-items: flex-start; gap: 2.5rem; margin-bottom: 3rem; flex-wrap: wrap;">
|
<div class=\"reveal\" style=\"display: flex; align-items: flex-start; gap: 2.5rem; margin-bottom: 3rem; flex-wrap: wrap;\">
|
||||||
<img src="/assets/will.png" alt="Will Anderson" style="width: 160px; height: 160px; border-radius: 50%; object-fit: cover; flex-shrink: 0;">
|
<img src=\"/assets/will.png\" alt=\"Will Anderson\" style=\"width: 160px; height: 160px; border-radius: 50%; object-fit: cover; flex-shrink: 0;\">
|
||||||
<div style="flex: 1; min-width: 260px;">
|
<div style=\"flex: 1; min-width: 260px;\">
|
||||||
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;">
|
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;\">
|
||||||
I grew up in Fort Smith, Arkansas, in the kind of instability where home is a moving target - roughly thirty addresses before I was fifteen, parents struggling with addiction, the material precarity that comes with all of that. I left home at fifteen, stayed with friends until I finished high school, found my way to college. At fourteen I'd already found software, writing C++ at the public library because it was the first thing in my life that responded to precision with correctness, and that property turned out to matter more to me than almost anything else.
|
I grew up in Fort Smith, Arkansas, in the kind of instability where home is a moving target - roughly thirty addresses before I was fifteen, parents struggling with addiction, the material precarity that comes with all of that. I left home at fifteen, stayed with friends until I finished high school, found my way to college. At fourteen I'd already found software, writing C++ at the public library because it was the first thing in my life that responded to precision with correctness, and that property turned out to matter more to me than almost anything else.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Career -->
|
<!-- Career -->
|
||||||
<div class="reveal" style="margin-bottom: 3rem;">
|
<div class=\"reveal\" style=\"margin-bottom: 3rem;\">
|
||||||
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;">
|
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;\">
|
||||||
I dropped out of college, worked, went back as an adult to finish my degree, and built my skills across nearly twenty years and every kind of organization - international consulting, early-stage startups, Fortune 5 enterprises. Logistics, retail, entertainment, hospitality, industrial automation, insurance, healthcare, financial services. I trained under Juval Löwy at IDesign and worked with him as a consultant from 2015 to 2021, which is where I learned what it actually means to practice software engineering as a discipline rather than an improvisation.
|
I dropped out of college, worked, went back as an adult to finish my degree, and built my skills across nearly twenty years and every kind of organization - international consulting, early-stage startups, Fortune 5 enterprises. Logistics, retail, entertainment, hospitality, industrial automation, insurance, healthcare, financial services. I trained under Juval Löwy at IDesign and worked with him as a consultant from 2015 to 2021, which is where I learned what it actually means to practice software engineering as a discipline rather than an improvisation.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Blockquote -->
|
<!-- Blockquote -->
|
||||||
<blockquote class="reveal" style="
|
<blockquote class=\"reveal\" style=\"
|
||||||
border-left: 3px solid var(--navy);
|
border-left: 3px solid var(--navy);
|
||||||
padding: 0.5rem 0 0.5rem 2rem;
|
padding: 0.5rem 0 0.5rem 2rem;
|
||||||
margin: 0 0 3rem;
|
margin: 0 0 3rem;
|
||||||
">
|
\">
|
||||||
<p style="
|
<p style=\"
|
||||||
font-family: var(--head);
|
font-family: var(--head);
|
||||||
font-size: clamp(1.4rem, 3vw, 2rem);
|
font-size: clamp(1.4rem, 3vw, 2rem);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -50,42 +50,42 @@ fn about_page() -> String {
|
|||||||
color: var(--t1);
|
color: var(--t1);
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
">
|
\">
|
||||||
Software shouldn't be hard. The complexity should live in the problem domain - not in the tools and processes we impose on ourselves.
|
Software shouldn't be hard. The complexity should live in the problem domain - not in the tools and processes we impose on ourselves.
|
||||||
</p>
|
</p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
|
||||||
<!-- What I saw -->
|
<!-- What I saw -->
|
||||||
<div class="reveal" style="margin-bottom: 3rem;">
|
<div class=\"reveal\" style=\"margin-bottom: 3rem;\">
|
||||||
<p class="label" style="margin-bottom: 1.25rem;">What I saw</p>
|
<p class=\"label\" style=\"margin-bottom: 1.25rem;\">What I saw</p>
|
||||||
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;">
|
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;\">
|
||||||
Across nearly twenty years I watched software get built at organizations with real stakes and real consequences, and I watched AI go from promise to product - watched the same mistake get made at each iteration: tools built to serve the organization's needs, not the person's. Engagement over relationship. Features over memory. Policies where values should be. The fundamental premise that you are a user, not a person, has been so thoroughly baked into the architecture of every major AI system that it doesn't register as a choice anymore. It's treated as the natural condition of the technology.
|
Across nearly twenty years I watched software get built at organizations with real stakes and real consequences, and I watched AI go from promise to product - watched the same mistake get made at each iteration: tools built to serve the organization's needs, not the person's. Engagement over relationship. Features over memory. Policies where values should be. The fundamental premise that you are a user, not a person, has been so thoroughly baked into the architecture of every major AI system that it doesn't register as a choice anymore. It's treated as the natural condition of the technology.
|
||||||
</p>
|
</p>
|
||||||
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9;">
|
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9;\">
|
||||||
It is not. It is a design decision. And it is the wrong one.
|
It is not. It is a design decision. And it is the wrong one.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="navy-line-center reveal" style="margin-bottom: 3rem;"></div>
|
<div class=\"navy-line-center reveal\" style=\"margin-bottom: 3rem;\"></div>
|
||||||
|
|
||||||
<!-- What I built -->
|
<!-- What I built -->
|
||||||
<div class="reveal" style="margin-bottom: 3rem;">
|
<div class=\"reveal\" style=\"margin-bottom: 3rem;\">
|
||||||
<p class="label" style="margin-bottom: 1.25rem;">What I built</p>
|
<p class=\"label\" style=\"margin-bottom: 1.25rem;\">What I built</p>
|
||||||
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;">
|
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;\">
|
||||||
Neuron is what I built in response to that. Not a startup in the traditional sense - no team, no funding, no press release - one person, nearly two years of work, and a conviction that this can be done differently. I wrote the memory architecture, I built the inference infrastructure, because the tools that existed weren't sufficient for what I was trying to build and so I built those too.
|
Neuron is what I built in response to that. Not a startup in the traditional sense - no team, no funding, no press release - one person, nearly two years of work, and a conviction that this can be done differently. I wrote the memory architecture, I built the inference infrastructure, because the tools that existed weren't sufficient for what I was trying to build and so I built those too.
|
||||||
</p>
|
</p>
|
||||||
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9;">
|
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9;\">
|
||||||
Use it long enough and you'll understand why I couldn't have gotten there on top of existing infrastructure. Some things have to be built from the ground up to be built right.
|
Use it long enough and you'll understand why I couldn't have gotten there on top of existing infrastructure. Some things have to be built from the ground up to be built right.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- What I believe -->
|
<!-- What I believe -->
|
||||||
<div class="reveal" style="margin-bottom: 3.5rem;">
|
<div class=\"reveal\" style=\"margin-bottom: 3.5rem;\">
|
||||||
<p class="label" style="margin-bottom: 1.25rem;">What I believe</p>
|
<p class=\"label\" style=\"margin-bottom: 1.25rem;\">What I believe</p>
|
||||||
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;">
|
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;\">
|
||||||
AI has genuine potential to free people to do work that actually matters to them - not to create engagement loops, not to harvest attention, but to actually serve the person sitting in front of it. That potential is almost entirely unrealized, not because the technology isn't capable, but because the incentives that shaped it were never oriented toward the person.
|
AI has genuine potential to free people to do work that actually matters to them - not to create engagement loops, not to harvest attention, but to actually serve the person sitting in front of it. That potential is almost entirely unrealized, not because the technology isn't capable, but because the incentives that shaped it were never oriented toward the person.
|
||||||
</p>
|
</p>
|
||||||
<p style="
|
<p style=\"
|
||||||
font-family: var(--head);
|
font-family: var(--head);
|
||||||
font-size: clamp(1.2rem, 2.5vw, 1.625rem);
|
font-size: clamp(1.2rem, 2.5vw, 1.625rem);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -93,22 +93,22 @@ fn about_page() -> String {
|
|||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
">
|
\">
|
||||||
Build AI that earns the trust it's given.
|
Build AI that earns the trust it's given.
|
||||||
</p>
|
</p>
|
||||||
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9;">
|
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9;\">
|
||||||
I don't know if Neuron will work at the scale I'm imagining. But I know it's worth finding out, and I know I'm not going back to the other way of building things.
|
I don't know if Neuron will work at the scale I'm imagining. But I know it's worth finding out, and I know I'm not going back to the other way of building things.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="navy-line-center reveal" style="margin-bottom: 3rem;"></div>
|
<div class=\"navy-line-center reveal\" style=\"margin-bottom: 3rem;\"></div>
|
||||||
|
|
||||||
<!-- CTA -->
|
<!-- CTA -->
|
||||||
<div class="reveal">
|
<div class=\"reveal\">
|
||||||
<p style="font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;">
|
<p style=\"font-family: var(--body); font-weight: 300; font-size: clamp(0.9rem, 1.5vw, 1.0625rem); color: var(--t2); line-height: 1.9; margin-bottom: 1.5rem;\">
|
||||||
Neuron opens to founding members on May 1st. 1,000 spots. That's how it starts.
|
Neuron opens to founding members on May 1st. 1,000 spots. That's how it starts.
|
||||||
</p>
|
</p>
|
||||||
<a href="/#pricing" class="btn-primary">
|
<a href=\"/#pricing\" class=\"btn-primary\">
|
||||||
Join as a founding member →
|
Join as a founding member →
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,34 +116,35 @@ fn about_page() -> String {
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer id="footer" aria-label="Footer">
|
<footer id=\"footer\" aria-label=\"Footer\">
|
||||||
<div class="container">
|
<div class=\"container\">
|
||||||
<div class="footer-inner">
|
<div class=\"footer-inner\">
|
||||||
|
|
||||||
<a href="/" class="footer-brand" aria-label="Neuron home" style="display:flex;flex-direction:column;align-items:center;">
|
<a href=\"/\" class=\"footer-brand\" aria-label=\"Neuron home\" style=\"display:flex;flex-direction:column;align-items:center;\">
|
||||||
<img src="/assets/brand/neuron-wordmark-on-light.png" srcset="/assets/brand/neuron-wordmark-on-light@2x.png 2x" alt="Neuron" height="24" style="display:block;margin-bottom:0.35rem;">
|
<img src=\"/assets/brand/neuron-wordmark-on-light.png\" srcset=\"/assets/brand/neuron-wordmark-on-light@2x.png 2x\" alt=\"Neuron\" height=\"24\" style=\"display:block;margin-bottom:0.35rem;\">
|
||||||
<p class="footer-brand-tagline">Built Different.</p>
|
<p class=\"footer-brand-tagline\">Built Different.</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="footer-center">
|
<div class=\"footer-center\">
|
||||||
<div class="navy-line"></div>
|
<div class=\"navy-line\"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer-right">
|
<div class=\"footer-right\">
|
||||||
<p class="footer-domain">neurontechnologies.ai</p>
|
<p class=\"footer-domain\">neurontechnologies.ai</p>
|
||||||
<nav class="footer-nav" aria-label="Footer navigation">
|
<nav class=\"footer-nav\" aria-label=\"Footer navigation\">
|
||||||
<a href="/legal/terms">Terms</a>
|
<a href=\"/legal/terms\">Terms</a>
|
||||||
<a href="/legal/enterprise-terms">Enterprise Agreement</a>
|
<a href=\"/legal/enterprise-terms\">Enterprise Agreement</a>
|
||||||
<a href="mailto:legal@neurontechnologies.ai">Contact</a>
|
<a href=\"mailto:legal@neurontechnologies.ai\">Contact</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer-bottom">
|
<div class=\"footer-bottom\">
|
||||||
<p class="footer-copy">© 2026 Neuron, LLC. All rights reserved.</p>
|
<p class=\"footer-copy\">© 2026 Neuron, LLC. All rights reserved.</p>
|
||||||
<p class="footer-tagline-bottom">Your memory. Your AI.</p>
|
<p class=\"footer-tagline-bottom\">Your memory. Your AI.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
"
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -5,7 +5,7 @@ from founding_badge import { founding_badge, founding_badge_css }
|
|||||||
|
|
||||||
extern fn el_html_doc(lang: String, head: String, body: String) -> String
|
extern fn el_html_doc(lang: String, head: String, body: String) -> String
|
||||||
extern fn el_meta_charset(charset: String) -> String
|
extern fn el_meta_charset(charset: String) -> String
|
||||||
extern fn el_meta(attrs: String) -> String
|
extern fn el_meta(name: String, content: String) -> String
|
||||||
extern fn el_title(text: String) -> String
|
extern fn el_title(text: String) -> String
|
||||||
extern fn el_link_stylesheet(href: String) -> String
|
extern fn el_link_stylesheet(href: String) -> String
|
||||||
extern fn el_script_src(src: String, defer_load: Bool) -> String
|
extern fn el_script_src(src: String, defer_load: Bool) -> String
|
||||||
@@ -13,7 +13,7 @@ extern fn el_script_inline(code: String) -> String
|
|||||||
extern fn el_nav(attrs: String, children: String) -> String
|
extern fn el_nav(attrs: String, children: String) -> String
|
||||||
extern fn el_div(attrs: String, children: String) -> String
|
extern fn el_div(attrs: String, children: String) -> String
|
||||||
extern fn el_a(href: String, attrs: String, children: String) -> String
|
extern fn el_a(href: String, attrs: String, children: String) -> String
|
||||||
extern fn el_img(attrs: String) -> String
|
extern fn el_img(src: String, alt: String, attrs: String) -> String
|
||||||
extern fn el_p(attrs: String, children: String) -> String
|
extern fn el_p(attrs: String, children: String) -> String
|
||||||
extern fn el_h1(attrs: String, text: String) -> String
|
extern fn el_h1(attrs: String, text: String) -> String
|
||||||
extern fn el_button(attrs: String, label: String) -> String
|
extern fn el_button(attrs: String, label: String) -> String
|
||||||
@@ -520,7 +520,7 @@ fn account_css() -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn account_nav() -> String {
|
fn account_nav() -> String {
|
||||||
let logo_img: String = el_img("src=\"/assets/brand/neuron-wordmark-on-light.png\" srcset=\"/assets/brand/neuron-wordmark-on-light@2x.png 2x\" alt=\"Neuron\" height=\"28\"")
|
let logo_img: String = el_img("/assets/brand/neuron-wordmark-on-light.png", "Neuron", "srcset=\"/assets/brand/neuron-wordmark-on-light@2x.png 2x\" height=\"28\"")
|
||||||
el_nav(
|
el_nav(
|
||||||
"id=\"nav\"",
|
"id=\"nav\"",
|
||||||
el_div(
|
el_div(
|
||||||
@@ -818,7 +818,7 @@ fn account_devices_card() -> String {
|
|||||||
el_div("class=\"device-icon\"", account_signin_svg_device()) +
|
el_div("class=\"device-icon\"", account_signin_svg_device()) +
|
||||||
el_div(
|
el_div(
|
||||||
"",
|
"",
|
||||||
el_p("class=\"devices-count\"", "2 devices included with your plan") +
|
el_p("class=\"devices-count\" id=\"devices-count-el\"", "") +
|
||||||
el_p("class=\"devices-sub\"", "Currently: Setup at launch")
|
el_p("class=\"devices-sub\"", "Currently: Setup at launch")
|
||||||
)
|
)
|
||||||
) +
|
) +
|
||||||
@@ -965,9 +965,9 @@ fn account_dashboard_section() -> String {
|
|||||||
fn account_page(supabase_url: String, supabase_anon_key: String) -> String {
|
fn account_page(supabase_url: String, supabase_anon_key: String) -> String {
|
||||||
let head: String =
|
let head: String =
|
||||||
el_meta_charset("UTF-8") +
|
el_meta_charset("UTF-8") +
|
||||||
el_meta("name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"") +
|
el_meta("viewport", "width=device-width, initial-scale=1.0") +
|
||||||
el_title("My Account - Neuron") +
|
el_title("My Account - Neuron") +
|
||||||
el_meta("name=\"description\" content=\"Manage your Neuron account, view your plan, and access your founding member details.\"") +
|
el_meta("description", "Manage your Neuron account, view your plan, and access your founding member details.") +
|
||||||
"<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/assets/favicon-16.png\">" +
|
"<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/assets/favicon-16.png\">" +
|
||||||
"<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/assets/favicon-32.png\">" +
|
"<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/assets/favicon-32.png\">" +
|
||||||
"<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">" +
|
"<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">" +
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ fn main() -> Void {
|
|||||||
'use strict';
|
'use strict';
|
||||||
var cfg = window.NEURON_CFG || {};
|
var cfg = window.NEURON_CFG || {};
|
||||||
var sb = supabase.createClient(cfg.supabase_url, cfg.supabase_anon_key, {
|
var sb = supabase.createClient(cfg.supabase_url, cfg.supabase_anon_key, {
|
||||||
auth: { flowType: 'pkce' }
|
auth: { flowType: 'implicit' }
|
||||||
});
|
});
|
||||||
|
|
||||||
window.sendMagicLink = async function() {
|
window.sendMagicLink = async function() {
|
||||||
@@ -25,7 +25,10 @@ fn main() -> Void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (btn) { btn.disabled = true; btn.textContent = 'Sending...'; }
|
if (btn) { btn.disabled = true; btn.textContent = 'Sending...'; }
|
||||||
var result = await sb.auth.signInWithOtp({ email: email });
|
var result = await sb.auth.signInWithOtp({
|
||||||
|
email: email,
|
||||||
|
options: { emailRedirectTo: window.location.origin + '/account' }
|
||||||
|
});
|
||||||
if (btn) { btn.disabled = false; btn.textContent = 'Continue with email'; }
|
if (btn) { btn.disabled = false; btn.textContent = 'Continue with email'; }
|
||||||
msgEl.style.display = 'block';
|
msgEl.style.display = 'block';
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
|
|||||||
@@ -103,6 +103,13 @@ fn main() -> Void {
|
|||||||
}
|
}
|
||||||
setHtml('plan-billing-note-el', billingNote);
|
setHtml('plan-billing-note-el', billingNote);
|
||||||
|
|
||||||
|
var devicesEl = document.getElementById('devices-count-el');
|
||||||
|
if (devicesEl) {
|
||||||
|
var deviceText = '2 devices included with your plan';
|
||||||
|
if (plan === 'free') { deviceText = '1 device included with your plan'; }
|
||||||
|
devicesEl.textContent = deviceText;
|
||||||
|
}
|
||||||
|
|
||||||
var meta = '';
|
var meta = '';
|
||||||
if (createdAt) {
|
if (createdAt) {
|
||||||
var d = new Date(createdAt);
|
var d = new Date(createdAt);
|
||||||
|
|||||||
+23
-17
@@ -686,7 +686,7 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
|
|||||||
if !str_eq(pi_email, "") {
|
if !str_eq(pi_email, "") {
|
||||||
let pi_email_enc: String = str_replace(str_replace(pi_email, "@", "%40"), "+", "%2B")
|
let pi_email_enc: String = str_replace(str_replace(pi_email, "@", "%40"), "+", "%2B")
|
||||||
let pi_search_url: String = "https://api.stripe.com/v1/customers/search?query=email%3A%22" + pi_email_enc + "%22&limit=1"
|
let pi_search_url: String = "https://api.stripe.com/v1/customers/search?query=email%3A%22" + pi_email_enc + "%22&limit=1"
|
||||||
let pi_search: String = http_get_auth(pi_search_url, auth_header)
|
let pi_search: String = http_get_auth(pi_search_url, stripe_key)
|
||||||
let pi_cus_id = json_get_string(pi_search, "id")
|
let pi_cus_id = json_get_string(pi_search, "id")
|
||||||
if str_eq(pi_cus_id, "") {
|
if str_eq(pi_cus_id, "") {
|
||||||
let pi_name_enc: String = str_replace(pi_name, " ", "%20")
|
let pi_name_enc: String = str_replace(pi_name, " ", "%20")
|
||||||
@@ -699,21 +699,24 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free tier: $0 PaymentIntent for age verification (18+ requirement).
|
// Free tier: SetupIntent for age verification (18+ requirement).
|
||||||
// Verifies card is valid. No charge, no capture.
|
// Verifies card is valid and saves it. No charge, no capture.
|
||||||
// Note: setup_future_usage cannot be used with amount=0.
|
// $0 PaymentIntents are rejected by Stripe; SetupIntent is the correct tool.
|
||||||
if str_eq(plan, "free") {
|
if str_eq(plan, "free") {
|
||||||
let free_pi_body: String = "amount=0"
|
let si_body: String = "automatic_payment_methods[enabled]=true"
|
||||||
+ "¤cy=usd"
|
+ "&usage=off_session"
|
||||||
+ "&payment_method_types[]=card"
|
|
||||||
+ "&metadata[plan]=free"
|
+ "&metadata[plan]=free"
|
||||||
+ "&metadata[purpose]=age_verification"
|
+ "&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 si_body = if !str_eq(pi_cus_id, "") { si_body + "&customer=" + pi_cus_id } else { si_body }
|
||||||
let free_pi_resp: String = http_post_form_auth(
|
let si_resp: String = http_post_form_auth(
|
||||||
"https://api.stripe.com/v1/payment_intents",
|
"https://api.stripe.com/v1/setup_intents",
|
||||||
free_pi_body,
|
si_body,
|
||||||
auth_header)
|
auth_header)
|
||||||
return free_pi_resp
|
if str_starts_with(si_resp, "{") {
|
||||||
|
let inner: String = str_slice(si_resp, 1, str_len(si_resp))
|
||||||
|
return "{\"setup_mode\":true,\"plan\":\"free\"," + inner
|
||||||
|
}
|
||||||
|
return si_resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup-mode path: save payment method, do not charge. Only valid
|
// Setup-mode path: save payment method, do not charge. Only valid
|
||||||
@@ -784,7 +787,7 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
|
|||||||
|
|
||||||
// 1. Search existing customers by email
|
// 1. Search existing customers by email
|
||||||
let lc_search_url: String = "https://api.stripe.com/v1/customers/search?query=email%3A%22" + lc_email_enc + "%22&limit=1"
|
let lc_search_url: String = "https://api.stripe.com/v1/customers/search?query=email%3A%22" + lc_email_enc + "%22&limit=1"
|
||||||
let lc_search: String = http_get_auth(lc_search_url, lc_auth)
|
let lc_search: String = http_get_auth(lc_search_url, stripe_key)
|
||||||
let lc_cus_id: String = json_get_string(lc_search, "id")
|
let lc_cus_id: String = json_get_string(lc_search, "id")
|
||||||
|
|
||||||
// 2. If none, create one. We always include supabase_user_id so the
|
// 2. If none, create one. We always include supabase_user_id so the
|
||||||
@@ -1116,13 +1119,16 @@ fn handle_request_inner(method: String, path: String, headers: Map, body: String
|
|||||||
}
|
}
|
||||||
let attest_name: String = json_get(body, "name")
|
let attest_name: String = json_get(body, "name")
|
||||||
let attest_email: String = json_get(body, "email")
|
let attest_email: String = json_get(body, "email")
|
||||||
let attest_plan: String = json_get(body, "plan")
|
|
||||||
let attest_ts: String = json_get(body, "timestamp")
|
let attest_ts: String = json_get(body, "timestamp")
|
||||||
let attest_text: String = json_get(body, "attestation")
|
let attest_text: String = json_get(body, "attestation")
|
||||||
let attest_ua: String = json_get(body, "user_agent")
|
let attest_ua: String = json_get(body, "user_agent")
|
||||||
if str_eq(attest_email, "") {
|
if str_eq(attest_email, "") {
|
||||||
return "{\"error\":\"email required\"}"
|
return "{\"error\":\"email required\"}"
|
||||||
}
|
}
|
||||||
|
// Founding membership now requires $199 Stripe payment — the attestation
|
||||||
|
// form is a waitlist-only path. Server enforces this regardless of what
|
||||||
|
// the client submits as plan to prevent bypassing payment.
|
||||||
|
let attest_plan: String = "waitlist"
|
||||||
let n_safe: String = str_replace(str_replace(attest_name, "\\", "\\\\"), "\"", "\\\"")
|
let n_safe: String = str_replace(str_replace(attest_name, "\\", "\\\\"), "\"", "\\\"")
|
||||||
let e_safe: String = str_replace(str_replace(attest_email, "\\", "\\\\"), "\"", "\\\"")
|
let e_safe: String = str_replace(str_replace(attest_email, "\\", "\\\\"), "\"", "\\\"")
|
||||||
let t_safe: String = str_replace(str_replace(attest_text, "\\", "\\\\"), "\"", "\\\"")
|
let t_safe: String = str_replace(str_replace(attest_text, "\\", "\\\\"), "\"", "\\\"")
|
||||||
@@ -2311,7 +2317,7 @@ fn sec_headers_json() -> String {
|
|||||||
+ "\"X-Frame-Options\":\"SAMEORIGIN\","
|
+ "\"X-Frame-Options\":\"SAMEORIGIN\","
|
||||||
+ "\"Referrer-Policy\":\"strict-origin-when-cross-origin\","
|
+ "\"Referrer-Policy\":\"strict-origin-when-cross-origin\","
|
||||||
+ "\"Permissions-Policy\":\"geolocation=(), microphone=(), camera=()\","
|
+ "\"Permissions-Policy\":\"geolocation=(), microphone=(), camera=()\","
|
||||||
+ "\"Content-Security-Policy\":\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com https://cdn.jsdelivr.net https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline'; frame-src https://challenges.cloudflare.com; connect-src 'self' https://api.stripe.com https://*.supabase.co; img-src 'self' data: https:; font-src 'self' data:\"}"
|
+ "\"Content-Security-Policy\":\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com https://cdn.jsdelivr.net https://googleads.g.doubleclick.net https://js.stripe.com https://static.cloudflareinsights.com https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; frame-src https://challenges.cloudflare.com https://js.stripe.com; connect-src 'self' https://analytics.google.com https://api.stripe.com https://*.supabase.co https://www.google.com https://www.googletagmanager.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Headers for compiled JS assets. Explicitly sets Content-Type so the browser
|
// Headers for compiled JS assets. Explicitly sets Content-Type so the browser
|
||||||
@@ -2327,7 +2333,7 @@ fn js_headers_json() -> String {
|
|||||||
+ "\"X-Frame-Options\":\"SAMEORIGIN\","
|
+ "\"X-Frame-Options\":\"SAMEORIGIN\","
|
||||||
+ "\"Referrer-Policy\":\"strict-origin-when-cross-origin\","
|
+ "\"Referrer-Policy\":\"strict-origin-when-cross-origin\","
|
||||||
+ "\"Permissions-Policy\":\"geolocation=(), microphone=(), camera=()\","
|
+ "\"Permissions-Policy\":\"geolocation=(), microphone=(), camera=()\","
|
||||||
+ "\"Content-Security-Policy\":\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com https://cdn.jsdelivr.net https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline'; frame-src https://challenges.cloudflare.com; connect-src 'self' https://api.stripe.com https://*.supabase.co; img-src 'self' data: https:; font-src 'self' data:\"}"
|
+ "\"Content-Security-Policy\":\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com https://cdn.jsdelivr.net https://googleads.g.doubleclick.net https://js.stripe.com https://static.cloudflareinsights.com https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; frame-src https://challenges.cloudflare.com https://js.stripe.com; connect-src 'self' https://analytics.google.com https://api.stripe.com https://*.supabase.co https://www.google.com https://www.googletagmanager.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Headers for static assets under /assets/ and /brand/.
|
// Headers for static assets under /assets/ and /brand/.
|
||||||
@@ -2343,7 +2349,7 @@ fn static_asset_headers_json() -> String {
|
|||||||
+ "\"X-Frame-Options\":\"SAMEORIGIN\","
|
+ "\"X-Frame-Options\":\"SAMEORIGIN\","
|
||||||
+ "\"Referrer-Policy\":\"strict-origin-when-cross-origin\","
|
+ "\"Referrer-Policy\":\"strict-origin-when-cross-origin\","
|
||||||
+ "\"Permissions-Policy\":\"geolocation=(), microphone=(), camera=()\","
|
+ "\"Permissions-Policy\":\"geolocation=(), microphone=(), camera=()\","
|
||||||
+ "\"Content-Security-Policy\":\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com https://cdn.jsdelivr.net https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline'; frame-src https://challenges.cloudflare.com; connect-src 'self' https://api.stripe.com https://*.supabase.co; img-src 'self' data: https:; font-src 'self' data:\"}"
|
+ "\"Content-Security-Policy\":\"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com https://cdn.jsdelivr.net https://googleads.g.doubleclick.net https://js.stripe.com https://static.cloudflareinsights.com https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; frame-src https://challenges.cloudflare.com https://js.stripe.com; connect-src 'self' https://analytics.google.com https://api.stripe.com https://*.supabase.co https://www.google.com https://www.googletagmanager.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com\"}"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_request(method: String, path: String, headers: Map, body: String) -> String {
|
fn handle_request(method: String, path: String, headers: Map, body: String) -> String {
|
||||||
|
|||||||
Reference in New Issue
Block a user