Fix about page voice, wire founding badge and dynamic counter

About: rewritten in memoir register — flowing connected sentences,
no staccato, no resignation mention, no employer mention.

Founding counter: polls /api/founding-count every 90s, updates DOM
in place with flash animation on new claims. Webhook now increments
the sold counter when a founding purchase completes.

Badge: founding_badge.el component with guilloché SVG background,
corner rosettes, member number, glow animation. Rendered on success page.

Pricing: "at cost" removed, parent onboarding callout added.
This commit is contained in:
Will Anderson
2026-04-29 16:38:40 -05:00
parent 1d8143cdbc
commit d190e08f38
4 changed files with 208 additions and 36 deletions
+137
View File
@@ -0,0 +1,137 @@
// founding_badge.el Founding Member badge component.
//
// Renders the certificate-style badge with guilloché background,
// navy colour scheme, and member number. Matches the React archive design.
// Used on the post-purchase success page and as a shareable /founding-badge route.
fn founding_badge(member_number: Int) -> String {
let num_str: String = int_to_str(member_number)
let show_number: Bool = member_number > 0
let number_html: String = if show_number {
"<div style=\"display:flex;flex-direction:column;align-items:center;gap:0;\">
<span style=\"font-size:8px;font-weight:700;letter-spacing:.20em;text-transform:uppercase;color:#0052A0;line-height:1;margin-bottom:-8px;position:relative;z-index:1;\">No.</span>
<p style=\"font-size:42px;font-weight:700;color:#0D0D14;letter-spacing:-.02em;line-height:.85;margin:0;\">" + num_str + "</p>
<p style=\"font-size:8px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:#0052A0;margin:0;\">of 1,000</p>
</div>"
} else {
"<p style=\"font-size:8px;letter-spacing:.18em;text-transform:uppercase;color:rgba(0,82,160,.45);margin:0;\">Your number awaits</p>"
}
return "
<div class=\"founding-badge\" aria-label=\"Founding Member badge\">
<!-- Guilloché background SVG -->
<svg aria-hidden=\"true\" class=\"founding-badge-bg\" viewBox=\"0 0 260 300\" preserveAspectRatio=\"none\">
<defs>
<pattern id=\"hatch\" width=\"6\" height=\"6\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate(45)\">
<line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"6\" stroke=\"rgba(0,82,160,.055)\" stroke-width=\"0.5\"/>
</pattern>
<pattern id=\"dots\" width=\"10\" height=\"10\" patternUnits=\"userSpaceOnUse\">
<circle cx=\"5\" cy=\"5\" r=\"0.6\" fill=\"rgba(0,82,160,.12)\"/>
</pattern>
</defs>
<rect width=\"260\" height=\"300\" fill=\"rgba(0,82,160,.025)\"/>
<rect width=\"260\" height=\"300\" fill=\"url(#hatch)\"/>
<rect width=\"260\" height=\"300\" fill=\"url(#dots)\"/>
<!-- Guilloché wave bands -->
<path d=\"M 0,40 C 26,34 52,46 78,40 S 130,34 156,40 S 208,46 260,40\" fill=\"none\" stroke=\"rgba(0,82,160,.18)\" stroke-width=\"0.7\"/>
<path d=\"M 0,38 C 29,44 58,32 87,38 S 145,44 174,38 S 232,32 260,38\" fill=\"none\" stroke=\"rgba(0,82,160,.12)\" stroke-width=\"0.7\"/>
<path d=\"M 0,260 C 26,254 52,266 78,260 S 130,254 156,260 S 208,266 260,260\" fill=\"none\" stroke=\"rgba(0,82,160,.18)\" stroke-width=\"0.7\"/>
<path d=\"M 0,258 C 29,264 58,252 87,258 S 145,264 174,258 S 232,252 260,258\" fill=\"none\" stroke=\"rgba(0,82,160,.12)\" stroke-width=\"0.7\"/>
<!-- Double-line ornate border -->
<rect x=\"6\" y=\"6\" width=\"248\" height=\"288\" fill=\"none\" stroke=\"rgba(0,82,160,.30)\" stroke-width=\"0.75\"/>
<rect x=\"10\" y=\"10\" width=\"240\" height=\"280\" fill=\"none\" stroke=\"rgba(0,82,160,.15)\" stroke-width=\"0.5\" stroke-dasharray=\"4 3\"/>
<!-- Corner rosettes -->
<g><circle cx=\"6\" cy=\"6\" r=\"14\" fill=\"none\" stroke=\"rgba(0,82,160,.22)\" stroke-width=\"0.6\"/><circle cx=\"6\" cy=\"6\" r=\"9\" fill=\"none\" stroke=\"rgba(0,82,160,.22)\" stroke-width=\"0.6\"/><circle cx=\"6\" cy=\"6\" r=\"5\" fill=\"none\" stroke=\"rgba(0,82,160,.22)\" stroke-width=\"0.6\"/><circle cx=\"6\" cy=\"6\" r=\"1.7\" fill=\"rgba(0,82,160,.25)\"/></g>
<g><circle cx=\"254\" cy=\"6\" r=\"14\" fill=\"none\" stroke=\"rgba(0,82,160,.22)\" stroke-width=\"0.6\"/><circle cx=\"254\" cy=\"6\" r=\"9\" fill=\"none\" stroke=\"rgba(0,82,160,.22)\" stroke-width=\"0.6\"/><circle cx=\"254\" cy=\"6\" r=\"5\" fill=\"none\" stroke=\"rgba(0,82,160,.22)\" stroke-width=\"0.6\"/><circle cx=\"254\" cy=\"6\" r=\"1.7\" fill=\"rgba(0,82,160,.25)\"/></g>
<g><circle cx=\"6\" cy=\"294\" r=\"14\" fill=\"none\" stroke=\"rgba(0,82,160,.22)\" stroke-width=\"0.6\"/><circle cx=\"6\" cy=\"294\" r=\"9\" fill=\"none\" stroke=\"rgba(0,82,160,.22)\" stroke-width=\"0.6\"/><circle cx=\"6\" cy=\"294\" r=\"5\" fill=\"none\" stroke=\"rgba(0,82,160,.22)\" stroke-width=\"0.6\"/><circle cx=\"6\" cy=\"294\" r=\"1.7\" fill=\"rgba(0,82,160,.25)\"/></g>
<g><circle cx=\"254\" cy=\"294\" r=\"14\" fill=\"none\" stroke=\"rgba(0,82,160,.22)\" stroke-width=\"0.6\"/><circle cx=\"254\" cy=\"294\" r=\"9\" fill=\"none\" stroke=\"rgba(0,82,160,.22)\" stroke-width=\"0.6\"/><circle cx=\"254\" cy=\"294\" r=\"5\" fill=\"none\" stroke=\"rgba(0,82,160,.22)\" stroke-width=\"0.6\"/><circle cx=\"254\" cy=\"294\" r=\"1.7\" fill=\"rgba(0,82,160,.25)\"/></g>
<!-- Micro text bands -->
<text x=\"130\" y=\"22\" text-anchor=\"middle\" font-size=\"4.5\" font-family=\"system-ui,sans-serif\" letter-spacing=\"3\" fill=\"rgba(0,82,160,.28)\" text-length=\"220\" length-adjust=\"spacing\">NEURON TECHNOLOGIES · FOUNDING MEMBER · NEURON TECHNOLOGIES · FOUNDING MEMBER</text>
<text x=\"130\" y=\"286\" text-anchor=\"middle\" font-size=\"4.5\" font-family=\"system-ui,sans-serif\" letter-spacing=\"3\" fill=\"rgba(0,82,160,.28)\" text-length=\"220\" length-adjust=\"spacing\">NEURON TECHNOLOGIES · FOUNDING MEMBER · NEURON TECHNOLOGIES · FOUNDING MEMBER</text>
</svg>
<!-- Badge content -->
<div class=\"founding-badge-inner\">
<p class=\"founding-badge-label\">Founding Member</p>
<div class=\"founding-badge-rule\"></div>
<div class=\"founding-badge-icon\">
<img src=\"/assets/neuron-icon.png\" alt=\"Neuron\" width=\"34\" height=\"34\">
</div>
" + number_html + "
</div>
</div>
"
}
fn founding_badge_css() -> String {
return "
<style>
@keyframes fb-glow-pulse {
0%,100% { box-shadow: 0 0 0 3px rgba(0,82,160,.08), 0 0 18px rgba(0,82,160,.22), 0 0 36px rgba(0,82,160,.10); }
50% { box-shadow: 0 0 0 4px rgba(0,82,160,.12), 0 0 28px rgba(0,82,160,.32), 0 0 56px rgba(0,82,160,.16); }
}
@keyframes fb-icon-float {
0%,100% { transform: translateY(0); }
50% { transform: translateY(-2px); }
}
.founding-badge {
position: relative;
display: inline-flex;
flex-direction: column;
align-items: center;
width: 260px;
background: #FAFAF8;
border: 1px solid rgba(0,120,212,.5);
animation: fb-glow-pulse 3s ease-in-out infinite;
overflow: hidden;
}
.founding-badge-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.founding-badge-inner {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
padding: 32px 28px 28px;
width: 100%;
text-align: center;
box-sizing: border-box;
}
.founding-badge-label {
font-family: var(--body,system-ui,sans-serif);
font-size: 8.5px;
font-weight: 600;
letter-spacing: .30em;
text-transform: uppercase;
color: #0052A0;
line-height: 1;
margin: 0;
}
.founding-badge-rule {
width: 48px;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(0,82,160,.45), transparent);
}
.founding-badge-icon {
width: 60px;
height: 60px;
border-radius: 50%;
background: rgba(255,255,255,.85);
border: 1px solid rgba(0,82,160,.2);
box-shadow: 0 0 0 4px rgba(0,82,160,.06);
display: flex;
align-items: center;
justify-content: center;
}
.founding-badge-icon img {
display: block;
animation: fb-icon-float 4s ease-in-out infinite;
}
</style>
"
}