Merge pull request 'promote: stage → main' (#5) from stage into main

This commit is contained in:
2026-05-05 11:07:25 +00:00
44 changed files with 1730 additions and 1051 deletions
+10 -1
View File
@@ -31,6 +31,15 @@ jobs:
id-token: write # needed for the OIDC token used by WIF
steps:
- name: Enforce stage-only source
# main only accepts merges from stage. Direct pushes from other branches
# are blocked by Gitea branch protection (enable_push=false for non-admins).
# workflow_dispatch is exempt to allow manual prod redeploy.
if: github.event_name != 'workflow_dispatch'
run: |
echo "Event: ${{ github.event_name }}, ref: ${{ github.ref }}"
echo "Source branch enforcement: OK (protected by Gitea branch rules)"
- name: Checkout neuron-web
uses: actions/checkout@v4
with:
@@ -72,7 +81,7 @@ jobs:
"https://will:${CHECKOUT_TOKEN}@git.neuralplatform.ai/neuron-technologies/el.git" \
"$DEST"
ls -la "$DEST" | head -5
echo "EL_HOME=$DEST" >> "$GITHUB_ENV"
echo "EL_HOME=$DEST/lang" >> "$GITHUB_ENV"
- name: Authenticate to GCP
id: auth
+12 -6
View File
@@ -14,6 +14,8 @@ on:
- 'Dockerfile.stage'
- 'build-stage.sh'
- '.gitea/workflows/dev.yaml'
- '.gitea/workflows/stage.yaml'
- '.gitea/workflows/deploy.yaml'
workflow_dispatch:
@@ -57,12 +59,18 @@ jobs:
- name: Configure docker auth for Artifact Registry
run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
- name: Get elc (pre-built linux/amd64)
- name: Get elc (pre-built linux/amd64 from El repo)
run: |
set -euo pipefail
curl -fL -o "$EL_HOME/dist/platform/elc" \
https://git.neuralplatform.ai/neuron-technologies/el/releases/download/v1.2.1/elc-linux-amd64
chmod +x "$EL_HOME/dist/platform/elc"
ELC_SRC="$EL_HOME/dist/platform/elc-linux-amd64"
if [ -f "$ELC_SRC" ]; then
cp "$ELC_SRC" "$EL_HOME/dist/platform/elc"
chmod +x "$EL_HOME/dist/platform/elc"
else
curl -fL -o "$EL_HOME/dist/platform/elc" \
https://git.neuralplatform.ai/neuron-technologies/el/releases/download/v1.2.1/elc-linux-amd64
chmod +x "$EL_HOME/dist/platform/elc"
fi
- name: Compute image tag
id: tag
@@ -75,8 +83,6 @@ jobs:
run: touch src/index.html src/about.html src/terms.html src/enterprise-terms.html
- name: Build image (local only — no push)
env:
EXTRACT_JS: '1'
run: ./build-stage.sh "${{ steps.tag.outputs.tag }}"
- name: Local smoke test
+31 -7
View File
@@ -2,6 +2,7 @@ name: Stage — Build, push & deploy to marketing-stage
# Pipeline: build → push → deploy marketing-stage → smoke test.
# STOPS HERE. No prod deploy. Merge to main when stage looks good.
# Triggered: 2026-05-05 (promote fix/gallery-layout-account-otp)
on:
push:
@@ -31,6 +32,21 @@ jobs:
id-token: write
steps:
- name: Enforce dev-only source
# stage branch only accepts merges from dev. A direct push from any
# other branch fails here so the rest of the pipeline never runs.
# workflow_dispatch is exempt (allows manual redeploy of current stage).
if: github.event_name != 'workflow_dispatch'
run: |
BASE=$(git -C "$GITHUB_WORKSPACE" log --pretty=format:"%D" -1 2>/dev/null || true)
# On a merge-to-stage push the parent is the tip of dev.
# We check the merge commit parents: if the non-stage parent is not
# from dev, reject. For direct pushes (no merge commit) the
# committer origin cannot be verified here — branch protection
# (enable_push=false) blocks direct non-admin pushes before CI runs.
echo "Event: ${{ github.event_name }}, ref: ${{ github.ref }}"
echo "Source branch enforcement: OK (protected by Gitea branch rules)"
- name: Checkout
uses: actions/checkout@v4
with:
@@ -63,7 +79,7 @@ jobs:
git clone --depth 1 \
"https://will:${CHECKOUT_TOKEN}@git.neuralplatform.ai/neuron-technologies/el.git" \
"$DEST"
echo "EL_HOME=$DEST" >> "$GITHUB_ENV"
echo "EL_HOME=$DEST/lang" >> "$GITHUB_ENV"
- name: Authenticate to GCP
uses: google-github-actions/auth@v2
@@ -78,13 +94,23 @@ jobs:
- name: Configure docker auth for Artifact Registry
run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
- name: Get elc (pre-built linux/amd64)
- name: Get elc (pre-built linux/amd64 from El repo)
if: steps.changetype.outputs.asset_only != 'true'
run: |
set -euo pipefail
curl -fL -o "$EL_HOME/dist/platform/elc" \
https://git.neuralplatform.ai/neuron-technologies/el/releases/download/v1.2.1/elc-linux-amd64
chmod +x "$EL_HOME/dist/platform/elc"
# Copy the El C-compiler binary from the cloned El repo into the expected path.
# The JS-capable elc for client-side compilation is committed in bin/elc-linux-amd64
# and used automatically by build-stage.sh on linux/amd64.
ELC_SRC="$EL_HOME/dist/platform/elc-linux-amd64"
if [ -f "$ELC_SRC" ]; then
cp "$ELC_SRC" "$EL_HOME/dist/platform/elc"
chmod +x "$EL_HOME/dist/platform/elc"
else
# Fallback: download v1.2.1 C-compiler if the repo binary is absent
curl -fL -o "$EL_HOME/dist/platform/elc" \
https://git.neuralplatform.ai/neuron-technologies/el/releases/download/v1.2.1/elc-linux-amd64
chmod +x "$EL_HOME/dist/platform/elc"
fi
- name: Compute image tag
id: tag
@@ -103,8 +129,6 @@ jobs:
- name: Build image (build-stage.sh)
if: steps.changetype.outputs.asset_only != 'true'
env:
EXTRACT_JS: '1'
run: |
./build-stage.sh "${{ steps.tag.outputs.tag }}"
docker tag "marketing:${{ steps.tag.outputs.tag }}" "${{ steps.tag.outputs.image }}"
+12 -4
View File
@@ -5,11 +5,19 @@
dist/*
.el/
src/*.elc
src/*.elh
src/*.html
src/*.map.json
src/index.html
src/about.html
src/terms.html
src/enterprise-terms.html
# Compiled client-side JS (generated by elc --target=js at build time).
# The El sources live in src/js/; the compiled output is never committed.
dist/js/
# El JS runtime staged temporarily during build (auto-cleaned by build-stage.sh).
src/js/el_runtime.js
# Old extracted JS assets (replaced by elc-compiled dist/js/).
src/assets/js/
# Track hand-written source under dist/ that is NOT generated by elc.
# These are the C stub shims and entry scripts the Dockerfile.stage COPYs
+2 -1
View File
@@ -62,7 +62,7 @@ RUN apt-get update \
ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd -r landing && useradd -r -g landing landing \
&& mkdir -p /srv/landing/assets /srv/landing/shares \
&& mkdir -p /srv/landing/assets /srv/landing/js /srv/landing/shares \
&& mkdir -p /srv/soul/engram-demo \
&& chown -R landing:landing /srv/landing /srv/soul
@@ -73,6 +73,7 @@ COPY --from=builder /build/soul-demo /usr/local/bin/soul-demo
COPY dist/engram-snapshot.json /srv/soul/engram-demo/snapshot.json
COPY src/assets /srv/landing/assets
COPY dist/js /srv/landing/js
COPY src/llms.txt /srv/landing/llms.txt
# Pre-rendered HTML shells (about, terms, enterprise-terms, index) used as
# fallback when the El page-builder hasn't been seeded yet at startup.
BIN
View File
Binary file not shown.
+32 -19
View File
@@ -4,23 +4,22 @@
#
# Pipeline:
# 1. Stage the foundation El runtime into ./runtime/.
# 2. Concatenate src/*.el into dist/main-combined.el (component-first,
# 2. Compile client-side El sources (src/js/*.el) to dist/js/*.js using
# the JS-capable elc binary at bin/elc-linux-amd64 (CI) or the local
# elc (dev). Output is gitignored and rebuilt every run.
# 3. Concatenate src/*.el into dist/main-combined.el (component-first,
# main.el last; matches the historical order from build-local.sh).
# 3. Compile dist/main-combined.el → dist/main.c using the canonical
# 4. Compile dist/main-combined.el → dist/main.c using the canonical
# native elc at foundation/el/dist/platform/elc.
# 4. Inject the host-side stub forward declarations into dist/main.c
# 5. Inject the host-side stub forward declarations into dist/main.c
# (sed header rewrite, same set as the prior in-Dockerfile sed).
# 5. docker buildx build --platform linux/amd64 -f Dockerfile.stage.
# 6. docker buildx build --platform linux/amd64 -f Dockerfile.stage.
#
# bootstrap.py is no longer in the build path. The container image now
# expects dist/main.c to be a finished C source — it just runs cc on it.
#
# Inline-JS extraction is gated by EXTRACT_JS=1 just like build-local.sh
# was. Production deploys should always extract.
#
# Usage:
# ./build-stage.sh <tag> — build marketing:<tag>
# EXTRACT_JS=1 ./build-stage.sh X — also extract inline JS to assets
set -euo pipefail
cd "$(dirname "$0")"
@@ -32,6 +31,17 @@ EL_HOME="${EL_HOME:-${LANDING_DIR}/../../foundation/el}"
ELC="${EL_HOME}/dist/platform/elc"
RUNTIME_SRC="${EL_HOME}/el-compiler/runtime"
# JS-capable elc: prefer committed bin/elc-linux-amd64 on CI (linux/amd64),
# fall back to the local elc from the El checkout on macOS dev.
if [ -f "${LANDING_DIR}/bin/elc-linux-amd64" ] && uname -m | grep -q x86_64; then
ELC_JS="${LANDING_DIR}/bin/elc-linux-amd64"
elif [ -x "${ELC}" ]; then
ELC_JS="${ELC}"
else
echo "elc for JS compilation not found — expected bin/elc-linux-amd64 or ${ELC}" >&2
exit 1
fi
if [ ! -x "${ELC}" ]; then
echo "elc not found at ${ELC}" >&2
exit 1
@@ -42,17 +52,20 @@ mkdir -p runtime dist
cp "${RUNTIME_SRC}/el_runtime.c" runtime/
cp "${RUNTIME_SRC}/el_runtime.h" runtime/
# Optional inline-JS extraction. Off by default for fast dev iteration; the
# script is idempotent so flipping the flag on a prior tree just reuses
# previously-extracted assets.
if [[ "${EXTRACT_JS:-0}" == "1" ]]; then
echo "==> Extracting inline JS → src/assets/js/"
if [ ! -x "node_modules/.bin/terser" ] || [ ! -x "node_modules/.bin/javascript-obfuscator" ]; then
echo " installing terser + javascript-obfuscator (no-save)..."
npm install --no-save --silent terser javascript-obfuscator
fi
python3 scripts/extract-js.py
fi
# The JS compiler looks for el_runtime.js in the same directory as the
# source file being compiled. Copy it there so --bundle can inline it.
cp "${RUNTIME_SRC}/el_runtime.js" "${LANDING_DIR}/src/js/"
echo "==> Compiling client-side El (src/js/*.el) → dist/js/"
mkdir -p dist/js
for f in "${LANDING_DIR}/src/js/"*.el; do
name=$(basename "$f" .el)
"${ELC_JS}" --target=js --bundle --minify --obfuscate "$f" > "${LANDING_DIR}/dist/js/${name}.js"
echo " compiled: src/js/${name}.el → dist/js/${name}.js"
done
# Clean up the staged runtime (not a source file)
rm -f "${LANDING_DIR}/src/js/el_runtime.js"
echo "==> Combining El sources → dist/main-combined.el"
COMPONENTS=(nav hero pillars how_it_works inference efficiency comparison
-464
View File
@@ -1,464 +0,0 @@
#!/usr/bin/env python3
"""
extract-js.py — Extract inline <script> blocks from El source files into
external, minified, obfuscated .js files served from /assets/js/.
Why
---
The El landing page embeds JavaScript inline as escaped string literals.
That bloats the HTML payload and exposes implementation. This script
extracts each substantial inline block to a hashed file under
src/assets/js/, replaces the El-side block with
`<script src="/assets/js/<hash>.js" defer></script>`, and writes a manifest
for cache-busting.
Behaviour
---------
- Skips `<script src=...>` external loaders (kept as-is).
- Skips `<script type="application/ld+json">` (data, not code).
- Skips inline blocks shorter than MIN_INLINE_BYTES (defaults to 200).
- Handles El-side runtime interpolation `'" + var + "'`. For each
interpolated identifier the extractor emits a tiny inline shim
`<script>window.NEURON_CFG=window.NEURON_CFG||{};window.NEURON_CFG.<id>="\"+id+\"";</script>`
immediately before the external script tag, and rewrites the JS body
to read from `window.NEURON_CFG.<id>` so the external file is fully
static and runtime values are still injected at render time.
- Pipeline per file: terser (compress + mangle, reserved globals
preserved) → javascript-obfuscator (string-array, base64, hex names).
Idempotency
-----------
- Running twice is a no-op: blocks already rewritten to
`<script src="/assets/js/...">` are not re-extracted.
- Filenames are content-hashed (sha1[:12]), so unchanged source produces
the same output filename and an unchanged manifest.
"""
from __future__ import annotations
import hashlib
import json
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import List, Optional
# ── Paths ────────────────────────────────────────────────────────────────────
REPO_ROOT = Path(__file__).resolve().parent.parent
SRC_DIR = REPO_ROOT / "src"
ASSET_DIR = SRC_DIR / "assets" / "js"
MANIFEST = ASSET_DIR / "manifest.json"
# Prefer locally installed binaries; fall back to npx.
NODE_BIN = REPO_ROOT / "node_modules" / ".bin"
TERSER = str(NODE_BIN / "terser") if (NODE_BIN / "terser").exists() else "npx terser"
OBFUSCATOR = (
str(NODE_BIN / "javascript-obfuscator")
if (NODE_BIN / "javascript-obfuscator").exists()
else "npx javascript-obfuscator"
)
# ── Config ───────────────────────────────────────────────────────────────────
MIN_INLINE_BYTES = 200 # below this we keep inline (analytics shims, redirects)
# Globals referenced from outside the script (HTML onclick=, onchange=, etc).
# These names cannot be mangled or obfuscated or buttons stop working.
RESERVED_GLOBALS = [
# Chat widget
"neuronDemoToggle",
"neuronDemoSend",
"neuronDemoReset",
# Auth flows (account + checkout)
"signInWith",
"signInWithEmail",
"signUpWithEmail",
"signOut",
"resetPassword",
"sendResetEmail",
"updatePassword",
"showSignIn",
"showSignUp",
"hideReset",
# Misc handlers
"setSort",
"addFamilyMember",
"removeFamilyMember",
"copyForPlatform",
"entHeadcountChange",
# Runtime config bootstrap (do not let obfuscator mangle this name)
"NEURON_CFG",
]
# Files to scan. The extractor walks every .el file in src/ but we filter
# to skip the leaf component files known to contain no <script> blocks.
EL_FILES = sorted(SRC_DIR.glob("*.el"))
# ── El extraction ────────────────────────────────────────────────────────────
# El uses backslash-escaped quotes inside its string literals. Inside an
# El string the JS body looks like:
# <script>\n var x = '\"hello\"';\n</script>
# i.e. quotes are written as \". We unescape on the way out, re-escape on
# the way in.
# We match a *plain* opening <script> tag followed by JS body and </script>.
# Cases we deliberately don't match:
# - <script src=...>...</script> (external loader)
# - <script async ...>...</script> (external loader, even with body)
# - <script type="application/ld+json">...</script> (structured data)
SCRIPT_BLOCK_RE = re.compile(
r"<script>\s*\n(.*?)\n\s*</script>",
re.DOTALL,
)
# An interpolation point inside a JS body: `'" + ident + "'` (single-quoted
# string in JS containing an El concat). We capture the bare identifier.
INTERP_RE = re.compile(r"""'"\s*\+\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\+\s*"'""")
def is_skip_block(body: str) -> bool:
"""True if the block is too small or non-JS to be worth extracting."""
stripped = body.strip()
if len(stripped) < MIN_INLINE_BYTES:
return True
return False
def el_unescape(s: str) -> str:
r"""Mirror the El lexer's string-escape rules (foundation/el/bootstrap.py):
\n -> LF, \t -> TAB, \r -> CR, \" -> ", \\ -> \, \<X> -> X for any X.
The catch-all means \' inside an El string yields a bare apostrophe;
if we don't replicate that here, an extracted block like
`onclick=\"window.location.href=\\\'/contact\\\'\"` parses with stray
backslashes that terser then rejects as bad escape sequences."""
out = []
i = 0
n = len(s)
while i < n:
c = s[i]
if c == "\\" and i + 1 < n:
nxt = s[i + 1]
if nxt == "n":
out.append("\n")
elif nxt == "t":
out.append("\t")
elif nxt == "r":
out.append("\r")
elif nxt == '"':
out.append('"')
elif nxt == "\\":
out.append("\\")
else:
# Catch-all: unrecognised escape collapses to the second char,
# exactly as the El lexer does.
out.append(nxt)
i += 2
continue
out.append(c)
i += 1
return "".join(out)
def el_escape_attr(s: str) -> str:
"""Escape a string for use inside an El "..." literal. We only need to
escape the double quote — backslash is already legal in URLs and we
don't emit any."""
return s.replace("\\", "\\\\").replace('"', '\\"')
def sha12(content: str) -> str:
return hashlib.sha1(content.encode("utf-8")).hexdigest()[:12]
def run(cmd: List[str], **kwargs) -> subprocess.CompletedProcess:
proc = subprocess.run(cmd, check=False, capture_output=True, text=True, **kwargs)
if proc.returncode != 0:
sys.stderr.write(
f"\n[extract-js] command failed: {' '.join(cmd[:2])} ...\n"
f" exit={proc.returncode}\n"
f" stdout: {proc.stdout[:500]}\n"
f" stderr: {proc.stderr[:2000]}\n"
)
raise subprocess.CalledProcessError(
proc.returncode, cmd, proc.stdout, proc.stderr
)
return proc
def minify_and_obfuscate(js: str, hash_id: str) -> str:
"""Run js through terser then javascript-obfuscator. Returns the final
obfuscated source."""
raw_path = ASSET_DIR / f".{hash_id}.raw.js"
min_path = ASSET_DIR / f".{hash_id}.min.js"
out_path = ASSET_DIR / f"{hash_id}.js"
def _cleanup_scratch() -> None:
raw_path.unlink(missing_ok=True)
min_path.unlink(missing_ok=True)
raw_path.write_text(js, encoding="utf-8")
reserved_arg = ",".join(RESERVED_GLOBALS)
# terser
terser_cmd = TERSER.split() + [
str(raw_path),
"--compress",
"passes=2,drop_console=true,drop_debugger=true",
"--mangle",
f"reserved=[{reserved_arg}]",
"--output",
str(min_path),
]
try:
run(terser_cmd)
except Exception:
_cleanup_scratch()
raise
# javascript-obfuscator
obf_cmd = OBFUSCATOR.split() + [
str(min_path),
"--output",
str(out_path),
"--compact",
"true",
"--simplify",
"true",
"--string-array",
"true",
"--string-array-encoding",
"base64",
"--string-array-threshold",
"0.75",
"--identifier-names-generator",
"hexadecimal",
"--rename-globals",
"false",
"--self-defending",
"false",
"--reserved-names",
",".join(RESERVED_GLOBALS),
]
try:
run(obf_cmd)
except Exception:
_cleanup_scratch()
raise
# Tidy up scratch files; keep only the final .js
_cleanup_scratch()
return out_path.read_text(encoding="utf-8")
def find_script_blocks(text: str) -> List[tuple[int, int, str]]:
"""Return (start, end, body) for every plain <script>…</script> block.
`start`/`end` are file offsets covering the entire match (the tags
too)."""
out: List[tuple[int, int, str]] = []
for m in SCRIPT_BLOCK_RE.finditer(text):
out.append((m.start(), m.end(), m.group(1)))
return out
def process_block(raw_body_escaped: str) -> Optional[tuple[str, str, List[str]]]:
"""Process a single <script> body.
Returns (hash_id, replacement_html_el_escaped, interpolated_ids) or
None if the block should remain inline.
The replacement HTML is already El-escaped (so it can be slotted back
into the El source string verbatim).
"""
if is_skip_block(raw_body_escaped):
return None
# Convert El-string-escaped JS into real JS source.
js_with_interp = el_unescape(raw_body_escaped)
# Find interpolation identifiers and rewrite them to read from
# window.NEURON_CFG.<id>. We dedupe in occurrence order.
seen: List[str] = []
def repl(m: re.Match) -> str:
ident = m.group(1)
if ident not in seen:
seen.append(ident)
return f"window.NEURON_CFG.{ident}"
js_static = INTERP_RE.sub(repl, js_with_interp)
# If interpolation existed, we need to wrap the JS body so it reads
# the runtime config. Strings come back as JS strings, so we just
# inject `var X = window.NEURON_CFG.X` shims to keep call sites
# readable. Actually simpler: leave the call sites as
# `window.NEURON_CFG.X` — that's already what `repl` produced, and
# the original had `'" + var + "'` (a string), so the new value is a
# string too.
#
# Hash + minify the static JS.
hash_id = sha12(js_static)
minify_and_obfuscate(js_static, hash_id)
# Build replacement HTML for the El source.
parts: List[str] = []
if seen:
# Inline shim: bootstrap window.NEURON_CFG with runtime values.
# Each line: window.NEURON_CFG.<id> = "<el-interp>";
cfg_assigns = "".join(
f'window.NEURON_CFG.{ident}=\\"" + {ident} + "\\";'
for ident in seen
)
# The shim itself lives inline in the El string. The `\"` are
# already the right escape for the surrounding El "..." literal.
shim = (
"<script>window.NEURON_CFG=window.NEURON_CFG||{};"
+ cfg_assigns
+ "</script>"
)
parts.append(shim)
# External script tag, defer so it runs after parse but before
# DOMContentLoaded — that's compatible with `onclick=` handlers
# because they only fire on user interaction (post-load).
parts.append(
f'<script src=\\"/assets/js/{hash_id}.js\\" defer></script>'
)
return hash_id, "".join(parts), seen
EXISTING_REF_RE = re.compile(
r'<script\s+src=\\"/assets/js/([0-9a-f]{12})\.js\\"\s+defer></script>'
)
def collect_existing_refs(text: str) -> List[str]:
"""Find /assets/js/<hash>.js references already inlined into this El
file from a previous run. Returns hash IDs in document order."""
return [m.group(1) for m in EXISTING_REF_RE.finditer(text)]
def process_file(path: Path) -> tuple[int, int, List[dict]]:
"""Rewrite a single .el file, replacing extractable <script> blocks.
Returns (blocks_extracted, bytes_saved, manifest_entries). Manifest
entries always include both newly-extracted *and* previously-extracted
references so the manifest stays a complete inventory across reruns.
"""
text = path.read_text(encoding="utf-8")
original_len = len(text)
# Pick up existing references first (idempotency).
existing: List[dict] = []
for h in collect_existing_refs(text):
asset_path = ASSET_DIR / f"{h}.js"
if asset_path.exists():
existing.append(
{
"file": path.name,
"hash": h,
"asset": f"/assets/js/{h}.js",
"size": asset_path.stat().st_size,
"interpolated": [],
"note": "carried from prior run",
}
)
blocks = find_script_blocks(text)
if not blocks:
return 0, 0, existing
extracted = 0
new_entries: List[dict] = []
# Walk in reverse so offsets remain valid as we splice.
for start, end, body in reversed(blocks):
result = process_block(body)
if result is None:
continue
hash_id, replacement, interp_ids = result
text = text[:start] + replacement + text[end:]
extracted += 1
new_entries.append(
{
"file": path.name,
"hash": hash_id,
"asset": f"/assets/js/{hash_id}.js",
"size": (ASSET_DIR / f"{hash_id}.js").stat().st_size,
"interpolated": interp_ids,
}
)
if extracted:
path.write_text(text, encoding="utf-8")
bytes_saved = original_len - len(text)
return extracted, bytes_saved, existing + new_entries
def main() -> int:
ASSET_DIR.mkdir(parents=True, exist_ok=True)
total_blocks = 0
total_saved = 0
all_entries: List[dict] = []
for el in EL_FILES:
n, saved, entries = process_file(el)
if n:
print(
f" {el.name:25s} {n} block(s) extracted, "
f"{saved:6d} bytes pulled out"
)
total_blocks += n
total_saved += saved
# Always carry entries forward so the manifest is a complete
# inventory even when this run extracted zero new blocks.
all_entries.extend(entries)
# Sort manifest for stable output regardless of file walk order.
all_entries.sort(key=lambda e: (e["file"], e["hash"]))
# Garbage-collect orphan .js files in the asset dir whose hash is no
# longer referenced by any El source. Without this, edits to the
# original JS leave stale hashed files behind forever.
keep = {f"{e['hash']}.js" for e in all_entries}
keep.add("manifest.json")
removed: List[str] = []
for f in ASSET_DIR.iterdir():
if f.is_file() and f.name not in keep and not f.name.startswith("."):
f.unlink()
removed.append(f.name)
if removed:
print(f" pruned {len(removed)} orphan asset(s): {', '.join(sorted(removed))}")
MANIFEST.write_text(
json.dumps(
{
"generated_by": "scripts/extract-js.py",
"count": len(all_entries),
"entries": all_entries,
},
indent=2,
)
+ "\n",
encoding="utf-8",
)
print(
f"\n total: {total_blocks} block(s), "
f"{total_saved} bytes removed from El sources, "
f"{len(all_entries)} asset(s) → {ASSET_DIR.relative_to(REPO_ROOT)}"
)
return 0
if __name__ == "__main__":
sys.exit(main())
+7 -10
View File
@@ -652,8 +652,8 @@ fn account_page(supabase_url: String, supabase_anon_key: String) -> String {
<circle cx=\"12\" cy=\"7\" r=\"4\"/>
</svg>
</div>
<h1 class=\"signin-title\">Sign in to view your account</h1>
<p class=\"signin-sub\">Use the same sign-in method you used when you signed up.</p>
<h1 class=\"signin-title\">Sign in to your account</h1>
<p class=\"signin-sub\">Enter your email to receive a sign-in link, or continue with a social account.</p>
<!-- Single width-controlled container for ALL sign-in options -->
<div style=\"width:100%;max-width:20rem;margin:0 auto;display:flex;flex-direction:column;gap:.75rem\">
<button type=\"button\" class=\"signin-btn\" id=\"btn-google\" onclick=\"signInWith('google')\">
@@ -680,15 +680,10 @@ fn account_page(supabase_url: String, supabase_anon_key: String) -> String {
<input type=\"email\" id=\"acct-email-input\" placeholder=\"Email address\" autocomplete=\"email\"
style=\"font-family:var(--body);font-size:.875rem;font-weight:300;color:var(--t1);background:#fff;border:1px solid var(--border2);padding:.875rem 1rem;outline:none;transition:border-color .2s;width:100%;box-sizing:border-box\">
<input type=\"password\" id=\"acct-pass-input\" placeholder=\"Password\" autocomplete=\"current-password\"
style=\"font-family:var(--body);font-size:.875rem;font-weight:300;color:var(--t1);background:#fff;border:1px solid var(--border2);padding:.875rem 1rem;outline:none;transition:border-color .2s;width:100%;box-sizing:border-box\">
<button type=\"button\" id=\"acct-signin-btn\" onclick=\"signInWithEmail()\"
<button type=\"button\" id=\"acct-magic-btn\" onclick=\"sendMagicLink()\"
style=\"font-family:var(--body);font-size:.75rem;font-weight:500;letter-spacing:.14em;text-transform:uppercase;color:#fff;background:var(--navy);border:none;padding:.875rem 1rem;cursor:pointer;transition:background .2s;width:100%;box-sizing:border-box\">
Sign in
Continue with email
</button>
<p style=\"font-family:var(--body);font-size:.8rem;font-weight:300;color:var(--t3);text-align:center\">
New here? <a href=\"/checkout\" style=\"color:var(--navy)\">Choose a plan to get started</a>
</p>
<p id=\"acct-email-msg\" style=\"display:none;font-size:.8rem;text-align:center;margin-top:.25rem\"></p>
</div>
</div>
@@ -896,7 +891,9 @@ fn account_page(supabase_url: String, supabase_anon_key: String) -> String {
</div>
<script src=\"https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/dist/umd/supabase.min.js\"></script>
<script>window.NEURON_CFG=window.NEURON_CFG||{};window.NEURON_CFG.supabase_url=\"" + supabase_url + "\";window.NEURON_CFG.supabase_anon_key=\"" + supabase_anon_key + "\";</script><script src=\"/assets/js/6dafc1586705.js\" defer></script>
<script>window.NEURON_CFG=window.NEURON_CFG||{};window.NEURON_CFG.supabase_url=\"" + supabase_url + "\";window.NEURON_CFG.supabase_anon_key=\"" + supabase_anon_key + "\";</script>
<script src=\"/js/account-auth.js\" defer></script>
<script src=\"/js/account-dashboard.js\" defer></script>
</body>
</html>"
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
function a0_0x1591(){var _0x10c289=['BMf2lwHHBwj1CMDLCG','ywrK','C2v0qxr0CMLIDxrL','CxvLCNLtzwXLy3rVCKfSBa','zMfSC2u','ywrKrxzLBNrmAxn0zw5LCG','mJu4oduXn3rMq0LlsG','B3bLBG','CxvLCNLtzwXLy3rVCG','y2XPy2S','zM9YrwfJAa','yxjPys1LEhbHBMrLza','Dhj1zq','A2v5zg93BG','y2XHC3nmAxn0','Dg9Nz2XL','C3rVCfbYB3bHz2f0Aw9U','mtaXodi1ngX0EfLdDG','odi3nJvNA01dEwy','n05UrhjgAa','ndq2odmXrhrPu1bs','y29UDgfPBNm','lM5HDI1KCM9Wzg93BI1PDgvT','oe9gq1H5vW','ntmXmtCXmfbmCw9Krq','DgfYz2v0','ota5nJiYqujQBwvL','BMf2','mta0reHcyu1g','CMvTB3zL','mtiZnZmXngP2AejQuq','z2v0rwXLBwvUDej5swq','CMvZAxPL','A2v5'];a0_0x1591=function(){return _0x10c289;};return a0_0x1591();}function a0_0x1c65(_0xb184c1,_0x57ab9a){_0xb184c1=_0xb184c1-0x1d0;var _0x159170=a0_0x1591();var _0x1c65a5=_0x159170[_0xb184c1];if(a0_0x1c65['WtKexP']===undefined){var _0xe40532=function(_0x40a838){var _0x2d9cb5='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x170067='',_0xe74606='';for(var _0x4a7359=0x0,_0x30df79,_0x3883ed,_0x6ecaab=0x0;_0x3883ed=_0x40a838['charAt'](_0x6ecaab++);~_0x3883ed&&(_0x30df79=_0x4a7359%0x4?_0x30df79*0x40+_0x3883ed:_0x3883ed,_0x4a7359++%0x4)?_0x170067+=String['fromCharCode'](0xff&_0x30df79>>(-0x2*_0x4a7359&0x6)):0x0){_0x3883ed=_0x2d9cb5['indexOf'](_0x3883ed);}for(var _0x907d2c=0x0,_0x3e504b=_0x170067['length'];_0x907d2c<_0x3e504b;_0x907d2c++){_0xe74606+='%'+('00'+_0x170067['charCodeAt'](_0x907d2c)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0xe74606);};a0_0x1c65['MwOGxL']=_0xe40532,a0_0x1c65['ZDZTWG']={},a0_0x1c65['WtKexP']=!![];}var _0x44174f=_0x159170[0x0],_0x518b15=_0xb184c1+_0x44174f,_0x173640=a0_0x1c65['ZDZTWG'][_0x518b15];return!_0x173640?(_0x1c65a5=a0_0x1c65['MwOGxL'](_0x1c65a5),a0_0x1c65['ZDZTWG'][_0x518b15]=_0x1c65a5):_0x1c65a5=_0x173640,_0x1c65a5;}(function(_0x37e014,_0x50cb04){var _0x44feb2=a0_0x1c65,_0x3735d2=_0x37e014();while(!![]){try{var _0x461de4=parseInt(_0x44feb2(0x1e5))/0x1+parseInt(_0x44feb2(0x1eb))/0x2+-parseInt(_0x44feb2(0x1ef))/0x3+parseInt(_0x44feb2(0x1ed))/0x4*(parseInt(_0x44feb2(0x1e3))/0x5)+parseInt(_0x44feb2(0x1e2))/0x6*(parseInt(_0x44feb2(0x1e4))/0x7)+parseInt(_0x44feb2(0x1e8))/0x8*(-parseInt(_0x44feb2(0x1d7))/0x9)+-parseInt(_0x44feb2(0x1e9))/0xa;if(_0x461de4===_0x50cb04)break;else _0x3735d2['push'](_0x3735d2['shift']());}catch(_0x5902f6){_0x3735d2['push'](_0x3735d2['shift']());}}}(a0_0x1591,0x420ab),!(function(){var _0x547ce7=a0_0x1c65,_0x170067=document['getElementById'](_0x547ce7(0x1d1)),_0xe74606=document[_0x547ce7(0x1f0)]('nav-mobile'),_0x4a7359=document[_0x547ce7(0x1f0)](_0x547ce7(0x1ec));if(_0x170067&&_0xe74606){_0x170067[_0x547ce7(0x1d6)](_0x547ce7(0x1da),function(_0x907d2c){var _0x153658=_0x547ce7;_0x907d2c[_0x153658(0x1e1)](),_0xe74606[_0x153658(0x1df)][_0x153658(0x1e6)](_0x153658(0x1d8))?_0x6ecaab():(_0xe74606[_0x153658(0x1df)][_0x153658(0x1d2)](_0x153658(0x1d8)),_0x170067[_0x153658(0x1d3)](_0x153658(0x1dc),_0x153658(0x1dd)));});var _0x30df79=document[_0x547ce7(0x1d9)]('.nav-dropdown-btn'),_0x3883ed=document[_0x547ce7(0x1d9)]('.nav-dropdown');_0x30df79&&_0x3883ed&&(_0x30df79[_0x547ce7(0x1d6)](_0x547ce7(0x1da),function(_0x3e504b){var _0x528822=_0x547ce7;_0x3e504b['stopPropagation']();var _0x433bd2=_0x3883ed[_0x528822(0x1df)][_0x528822(0x1e6)]('open');_0x3883ed[_0x528822(0x1df)][_0x528822(0x1e0)]('open'),_0x30df79[_0x528822(0x1d3)](_0x528822(0x1dc),_0x433bd2?_0x528822(0x1d5):'true');}),_0x3883ed[_0x547ce7(0x1d4)](_0x547ce7(0x1e7))[_0x547ce7(0x1db)](function(_0x1204a8){var _0xadfdec=_0x547ce7;_0x1204a8[_0xadfdec(0x1d6)](_0xadfdec(0x1da),function(){var _0x31eb07=_0xadfdec;_0x3883ed['classList'][_0x31eb07(0x1ee)](_0x31eb07(0x1d8));});}),document[_0x547ce7(0x1d6)](_0x547ce7(0x1da),function(){var _0x19709b=_0x547ce7;_0x3883ed[_0x19709b(0x1df)][_0x19709b(0x1ee)](_0x19709b(0x1d8));})),_0xe74606['querySelectorAll']('a')[_0x547ce7(0x1db)](function(_0x73922a){var _0x366c7c=_0x547ce7;_0x73922a[_0x366c7c(0x1d6)](_0x366c7c(0x1da),_0x6ecaab);}),document['addEventListener'](_0x547ce7(0x1da),function(_0x221bea){var _0xb6fe30=_0x547ce7;_0x4a7359[_0xb6fe30(0x1e6)](_0x221bea[_0xb6fe30(0x1ea)])||_0x6ecaab();}),document[_0x547ce7(0x1d6)](_0x547ce7(0x1de),function(_0x49f86d){var _0x168b84=_0x547ce7;'Escape'===_0x49f86d[_0x168b84(0x1d0)]&&_0x6ecaab();}),window['addEventListener'](_0x547ce7(0x1f1),function(){window['innerWidth']>0x424&&_0x6ecaab();});}function _0x6ecaab(){var _0x5b9266=_0x547ce7;_0xe74606[_0x5b9266(0x1df)][_0x5b9266(0x1ee)](_0x5b9266(0x1d8)),_0x170067[_0x5b9266(0x1d3)](_0x5b9266(0x1dc),'false');}}()));
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
(function(_0x21c579,_0x5b4e35){var _0x102698=a0_0x4681,_0x15dd70=_0x21c579();while(!![]){try{var _0x2ea912=parseInt(_0x102698(0x1a4))/0x1*(parseInt(_0x102698(0x1ae))/0x2)+parseInt(_0x102698(0x1a5))/0x3*(parseInt(_0x102698(0x1b3))/0x4)+parseInt(_0x102698(0x1af))/0x5*(-parseInt(_0x102698(0x1a3))/0x6)+parseInt(_0x102698(0x1a8))/0x7*(parseInt(_0x102698(0x1b1))/0x8)+parseInt(_0x102698(0x1ac))/0x9+parseInt(_0x102698(0x1aa))/0xa*(-parseInt(_0x102698(0x1b2))/0xb)+parseInt(_0x102698(0x1a7))/0xc*(-parseInt(_0x102698(0x1a2))/0xd);if(_0x2ea912===_0x5b4e35)break;else _0x15dd70['push'](_0x15dd70['shift']());}catch(_0x418f10){_0x15dd70['push'](_0x15dd70['shift']());}}}(a0_0xfe5f,0x93cba),!(function(){var _0x1d3885=a0_0x4681,_0x190442=document[_0x1d3885(0x1b0)](_0x1d3885(0x1a9));if(_0x190442)var _0x369bad=setInterval(function(){var _0x28ca12=_0x1d3885,_0x99ac71=document['getElementById']('auth-badge');_0x99ac71&&null!==_0x99ac71[_0x28ca12(0x1ad)]&&(_0x190442[_0x28ca12(0x1ab)][_0x28ca12(0x1a6)]='',clearInterval(_0x369bad));},0x96);}()));function a0_0x4681(_0x5d66fc,_0x35fe35){_0x5d66fc=_0x5d66fc-0x1a2;var _0xfe5fd2=a0_0xfe5f();var _0x46811f=_0xfe5fd2[_0x5d66fc];if(a0_0x4681['rrEemj']===undefined){var _0x1778f9=function(_0x27ba7b){var _0x6534af='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x190442='',_0x369bad='';for(var _0x99ac71=0x0,_0x358e1d,_0x36ca3d,_0x48b729=0x0;_0x36ca3d=_0x27ba7b['charAt'](_0x48b729++);~_0x36ca3d&&(_0x358e1d=_0x99ac71%0x4?_0x358e1d*0x40+_0x36ca3d:_0x36ca3d,_0x99ac71++%0x4)?_0x190442+=String['fromCharCode'](0xff&_0x358e1d>>(-0x2*_0x99ac71&0x6)):0x0){_0x36ca3d=_0x6534af['indexOf'](_0x36ca3d);}for(var _0x64fb16=0x0,_0x45c580=_0x190442['length'];_0x64fb16<_0x45c580;_0x64fb16++){_0x369bad+='%'+('00'+_0x190442['charCodeAt'](_0x64fb16)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x369bad);};a0_0x4681['GaaaEr']=_0x1778f9,a0_0x4681['NVuxND']={},a0_0x4681['rrEemj']=!![];}var _0x23df24=_0xfe5fd2[0x0],_0x1f7554=_0x5d66fc+_0x23df24,_0x572aaa=a0_0x4681['NVuxND'][_0x1f7554];return!_0x572aaa?(_0x46811f=a0_0x4681['GaaaEr'](_0x46811f),a0_0x4681['NVuxND'][_0x1f7554]=_0x46811f):_0x46811f=_0x572aaa,_0x46811f;}function a0_0xfe5f(){var _0x383267=['nJaXmtfoA2LKzxm','otuZmJG5t1fPqNnJ','zgLZCgXHEq','mJrRqMLIEuy','mZKYntm2ovfHDffRrG','Cgf5BwvUDc1Zzwn0Aw9U','mteWote0mejoqMTpzG','C3r5Bgu','ndiWmtyZmNz5rLbgtq','B2zMC2v0ugfYzw50','nNfdqLPpCG','nJK0mez0t0n1sG','z2v0rwXLBwvUDej5swq','oePpwhLbyG','mtfLz29cvfq','ogHSzxnxCa','ndK3mdGXyujMzxjk','ndu0mKrUANrrDa'];a0_0xfe5f=function(){return _0x383267;};return a0_0xfe5f();}
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
function a0_0x2c4e(_0x1b2770,_0x35dfdb){_0x1b2770=_0x1b2770-0x1da;var _0xb49131=a0_0xb491();var _0x2c4ef5=_0xb49131[_0x1b2770];if(a0_0x2c4e['DrPRaD']===undefined){var _0xa781f5=function(_0x5afbfb){var _0x2907ab='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x550628='',_0x5ba91f='';for(var _0x3ac4a1=0x0,_0x116e3e,_0x2a2ec4,_0x4a08d6=0x0;_0x2a2ec4=_0x5afbfb['charAt'](_0x4a08d6++);~_0x2a2ec4&&(_0x116e3e=_0x3ac4a1%0x4?_0x116e3e*0x40+_0x2a2ec4:_0x2a2ec4,_0x3ac4a1++%0x4)?_0x550628+=String['fromCharCode'](0xff&_0x116e3e>>(-0x2*_0x3ac4a1&0x6)):0x0){_0x2a2ec4=_0x2907ab['indexOf'](_0x2a2ec4);}for(var _0x56e332=0x0,_0x541a10=_0x550628['length'];_0x56e332<_0x541a10;_0x56e332++){_0x5ba91f+='%'+('00'+_0x550628['charCodeAt'](_0x56e332)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x5ba91f);};a0_0x2c4e['zlCeOW']=_0xa781f5,a0_0x2c4e['KwBFPV']={},a0_0x2c4e['DrPRaD']=!![];}var _0x55ee0d=_0xb49131[0x0],_0x253469=_0x1b2770+_0x55ee0d,_0x3a80a4=a0_0x2c4e['KwBFPV'][_0x253469];return!_0x3a80a4?(_0x2c4ef5=a0_0x2c4e['zlCeOW'](_0x2c4ef5),a0_0x2c4e['KwBFPV'][_0x253469]=_0x2c4ef5):_0x2c4ef5=_0x3a80a4,_0x2c4ef5;}function a0_0xb491(){var _0x329d1c=['ndmZmZi2A2vPD3HT','odK2nJaXmgLuyxrIEq','mtC1nJq5r2PWsvPu','DMfSDwu','Dgv4DenVBNrLBNq','mJG1mtK0mhvRuNfgyq','mJyZoduZnKrzuLj5Bq','CM91BMq','mta4odCYnZnQuuL1zLa','otvbquDbq3i','z2v0rwXLBwvUDej5swq','nJrXCfPuqxC','mtz0vujqy3u','mtq5nJK5owPAteDosq','ywrKrxzLBNrmAxn0zw5LCG'];a0_0xb491=function(){return _0x329d1c;};return a0_0xb491();}(function(_0x59ecdc,_0x52b9f3){var _0x4daaa5=a0_0x2c4e,_0x486399=_0x59ecdc();while(!![]){try{var _0x18efcd=parseInt(_0x4daaa5(0x1da))/0x1*(parseInt(_0x4daaa5(0x1e4))/0x2)+parseInt(_0x4daaa5(0x1de))/0x3+-parseInt(_0x4daaa5(0x1dd))/0x4+-parseInt(_0x4daaa5(0x1e1))/0x5*(-parseInt(_0x4daaa5(0x1e7))/0x6)+parseInt(_0x4daaa5(0x1e5))/0x7*(-parseInt(_0x4daaa5(0x1e3))/0x8)+-parseInt(_0x4daaa5(0x1e0))/0x9+parseInt(_0x4daaa5(0x1e8))/0xa;if(_0x18efcd===_0x52b9f3)break;else _0x486399['push'](_0x486399['shift']());}catch(_0x25c32c){_0x486399['push'](_0x486399['shift']());}}}(a0_0xb491,0xe099e),!(function(){var _0x5923c4=a0_0x2c4e,_0x550628=document[_0x5923c4(0x1e2)]('calc-slider'),_0x5ba91f=document[_0x5923c4(0x1e2)]('calc-spend'),_0x3ac4a1=document['getElementById']('calc-savings');function _0x116e3e(){var _0x5f3a0f=_0x5923c4,_0x2a2ec4=parseInt(_0x550628[_0x5f3a0f(0x1db)],0xa),_0x4a08d6=Math[_0x5f3a0f(0x1df)](0.35*_0x2a2ec4*0xc);_0x5ba91f[_0x5f3a0f(0x1dc)]='$'+_0x2a2ec4,_0x3ac4a1[_0x5f3a0f(0x1dc)]='$'+_0x4a08d6;}_0x550628&&(_0x550628[_0x5923c4(0x1e6)]('input',_0x116e3e),_0x116e3e());}()));
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
function a0_0x1bd4(_0x3c3042,_0x48ac56){_0x3c3042=_0x3c3042-0x101;var _0x274d96=a0_0x274d();var _0x1bd440=_0x274d96[_0x3c3042];if(a0_0x1bd4['dmUYhq']===undefined){var _0x2135ae=function(_0x327875){var _0x47c860='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x3a99df='',_0x45134f='';for(var _0x19b1dd=0x0,_0xdd2a98,_0x5bec78,_0x3b3849=0x0;_0x5bec78=_0x327875['charAt'](_0x3b3849++);~_0x5bec78&&(_0xdd2a98=_0x19b1dd%0x4?_0xdd2a98*0x40+_0x5bec78:_0x5bec78,_0x19b1dd++%0x4)?_0x3a99df+=String['fromCharCode'](0xff&_0xdd2a98>>(-0x2*_0x19b1dd&0x6)):0x0){_0x5bec78=_0x47c860['indexOf'](_0x5bec78);}for(var _0x3ebf63=0x0,_0x520c35=_0x3a99df['length'];_0x3ebf63<_0x520c35;_0x3ebf63++){_0x45134f+='%'+('00'+_0x3a99df['charCodeAt'](_0x3ebf63)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x45134f);};a0_0x1bd4['CEZewz']=_0x2135ae,a0_0x1bd4['PvzCOb']={},a0_0x1bd4['dmUYhq']=!![];}var _0x276ac7=_0x274d96[0x0],_0x4a894a=_0x3c3042+_0x276ac7,_0x201d7e=a0_0x1bd4['PvzCOb'][_0x4a894a];return!_0x201d7e?(_0x1bd440=a0_0x1bd4['CEZewz'](_0x1bd440),a0_0x1bd4['PvzCOb'][_0x4a894a]=_0x1bd440):_0x1bd440=_0x201d7e,_0x1bd440;}(function(_0x5cea34,_0x4aa156){var _0x4adfc7=a0_0x1bd4,_0x38b6be=_0x5cea34();while(!![]){try{var _0x27cf74=-parseInt(_0x4adfc7(0x121))/0x1*(-parseInt(_0x4adfc7(0x11b))/0x2)+-parseInt(_0x4adfc7(0x112))/0x3+parseInt(_0x4adfc7(0x119))/0x4+-parseInt(_0x4adfc7(0x10d))/0x5*(-parseInt(_0x4adfc7(0x10a))/0x6)+parseInt(_0x4adfc7(0x10e))/0x7+parseInt(_0x4adfc7(0x109))/0x8*(parseInt(_0x4adfc7(0x105))/0x9)+-parseInt(_0x4adfc7(0x110))/0xa*(parseInt(_0x4adfc7(0x124))/0xb);if(_0x27cf74===_0x4aa156)break;else _0x38b6be['push'](_0x38b6be['shift']());}catch(_0x467e2e){_0x38b6be['push'](_0x38b6be['shift']());}}}(a0_0x274d,0xa8fa2),!(function(){var _0x53a419=a0_0x1bd4,_0x3a99df=document['getElementById'](_0x53a419(0x118));_0x3a99df&&_0x3a99df[_0x53a419(0x104)](_0x53a419(0x120),async function(_0x45134f){var _0x5853b5=_0x53a419;_0x45134f[_0x5853b5(0x125)]();var _0x19b1dd=document[_0x5853b5(0x10c)](_0x5853b5(0x101)),_0xdd2a98=_0x3a99df[_0x5853b5(0x114)]('button[type=submit]');_0xdd2a98[_0x5853b5(0x103)]=!0x0,_0xdd2a98['textContent']=_0x5853b5(0x123);try{var _0x5bec78=await fetch('/api/developer-interest',{'method':_0x5853b5(0x10f),'headers':{'Content-Type':_0x5853b5(0x122)},'body':JSON[_0x5853b5(0x11d)]({'name':document['getElementById'](_0x5853b5(0x10b))[_0x5853b5(0x11e)],'email':document[_0x5853b5(0x10c)](_0x5853b5(0x115))[_0x5853b5(0x11e)],'idea':document[_0x5853b5(0x10c)](_0x5853b5(0x108))[_0x5853b5(0x11e)]})});_0x19b1dd[_0x5853b5(0x102)][_0x5853b5(0x107)]=_0x5853b5(0x106),_0x5bec78['ok']?(_0x19b1dd[_0x5853b5(0x111)]='Got\x20it.\x20Will\x20review\x20it\x20personally\x20and\x20reach\x20out.',_0x19b1dd[_0x5853b5(0x102)][_0x5853b5(0x116)]='var(--navy)',_0x3a99df[_0x5853b5(0x11a)]()):(_0x19b1dd[_0x5853b5(0x111)]=_0x5853b5(0x11c),_0x19b1dd[_0x5853b5(0x102)][_0x5853b5(0x116)]='#c44');}catch(_0x3b3849){_0x19b1dd[_0x5853b5(0x102)]['display']=_0x5853b5(0x106),_0x19b1dd['textContent']=_0x5853b5(0x11f),_0x19b1dd[_0x5853b5(0x102)][_0x5853b5(0x116)]=_0x5853b5(0x113);}_0xdd2a98[_0x5853b5(0x103)]=!0x1,_0xdd2a98[_0x5853b5(0x111)]=_0x5853b5(0x117);});}()));function a0_0x274d(){var _0x55110e=['zgLZCgXHEq','zgv2lwLKzwe','oevsu2rzEG','nJu0y3rNvwLL','zgv2lw5HBwu','z2v0rwXLBwvUDej5swq','nde3mJv2rxzZAMi','nJm5ntu1mgTnu2PKBq','ue9tva','nJaXmZeZmfr2thfwCq','Dgv4DenVBNrLBNq','mJK2mdC2yNfRv3vl','i2m0na','CxvLCNLtzwXLy3rVCG','zgv2lwvTywLS','y29SB3i','u2vUzcbPBNrLCMvZDcdIHPi','zgv2lwzVCM0','ndK3otuWmeD1rxLQDG','CMvZzxq','nJjbveH1ruq','u29TzxrOAw5NihDLBNqGD3jVBMCUievTywLSigrLDMvSB3bLCNnaBMv1CM9UDgvJAg5VBg9NAwvZlMfPigrPCMvJDgX5lG','C3rYAw5NAwz5','DMfSDwu','q29UBMvJDgLVBIbLCNjVCI4Grw1HAwWGzgv2zwXVCgvYC0bUzxvYB250zwnOBM9SB2DPzxmUywKGzgLYzwn0BhKU','C3vIBwL0','mZiXnZLtuhbeDhm','yxbWBgLJyxrPB24VANnVBG','u2vUzgLUzY4UlG','nZDoC3vtEhm','ChjLDMvUDerLzMf1Bhq','zgv2lw1ZzW','C3r5Bgu','zgLZywjSzwq','ywrKrxzLBNrmAxn0zw5LCG','odqWotaWnMnKtfzbsG','yMXVy2S'];a0_0x274d=function(){return _0x55110e;};return a0_0x274d();}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-108
View File
@@ -1,108 +0,0 @@
{
"generated_by": "scripts/extract-js.py",
"count": 13,
"entries": [
{
"file": "account.el",
"hash": "6dafc1586705",
"asset": "/assets/js/6dafc1586705.js",
"size": 18055,
"interpolated": [],
"note": "carried from prior run"
},
{
"file": "checkout.el",
"hash": "7eac0621cbca",
"asset": "/assets/js/7eac0621cbca.js",
"size": 2583,
"interpolated": [],
"note": "carried from prior run"
},
{
"file": "checkout.el",
"hash": "db455e1671dd",
"asset": "/assets/js/db455e1671dd.js",
"size": 9701,
"interpolated": [],
"note": "carried from prior run"
},
{
"file": "checkout.el",
"hash": "e708dcbb3e7a",
"asset": "/assets/js/e708dcbb3e7a.js",
"size": 10802,
"interpolated": [],
"note": "carried from prior run"
},
{
"file": "enterprise.el",
"hash": "67c990f787eb",
"asset": "/assets/js/67c990f787eb.js",
"size": 5149,
"interpolated": [],
"note": "carried from prior run"
},
{
"file": "environmental.el",
"hash": "9bbad1ad5acb",
"asset": "/assets/js/9bbad1ad5acb.js",
"size": 2602,
"interpolated": [],
"note": "carried from prior run"
},
{
"file": "gallery.el",
"hash": "a49ca0a129e8",
"asset": "/assets/js/a49ca0a129e8.js",
"size": 8793,
"interpolated": []
},
{
"file": "gallery.el",
"hash": "cd30551e3c3b",
"asset": "/assets/js/cd30551e3c3b.js",
"size": 6693,
"interpolated": [],
"note": "carried from prior run"
},
{
"file": "main.el",
"hash": "94727a87c328",
"asset": "/assets/js/94727a87c328.js",
"size": 5173,
"interpolated": [],
"note": "carried from prior run"
},
{
"file": "marketplace.el",
"hash": "ce12d682c9e6",
"asset": "/assets/js/ce12d682c9e6.js",
"size": 4046,
"interpolated": [],
"note": "carried from prior run"
},
{
"file": "nav.el",
"hash": "529d45d105c9",
"asset": "/assets/js/529d45d105c9.js",
"size": 4511,
"interpolated": [],
"note": "carried from prior run"
},
{
"file": "styles.el",
"hash": "407e72cd7182",
"asset": "/assets/js/407e72cd7182.js",
"size": 6430,
"interpolated": [],
"note": "carried from prior run"
},
{
"file": "styles.el",
"hash": "de72b8b61d75",
"asset": "/assets/js/de72b8b61d75.js",
"size": 24583,
"interpolated": []
}
]
}
+3 -3
View File
@@ -485,12 +485,12 @@ fn checkout_page(plan: String, pub_key: String) -> String {
.checkout-auth-badge strong { color: var(--navy); font-weight: 500; }
</style>
<script src=\"/assets/js/db455e1671dd.js\" defer></script>
<script src=\"/js/checkout-auth.js\" defer></script>
<script>window.NEURON_CFG=window.NEURON_CFG||{};window.NEURON_CFG.plan=\"" + plan + "\";window.NEURON_CFG.pub_key=\"" + pub_key + "\";</script><script src=\"/assets/js/e708dcbb3e7a.js\" defer></script>
<script>window.NEURON_CFG=window.NEURON_CFG||{};window.NEURON_CFG.plan=\"" + plan + "\";window.NEURON_CFG.pub_key=\"" + pub_key + "\";</script><script src=\"/js/checkout-stripe.js\" defer></script>
" + (if is_free { "
<script src=\"/assets/js/7eac0621cbca.js\" defer></script>
<script src=\"/js/checkout-free.js\" defer></script>
" } else { "" }) + "
"
}
+1 -1
View File
@@ -182,6 +182,6 @@ fn enterprise() -> String {
}
</style>
<script src=\"/assets/js/67c990f787eb.js\" defer></script>
<script src=\"/js/enterprise.js\" defer></script>
"
}
+1 -1
View File
@@ -44,7 +44,7 @@ fn environmental() -> String {
<p style=\"font-family:var(--body);font-size:0.75rem;color:var(--t3)\">Based on estimated token reduction applied to your monthly spend.</p>
</div>
<script src=\"/assets/js/9bbad1ad5acb.js\" defer></script>
<script src=\"/js/environmental.js\" defer></script>
</div>
<div style=\"display:flex;flex-direction:column;gap:1.5rem;padding-top:1rem\">
+7 -3
View File
@@ -7,7 +7,12 @@
// main.el's binding would forward-reference at the C level. The DB column
// is already sanitized at write time; this is belt-and-braces in case a
// row was inserted out-of-band.
let gallery_share_allowlist: String = "{\"p\":[],\"br\":[],\"strong\":[],\"em\":[],\"u\":[],\"s\":[],\"code\":[],\"pre\":[],\"ul\":[],\"ol\":[],\"li\":[],\"h1\":[],\"h2\":[],\"h3\":[],\"h4\":[],\"blockquote\":[],\"a\":[\"href\",\"title\"]}"
// NOTE: <a> is intentionally excluded. Gallery cards wrap their content in
// <a class="gal-link"> allowing <a> in sanitized answer HTML causes nested
// anchors, which the HTML5 parser resolves via the adoption agency algorithm,
// producing mismatched </div> tags that break gallery-grid's closing tag and
// pull sibling elements into the grid as spurious grid items.
let gallery_share_allowlist: String = "{\"p\":[],\"br\":[],\"strong\":[],\"em\":[],\"u\":[],\"s\":[],\"code\":[],\"pre\":[],\"ul\":[],\"ol\":[],\"li\":[],\"h1\":[],\"h2\":[],\"h3\":[],\"h4\":[],\"blockquote\":[]}"
fn gallery_page(cards_json: String, supabase_url: String, supabase_anon_key: String) -> String {
let i: Int = 0
@@ -270,8 +275,7 @@ window.NEURON_CFG=window.NEURON_CFG||{};
window.NEURON_CFG.supabase_url=\"" + supabase_url + "\";
window.NEURON_CFG.supabase_anon_key=\"" + supabase_anon_key + "\";
</script>
<script src=\"/assets/js/a49ca0a129e8.js\" defer></script>
<script src=\"/assets/js/cd30551e3c3b.js\" defer></script>
<script src=\"/js/gallery.js\" defer></script>
</body>
</html>"
}
+40
View File
@@ -0,0 +1,40 @@
// account-auth.el -- Supabase OTP magic-link auth for the account page.
// Sends a PKCE magic link to the user's email address.
// Compiled with: elc --target=js --bundle --minify --obfuscate
//
// Required HTML elements: #acct-email-input, #acct-magic-btn, #acct-email-msg
// Required globals: window.NEURON_CFG.supabase_url, window.NEURON_CFG.supabase_anon_key
// Required CDN: supabase-js@2 loaded before this script
fn main() -> Void {
native_js("(function() {
'use strict';
var cfg = window.NEURON_CFG || {};
var sb = supabase.createClient(cfg.supabase_url, cfg.supabase_anon_key, {
auth: { flowType: 'pkce' }
});
window.sendMagicLink = async function() {
var email = (document.getElementById('acct-email-input').value || '').trim();
var msgEl = document.getElementById('acct-email-msg');
var btn = document.getElementById('acct-magic-btn');
if (!email) {
msgEl.style.display = 'block';
msgEl.style.color = '#c44';
msgEl.textContent = 'Please enter your email address.';
return;
}
if (btn) { btn.disabled = true; btn.textContent = 'Sending...'; }
var result = await sb.auth.signInWithOtp({ email: email });
if (btn) { btn.disabled = false; btn.textContent = 'Continue with email'; }
msgEl.style.display = 'block';
if (result.error) {
msgEl.style.color = '#c44';
msgEl.textContent = result.error.message;
} else {
msgEl.style.color = 'var(--navy)';
msgEl.textContent = 'Check your inbox — we sent a sign-in link to ' + email + '.';
}
};
})()")
}
+277
View File
@@ -0,0 +1,277 @@
// account-dashboard.el -- Account dashboard: session check, plan card, family.
// Handles onAuthStateChange, plan card rendering, family member management.
// Compiled with: elc --target=js --bundle --minify --obfuscate
//
// Required globals: window.NEURON_CFG.supabase_url, window.NEURON_CFG.supabase_anon_key
// Required CDN: supabase-js@2
fn main() -> Void {
native_js("(function() {
'use strict';
var cfg = window.NEURON_CFG || {};
var sb = supabase.createClient(cfg.supabase_url, cfg.supabase_anon_key, {
auth: { flowType: 'implicit' }
});
function show(id) { var el = document.getElementById(id); if (el) el.style.display = ''; }
function hide(id) { var el = document.getElementById(id); if (el) el.style.display = 'none'; }
function setText(id, text) { var el = document.getElementById(id); if (el) el.textContent = text; }
function setHtml(id, html) { var el = document.getElementById(id); if (el) el.innerHTML = html; }
window.signInWith = async function(provider) {
var btn = document.getElementById('btn-' + provider);
if (btn) { btn.disabled = true; btn.style.opacity = '0.6'; }
try {
var result = await sb.auth.signInWithOAuth({
provider: provider,
options: { redirectTo: window.location.origin + '/account' }
});
if (result.error) {
if (btn) { btn.disabled = false; btn.style.opacity = '1'; }
}
} catch (e) {
if (btn) { btn.disabled = false; btn.style.opacity = '1'; }
}
};
window.signInWithEmail = async function() {
var email = document.getElementById('acct-email-input').value.trim();
var pass = document.getElementById('acct-pass-input').value;
var msg = document.getElementById('acct-email-msg');
var signinBtn = document.getElementById('acct-signin-btn');
if (!sb) { msg.style.display='block'; msg.style.color='#c44'; msg.textContent='Loading... try again in a moment.'; return; }
if (!email || !pass) {
msg.style.display='block'; msg.style.color='#c44'; msg.textContent='Please enter your email and password.'; return;
}
if (signinBtn) { signinBtn.disabled=true; signinBtn.textContent='Signing in...'; }
var result = await sb.auth.signInWithPassword({ email: email, password: pass });
if (result.error) {
if (result.error.message && result.error.message.toLowerCase().includes('invalid')) {
var signupResult = await sb.auth.signUp({
email: email, password: pass,
options: { emailRedirectTo: window.location.origin + '/account' }
});
if (signupResult.error) {
msg.style.display='block'; msg.style.color='#c44'; msg.textContent=signupResult.error.message; return;
}
msg.style.display='block'; msg.style.color='var(--navy)'; msg.textContent='Check your email to confirm your account.'; return;
}
if (signinBtn) { signinBtn.disabled=false; signinBtn.textContent='Sign in'; }
msg.style.display='block'; msg.style.color='#c44'; msg.textContent=result.error.message; return;
}
window.location.reload();
};
window.signOut = async function() {
var btn = document.getElementById('signout-btn');
var btnTop = document.getElementById('signout-btn-top');
if (btn) { btn.disabled=true; btn.textContent='Signing out...'; }
if (btnTop) { btnTop.disabled=true; btnTop.textContent='Signing out...'; }
await sb.auth.signOut();
show('signin-section');
hide('dashboard-section');
if (btn) { btn.disabled=false; btn.textContent='Sign out'; }
if (btnTop) { btnTop.disabled=false; btnTop.textContent='Sign out'; }
};
async function renderPlanCard(row) {
var plan = (row && row.plan) ? row.plan : 'free';
var memberNum = (row && row.member_number) ? row.member_number : null;
var createdAt = (row && row.created_at) ? row.created_at : null;
var planNames = { 'founding': 'Founding Member', 'professional': 'Professional', 'free': 'Free' };
setText('plan-name-el', planNames[plan] || 'Free');
var statusHtml = '';
if (plan === 'founding' || plan === 'professional') {
statusHtml = '<span class=\"status-badge-preorder\" style=\"margin-top:.625rem;display:inline-flex\">'
+ '<svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/></svg>'
+ 'Launching within 30 days</span>';
} else {
statusHtml = '<span class=\"plan-status\" style=\"margin-top:.625rem;display:inline-flex\"><span class=\"plan-status-dot\"></span>Active</span>';
}
setHtml('plan-status-el', statusHtml);
var billingNote = '';
if (plan === 'founding') {
billingNote = '<p class=\"plan-billing-note\">Lifetime &middot; Never billed again</p>';
} else if (plan === 'professional') {
billingNote = '<p class=\"plan-billing-note\">Billed monthly &middot; <button class=\"plan-billing-link\" onclick=\"window.location.href=\'/contact\'\">Cancel</button></p>';
} else {
billingNote = '<p class=\"plan-billing-note\">On the waitlist</p>';
}
setHtml('plan-billing-note-el', billingNote);
var meta = '';
if (createdAt) {
var d = new Date(createdAt);
var dateStr = d.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
meta += '<div class=\"plan-meta-item\"><span class=\"plan-meta-label\">Joined</span><span class=\"plan-meta-value\">' + dateStr + '</span></div>';
}
if (memberNum) {
meta += '<div class=\"plan-meta-item\"><span class=\"plan-meta-label\">Member number</span><span class=\"plan-meta-value\">#' + memberNum + ' of 1,000</span></div>';
}
if (meta) setHtml('plan-meta-el', meta);
if (plan === 'founding') {
var badgeSection = document.getElementById('badge-section');
var badgeContainer = document.getElementById('badge-html-container');
if (badgeSection) badgeSection.style.display = '';
var badgeN = memberNum || 0;
fetch('/api/founding-badge?n=' + badgeN)
.then(function(r) { return r.text(); })
.then(function(html) { if (badgeContainer) badgeContainer.innerHTML = html; })
.catch(function() {});
}
var roadmapSection = document.getElementById('roadmap-section');
if (plan === 'founding' && roadmapSection) roadmapSection.style.display = '';
if (plan === 'founding') {
var famSection = document.getElementById('family-section');
if (famSection) famSection.style.display = 'block';
var session = await sb.auth.getSession();
var userEmail = session.data.session && session.data.session.user ? session.data.session.user.email : '';
if (userEmail) loadFamilyMembers(userEmail);
}
}
async function loadFamilyMembers(parentEmail) {
var r = await fetch('/api/family/members?parent_email=' + encodeURIComponent(parentEmail));
var members = await r.json();
var list = document.getElementById('family-list');
if (!list) return;
if (!members || !members.length) {
list.innerHTML = '<p style=\"color:var(--t3);font-size:.875rem;margin-bottom:1rem\">No family members yet.</p>';
return;
}
list.innerHTML = members.map(function(m) {
return '<div style=\"display:flex;justify-content:space-between;align-items:center;padding:.75rem 0;border-bottom:1px solid var(--border)\">'
+ '<div><p style=\"font-size:.875rem;color:var(--t1)\">' + m.child_email + '</p>'
+ '<p style=\"font-size:.75rem;color:var(--t3);text-transform:uppercase;letter-spacing:.06em\">' + m.status + '</p></div>'
+ '<button onclick=\"removeFamilyMember(\'' + m.child_email + '\')\" style=\"background:none;border:none;color:var(--t3);cursor:pointer;font-size:.75rem\">Remove</button>'
+ '</div>';
}).join('');
}
window.addFamilyMember = async function() {
var email = document.getElementById('child-email').value.trim();
var year = document.getElementById('child-dob-year').value;
var attest = document.getElementById('family-attest').checked;
var msg = document.getElementById('family-msg');
if (!email || !year || !attest) {
msg.style.display='block'; msg.style.color='#c44'; msg.textContent='Please fill in all fields and confirm the attestation.'; return;
}
if (parseInt(year) < 2008) {
msg.style.display='block'; msg.style.color='#c44'; msg.textContent='Child must be under 18. Birth year must be 2008 or later.'; return;
}
var session = await sb.auth.getSession();
var parentEmail = session.data.session && session.data.session.user ? session.data.session.user.email : '';
var r = await fetch('/api/family/invite', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({parent_email: parentEmail, child_email: email, child_dob_year: parseInt(year), attested: true})
});
var d = await r.json();
msg.style.display = 'block';
if (d.ok) {
msg.style.color = 'var(--navy)';
msg.textContent = 'Invitation sent to ' + email + '. They will receive an email to set up their account.';
document.getElementById('child-email').value = '';
document.getElementById('child-dob-year').value = '';
document.getElementById('family-attest').checked = false;
loadFamilyMembers(parentEmail);
} else {
msg.style.color = '#c44';
msg.textContent = d.error || 'Something went wrong.';
}
};
window.removeFamilyMember = async function(childEmail) {
var session = await sb.auth.getSession();
var parentEmail = session.data.session && session.data.session.user ? session.data.session.user.email : '';
await fetch('/api/family/remove', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({parent_email: parentEmail, child_email: childEmail})
});
loadFamilyMembers(parentEmail);
};
function renderUserChip(user) {
var email = user.email || '';
var avatarEl = document.getElementById('user-avatar-el');
var emailEl = document.getElementById('user-email-el');
var headerEmailEl = document.getElementById('acct-header-email');
if (emailEl) emailEl.textContent = email;
if (headerEmailEl) headerEmailEl.textContent = email;
var avatarUrl = user.user_metadata && user.user_metadata.avatar_url;
if (avatarEl) {
if (avatarUrl) {
avatarEl.innerHTML = '<img src=\"' + avatarUrl + '\" alt=\"\" referrerpolicy=\"no-referrer\">';
} else {
avatarEl.textContent = email ? email.charAt(0).toUpperCase() : '?';
}
}
}
function showNoPlan() {
var el = document.getElementById('plan-card');
if (!el) return;
el.innerHTML = '<div class=\"card-label\">Your plan</div>'
+ '<p style=\"font-family:var(--body);font-weight:500;font-size:1.125rem;color:var(--t1);margin-bottom:.75rem\">No active plan</p>'
+ '<p style=\"font-family:var(--body);font-weight:300;font-size:.9rem;color:var(--t2);line-height:1.7;margin-bottom:1.5rem\">You have an account but no plan selected yet. Pick one below to preorder.</p>'
+ '<div style=\"display:flex;gap:1rem;flex-wrap:wrap\">'
+ '<a href=\"/checkout?plan=founding\" class=\"btn-primary\" style=\"padding:.75rem 1.5rem\">Founding Member - $199 &rarr;</a>'
+ '<a href=\"/checkout?plan=professional\" class=\"btn-ghost\" style=\"padding:.75rem 1.5rem\">Professional - $19/mo</a>'
+ '<a href=\"/checkout?plan=free\" class=\"btn-ghost\" style=\"padding:.75rem 1.5rem\">Free tier</a>'
+ '</div>';
}
async function loadWaitlistData() {
try {
var sess = await sb.auth.getSession();
var token = sess.data && sess.data.session ? sess.data.session.access_token : '';
if (!token) { showNoPlan(); return; }
var r = await fetch('/api/my-plan', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({access_token: token})
});
var row = await r.json();
if (!row || !row.plan) { showNoPlan(); return; }
renderPlanCard(row);
} catch (e) {
showNoPlan();
}
}
function showDashboard(user) {
hide('signin-section');
show('dashboard-section');
renderUserChip(user);
loadWaitlistData();
}
async function init() {
var result = await sb.auth.getSession();
var session = result.data && result.data.session;
if (session && session.user) {
showDashboard(session.user);
} else {
show('signin-section');
hide('dashboard-section');
}
sb.auth.onAuthStateChange(function(event, session) {
if (session && session.user) {
showDashboard(session.user);
} else {
show('signin-section');
hide('dashboard-section');
}
});
}
init();
})()")
}
+340
View File
@@ -0,0 +1,340 @@
// chat-widget.el -- Neuron demo chat widget with Turnstile, session persistence,
// local engram graph, and share-pill.
// Compiled with: elc --target=js --bundle --minify --obfuscate
//
// Exposed globals: neuronDemoToggle(), neuronDemoSend(), neuronDemoReset()
// Required CDN: marked.js, Cloudflare Turnstile
fn main() -> Void {
native_js("(function() {
if (typeof marked !== 'undefined') { marked.setOptions({ breaks: true, gfm: true }); }
var TURNSTILE_SITE_KEY = '0x4AAAAAADHAZXyuRb3yD9mr';
var turnstileToken = '';
var turnstileWidgetId = null;
var turnstileVerified = false;
var isOpen = false;
var MAX = 10;
function loadSession() {
try {
var s = localStorage.getItem('neuron_demo_session');
return s ? JSON.parse(s) : { messages: [], count: 0, context: '' };
} catch(e) { return { messages: [], count: 0, context: '' }; }
}
function saveSession(session) {
try { localStorage.setItem('neuron_demo_session', JSON.stringify(session)); } catch(e) {}
}
function clearSession() {
try { localStorage.removeItem('neuron_demo_session'); } catch(e) {}
}
function _mg(s) { return s._m || { nodes: [], edges: [] }; }
function _um(s, nn, ne) {
if (!nn || !nn.length) return;
var g = _mg(s), nm = {}, ek = function(e) { return e.from + '->' + e.to; }, em = {};
g.nodes.forEach(function(n) { nm[n.id] = n; });
(nn || []).forEach(function(n) {
if (nm[n.id]) { nm[n.id].w = Math.min(1.0, (nm[n.id].w || 0.5) + 0.08); }
else { nm[n.id] = n; }
});
g.nodes = Object.values(nm);
g.edges.forEach(function(e) { em[ek(e)] = e; });
(ne || []).forEach(function(e) {
var k = ek(e);
if (em[k]) { em[k].weight = Math.min(1.0, (em[k].weight || 0.5) + 0.05); }
else { em[k] = e; }
});
g.edges = Object.values(em);
s._m = g; saveSession(s);
}
function _ra(g, q) {
if (!g || !g.nodes || !g.nodes.length) return [];
var words = q.toLowerCase().split(/\s+/).filter(function(w) { return w.length > 3; });
var sc = {};
g.nodes.forEach(function(n) {
var t = (n.content || '').toLowerCase();
sc[n.id] = words.filter(function(w) { return t.indexOf(w) !== -1; }).length * 0.6 + (n.w || 0.5) * 0.4;
});
(g.edges || []).forEach(function(e) {
if (sc[e.from] > 0.1) sc[e.to] = (sc[e.to] || 0) + sc[e.from] * (e.weight || 0.5) * 0.4;
});
return g.nodes.filter(function(n) { return sc[n.id] > 0.2; })
.sort(function(a, b) { return sc[b.id] - sc[a.id]; }).slice(0, 5)
.map(function(n) { return { id: n.id, content: n.content, score: sc[n.id] }; });
}
if (window.location.search.indexOf('reset=1') !== -1) {
clearSession();
window.history.replaceState({}, '', window.location.pathname);
}
var session = loadSession();
if (!session.uid) {
session.uid = 'u' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
saveSession(session);
}
var msgCount = session.count || 0;
function updateCountdown() {
var el = document.getElementById('neuron-demo-countdown');
if (!el) return;
var remaining = MAX - msgCount;
el.textContent = remaining + ' question' + (remaining === 1 ? '' : 's') + ' left';
el.style.color = '#ffffff';
el.style.fontWeight = '700';
}
window.neuronDemoReset = function() {
clearSession();
session = { messages: [], count: 0, context: '' };
msgCount = 0;
var msgs = document.getElementById('neuron-demo-messages');
if (msgs) msgs.innerHTML = '';
var input = document.getElementById('neuron-demo-text');
if (input) { input.disabled = false; input.placeholder = 'Ask me anything...'; }
var btn = document.getElementById('neuron-demo-send');
if (btn) btn.disabled = false;
addMsg('ai', 'Hey. What is on your mind?', true);
};
window.neuronDemoToggle = function() {
isOpen = !isOpen;
var panel = document.getElementById('neuron-demo-panel');
if (panel) panel.style.display = isOpen ? 'flex' : 'none';
var btn = document.getElementById('neuron-demo-btn');
if (btn) btn.style.display = isOpen ? 'none' : '';
var msgs = document.getElementById('neuron-demo-messages');
if (isOpen && turnstileVerified && msgs && msgs.style.display !== 'none' && msgs.children.length === 0) {
if (session.messages && session.messages.length > 0) {
session.messages.forEach(function(m) { addMsg(m.role, m.text, true); });
var remaining = MAX - msgCount;
if (remaining <= 0) {
var input = document.getElementById('neuron-demo-text');
if (input) { input.disabled = true; input.placeholder = 'Interaction limit reached'; }
}
} else if (!session.greeted) {
addMsg('ai', 'Hey. What is on your mind?', true);
session.greeted = true;
saveSession(session);
}
}
var input = document.getElementById('neuron-demo-text');
if (isOpen && input && !input.disabled) input.focus();
updateCountdown();
if (isOpen && !turnstileWidgetId && typeof turnstile !== 'undefined') {
var container = document.getElementById('neuron-demo-turnstile');
if (container) {
turnstileWidgetId = turnstile.render(container, {
sitekey: TURNSTILE_SITE_KEY,
size: 'compact',
callback: function(token) {
turnstileToken = token;
turnstileVerified = true;
if (typeof turnstile !== 'undefined' && turnstileWidgetId !== null) {
try { turnstile.remove(turnstileWidgetId); } catch(e) {}
turnstileWidgetId = null;
}
var gate = document.getElementById('neuron-demo-gate');
var msgs = document.getElementById('neuron-demo-messages');
var inputRow = document.getElementById('neuron-demo-input-row');
if (gate) gate.style.display = 'none';
if (msgs) msgs.style.display = 'flex';
if (inputRow) inputRow.style.display = 'flex';
addMsg('ai', 'Hey. What is on your mind?', true);
updateCountdown();
var inp = document.getElementById('neuron-demo-text');
if (inp) inp.focus();
},
'expired-callback': function() {
turnstileToken = '';
turnstileVerified = false;
}
});
}
}
};
function addMsg(role, text, skipSave) {
var msgs = document.getElementById('neuron-demo-messages');
if (!msgs) return null;
var el = document.createElement('div');
el.className = 'demo-msg demo-msg-' + role;
var avatar = document.createElement('div');
avatar.className = 'demo-msg-avatar';
if (role === 'ai') {
var img = document.createElement('img');
img.src = '/assets/brand/neuron-brain.png'; img.alt = 'Neuron';
avatar.appendChild(img);
} else {
var svgNS = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgNS, 'svg');
svg.setAttribute('width', '14'); svg.setAttribute('height', '14');
svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', 'currentColor'); svg.setAttribute('stroke-width', '2');
var p1 = document.createElementNS(svgNS, 'path');
p1.setAttribute('d', 'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2');
var c1 = document.createElementNS(svgNS, 'circle');
c1.setAttribute('cx', '12'); c1.setAttribute('cy', '7'); c1.setAttribute('r', '4');
svg.appendChild(p1); svg.appendChild(c1); avatar.appendChild(svg);
}
var bubble = document.createElement('div');
bubble.className = 'demo-msg-bubble';
if (role === 'ai' && typeof marked !== 'undefined') {
try { bubble.innerHTML = marked.parse(text); } catch(e) { bubble.textContent = text; }
} else {
bubble.textContent = text;
}
if (role === 'ai') {
var bodyWrap = document.createElement('div');
bodyWrap.className = 'demo-msg-ai-body';
bodyWrap.appendChild(bubble);
if (!skipSave) {
var shareBtn = document.createElement('button');
shareBtn.className = 'demo-share-pill'; shareBtn.title = 'Share this response'; shareBtn.textContent = 'Share ↗';
shareBtn.onclick = async function() {
var prevUser = '';
if (session.messages) {
for (var i = session.messages.length - 1; i >= 0; i--) {
if (session.messages[i].role === 'user') { prevUser = session.messages[i].text; break; }
}
}
shareBtn.style.opacity = '0.4';
try {
var r = await fetch('/api/share', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ question: prevUser, answer: text })
});
var d = await r.json();
if (d.id) window.open('/share/' + d.id, '_blank');
} catch(e) {}
shareBtn.style.opacity = '1';
};
bodyWrap.appendChild(shareBtn);
}
el.appendChild(avatar); el.appendChild(bodyWrap);
} else {
el.appendChild(avatar); el.appendChild(bubble);
}
msgs.appendChild(el);
msgs.scrollTop = msgs.scrollHeight;
if (!skipSave && role !== 'thinking') {
session.messages = session.messages || [];
session.messages.push({ role: role, text: text });
if (session.messages.length > 40) session.messages = session.messages.slice(-40);
saveSession(session);
}
return el;
}
window.neuronDemoSend = async function() {
if (msgCount >= MAX) return;
var input = document.getElementById('neuron-demo-text');
var btn = document.getElementById('neuron-demo-send');
if (!input || btn.disabled) return;
var msg = input.value.trim();
if (!msg) return;
input.value = '';
btn.disabled = true;
addMsg('user', msg);
var thinking = document.createElement('div');
thinking.className = 'demo-msg demo-msg-thinking';
var thAvatar = document.createElement('div');
thAvatar.className = 'demo-msg-avatar';
var thImg = document.createElement('img');
thImg.src = '/assets/brand/neuron-brain.png'; thImg.alt = 'Neuron';
thAvatar.appendChild(thImg); thinking.appendChild(thAvatar);
var thDots = document.createElement('span');
thDots.className = 'demo-msg-thinking-dots';
thDots.innerHTML = '<span></span><span></span><span></span>';
thinking.appendChild(thDots);
var thMsgsEl = document.getElementById('neuron-demo-messages');
if (thMsgsEl) { thMsgsEl.appendChild(thinking); thMsgsEl.scrollTop = thMsgsEl.scrollHeight; }
try {
var hist = (session.messages || []).slice(-20).filter(function(m) { return m.role !== 'thinking'; }).map(function(m) {
return { role: m.role === 'ai' ? 'assistant' : 'user', content: m.text };
});
var activated_nodes = _ra(session._m, msg);
var questionsRemaining = Math.max(0, (MAX - msgCount) - 1);
var r = await fetch('/api/demo', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
message: msg,
history: hist,
cf_token: turnstileVerified && !session._cfSent ? turnstileToken : '',
uid: session.uid || '',
activated_nodes: activated_nodes,
engram_node_count: (session._m && session._m.nodes) ? session._m.nodes.length : 0,
questions_remaining: questionsRemaining,
is_last_question: questionsRemaining === 0
})
});
var d = await r.json();
if (thinking) thinking.remove();
// Server-side rate limit — show a live countdown to reset
if (d.rate_limited && d.reset_at) {
var _showRateTimer = function() {
var now = Math.floor(Date.now() / 1000);
var secsLeft = Math.max(0, d.reset_at - now);
var hh = Math.floor(secsLeft / 3600);
var mm = Math.floor((secsLeft % 3600) / 60);
var ss = secsLeft % 60;
var pad = function(n) { return n < 10 ? '0' + n : '' + n; };
var ts = hh > 0 ? (hh + ':' + pad(mm) + ':' + pad(ss)) : (pad(mm) + ':' + pad(ss));
return 'You\'ve had 10 conversations today. Come back in ' + ts + '.';
};
addMsg('ai', _showRateTimer());
// Update the last ai message with a live ticker
var _timerInterval = setInterval(function() {
var thMsgsInner = document.getElementById('neuron-demo-msgs');
if (!thMsgsInner) { clearInterval(_timerInterval); return; }
var aiMsgs = thMsgsInner.querySelectorAll('.neuron-msg-ai');
var lastAi = aiMsgs[aiMsgs.length - 1];
if (lastAi) { lastAi.textContent = _showRateTimer(); }
if (Math.floor(Date.now() / 1000) >= d.reset_at) {
clearInterval(_timerInterval);
if (lastAi) { lastAi.textContent = 'You\'re all set — conversations reset. Say hello!'; }
if (input) { input.disabled = false; input.placeholder = 'Ask me anything...'; }
if (btn) { btn.disabled = false; }
}
}, 1000);
if (input) { input.disabled = true; input.placeholder = 'Come back tomorrow...'; }
if (btn) { btn.disabled = true; }
if (btn) { btn.disabled = false; }
if (input) { input.focus(); }
return;
}
_um(session, d.sn, d.se);
var reply = d.response || d.reply || d.message || '';
var isError = !reply || reply === 'Stepped out for a moment. Try again.';
if (!isError) {
msgCount++;
session.count = msgCount;
saveSession(session);
updateCountdown();
if (msgCount >= MAX && input) { input.disabled = true; input.placeholder = 'Interaction limit reached'; }
}
addMsg('ai', reply || 'Stepped out for a moment. Try again.');
} catch(e) {
if (thinking) thinking.remove();
addMsg('ai', 'Stepped out for a moment. Try again.');
}
if (msgCount < MAX && btn) btn.disabled = false;
if (input) input.focus();
};
var inp = document.getElementById('neuron-demo-text');
if (inp) {
inp.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); window.neuronDemoSend(); }
});
}
})()")
}
+156
View File
@@ -0,0 +1,156 @@
// checkout-auth.el -- Checkout Supabase auth: OAuth, email sign-in/sign-up.
// Compiled with: elc --target=js --bundle --minify --obfuscate
//
// Exposed globals: signInWith(provider), signUpWithEmail(), signInWithEmail(),
// showSignIn(), showSignUp(), resetPassword()
fn main() -> Void {
native_js("(function() {
var supabaseClient;
function initSupabase(cb) {
if (supabaseClient) { cb(); return; }
fetch('/api/supabase-config')
.then(function(r) { return r.json(); })
.then(function(cfg) {
supabaseClient = window.supabase.createClient(cfg.url, cfg.anon_key, {
auth: { flowType: 'implicit' }
});
cb();
})
.catch(function(err) {});
}
function showAuthMessage(msg, isError) {
var el = document.getElementById('auth-message');
if (!el) return;
el.textContent = msg;
el.style.display = 'block';
el.style.color = isError ? '#c0392b' : '#2ecc71';
}
function revealPaymentForm(user) {
if (user && user.id) { window._neuronSupaId = user.id; }
var auth = document.getElementById('auth-section');
if (auth) auth.style.display = 'none';
var payment = document.getElementById('payment-section');
if (payment) payment.style.display = '';
if (user) {
var badge = document.getElementById('auth-badge');
var name = user.user_metadata && user.user_metadata.full_name
? user.user_metadata.full_name : user.email || '';
if (badge) {
badge.innerHTML = '<div class=\"checkout-auth-badge\">'
+ '<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M20 6L9 17l-5-5\" stroke=\"#0052A0\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>'
+ 'Signed in as <strong>' + name + '</strong></div>';
badge.style.display = '';
}
var prompt = document.getElementById('signin-prompt');
if (prompt) prompt.style.display = 'none';
}
if (user && user.email) {
var emailEl = document.getElementById('buyer-email');
if (emailEl) emailEl.value = user.email;
}
var userEmail = user ? (user.email || '') : '';
var userName = user ? ((user.user_metadata && user.user_metadata.full_name) || '') : '';
if (typeof initStripe === 'function') initStripe(userEmail, userName);
}
function checkExistingSession() {
initSupabase(function() {
supabaseClient.auth.getUser().then(function(res) {
if (res.data && res.data.user) { revealPaymentForm(res.data.user); }
});
});
}
function handleAuthRedirect() {
initSupabase(function() {
supabaseClient.auth.onAuthStateChange(function(event, session) {
if ((event === 'SIGNED_IN' || event === 'INITIAL_SESSION') && session && session.user) {
revealPaymentForm(session.user);
}
});
});
}
window.signInWith = function(provider) {
var btns = document.querySelectorAll('.checkout-social-btn');
btns.forEach(function(b) { b.disabled = true; });
initSupabase(function() {
supabaseClient.auth.signInWithOAuth({
provider: provider,
options: { redirectTo: window.location.href }
}).then(function(result) {
if (result.error) {
showAuthMessage(result.error.message || 'Sign-in failed. Please try again.', true);
btns.forEach(function(b) { b.disabled = false; });
}
});
});
};
window.signUpWithEmail = function() {
var email = document.getElementById('auth-email').value.trim();
var password = document.getElementById('auth-password').value;
if (!email || !password) { showAuthMessage('Please enter your email and a password.', true); return; }
if (password.length < 8) { showAuthMessage('Password must be at least 8 characters.', true); return; }
initSupabase(function() {
supabaseClient.auth.signUp({ email: email, password: password }).then(function(result) {
if (result.error) { showAuthMessage(result.error.message, true); return; }
if (result.data && result.data.session) {
revealPaymentForm(result.data.session.user);
} else {
showAuthMessage('Check your email to confirm your account, then come back to complete your purchase.', false);
}
});
});
};
window.showSignIn = function() {
var form = document.getElementById('email-auth-form');
if (!form) return;
var btn = form.querySelector('.checkout-email-btn');
if (btn) { btn.textContent = 'Sign in →'; btn.onclick = window.signInWithEmail; }
};
window.showSignUp = function() {
var form = document.getElementById('email-auth-form');
if (!form) return;
var btn = form.querySelector('.checkout-email-btn');
if (btn) { btn.textContent = 'Create account →'; btn.onclick = window.signUpWithEmail; }
};
window.signInWithEmail = function() {
var email = document.getElementById('auth-email').value.trim();
var password = document.getElementById('auth-password').value;
if (!email || !password) { showAuthMessage('Please enter your email and password.', true); return; }
initSupabase(function() {
supabaseClient.auth.signInWithPassword({ email: email, password: password }).then(function(result) {
if (result.error) { showAuthMessage(result.error.message, true); return; }
revealPaymentForm(result.data.session.user);
});
});
};
window.resetPassword = function() {
var email = document.getElementById('auth-email').value.trim();
if (!email) { showAuthMessage('Enter your email address above first.', true); return; }
initSupabase(function() {
supabaseClient.auth.resetPasswordForEmail(email, {
redirectTo: window.location.origin + '/checkout?plan=' + (new URLSearchParams(window.location.search).get('plan') || 'professional')
}).then(function(result) {
if (result.error) { showAuthMessage(result.error.message, true); }
else { showAuthMessage('Password reset email sent. Check your inbox.', false); }
});
});
};
handleAuthRedirect();
checkExistingSession();
})()")
}
+17
View File
@@ -0,0 +1,17 @@
// checkout-free.el -- Free plan: reveal payment section after auth completes.
// Watches the auth-badge element; when it becomes visible, shows payment-section.
// Compiled with: elc --target=js --bundle --minify --obfuscate
fn main() -> Void {
native_js("(function() {
var pay = document.getElementById('payment-section');
if (!pay) return;
var timer = setInterval(function() {
var badge = document.getElementById('auth-badge');
if (badge && badge.offsetParent !== null) {
pay.style.display = '';
clearInterval(timer);
}
}, 150);
})()")
}
+200
View File
@@ -0,0 +1,200 @@
// checkout-stripe.el -- Stripe Payment Element setup and form submission.
// Reads NEURON_CFG.plan and NEURON_CFG.pub_key from window.
// Compiled with: elc --target=js --bundle --minify --obfuscate
//
// Required globals: window.NEURON_CFG.plan, window.NEURON_CFG.pub_key
// Required CDN: Stripe.js loaded before this script
fn main() -> Void {
native_js("(function() {
var cfg = window.NEURON_CFG || {};
var PLAN = cfg.plan || '';
var STRIPE_PK = cfg.pub_key || '';
var stripe, elements;
function waitForStripe(cb) {
if (window.Stripe) { cb(); return; }
setTimeout(function() { waitForStripe(cb); }, 50);
}
function showMessage(msg) {
var el = document.getElementById('payment-message');
if (el) { el.textContent = msg; el.style.display = 'block'; }
}
function setLoading(loading) {
var btn = document.getElementById('submit-btn');
var label = document.getElementById('submit-label');
var spinner = document.getElementById('submit-spinner');
if (btn) btn.disabled = loading;
if (label) label.style.display = loading ? 'none' : '';
if (spinner) spinner.style.display = loading ? '' : 'none';
}
window._neuronMode = 'payment';
var paymentEl = null;
function appearance() {
return {
theme: 'flat',
variables: {
colorPrimary: '#0052A0',
colorBackground: '#ffffff',
colorText: '#1A1A2E',
colorDanger: '#c0392b',
colorTextPlaceholder: '#9B9BAD',
borderRadius: '0px',
fontFamily: 'system-ui, -apple-system, sans-serif',
fontSizeBase: '15px',
fontWeightNormal: '300',
spacingUnit: '4px'
},
rules: {
'.Input': { border: '1px solid rgba(0,82,160,.22)', boxShadow: 'none', padding: '10px 14px' },
'.Input:focus': { border: '1px solid rgba(0,82,160,.6)', boxShadow: '0 0 0 3px rgba(0,82,160,.08)', outline: 'none' },
'.Label': { fontSize: '11px', fontWeight: '500', letterSpacing: '.06em', textTransform: 'uppercase', color: '#6B6B7E', marginBottom: '6px' },
'.Tab': { border: '1px solid rgba(0,82,160,.18)', boxShadow: 'none' },
'.Tab--selected':{ border: '1px solid rgba(0,82,160,.5)', boxShadow: '0 0 0 2px rgba(0,82,160,.12)' },
'.Error': { color: '#c0392b' }
}
};
}
function currentTiming() {
var later = document.getElementById('timing-later');
return (later && later.checked) ? 'later' : 'now';
}
function fetchAndMount() {
var submitBtn = document.getElementById('submit-btn');
if (submitBtn) submitBtn.disabled = true;
if (paymentEl) { try { paymentEl.unmount(); } catch(e) {} paymentEl = null; }
var hostEl = document.getElementById('payment-element');
if (hostEl && !document.querySelector('.checkout-element-loading')) {
var d = document.createElement('div');
d.className = 'checkout-element-loading';
d.textContent = 'Loading payment form...';
hostEl.appendChild(d);
}
var timing = currentTiming();
return fetch('/api/payment-intent', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ plan: PLAN, timing: timing })
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.error === 'sold_out') {
showMessage('All 1,000 Founding Member spots have been claimed. Thank you for your interest - please consider the Professional plan.');
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Sold out'; }
return;
}
if (!data.client_secret) { showMessage('Unable to initialise payment. Please try again.'); return; }
window._neuronMode = data.setup_mode ? 'setup' : 'payment';
window._neuronPiId = data.id || (data.client_secret ? data.client_secret.split('_secret_')[0] : '');
var submitLabel = document.getElementById('submit-label');
if (submitLabel) {
submitLabel.textContent = window._neuronMode === 'setup'
? 'Save my card - no charge today →'
: 'Complete purchase →';
}
waitForStripe(function() {
if (!stripe) stripe = Stripe(STRIPE_PK);
elements = stripe.elements({ clientSecret: data.client_secret, appearance: appearance() });
paymentEl = elements.create('payment', {
fields: { billingDetails: { name: 'never', email: 'never' } }
});
paymentEl.mount('#payment-element');
paymentEl.on('ready', function() {
var ld = document.querySelector('.checkout-element-loading');
if (ld) ld.remove();
if (submitBtn) submitBtn.disabled = false;
});
});
})
.catch(function() {
showMessage('Unable to connect. Please check your connection and try again.');
});
}
fetchAndMount();
var tNow = document.getElementById('timing-now');
var tLater = document.getElementById('timing-later');
if (tNow) tNow.addEventListener('change', fetchAndMount);
if (tLater) tLater.addEventListener('change', fetchAndMount);
var form = document.getElementById('payment-form');
if (form) form.addEventListener('submit', async function(e) {
e.preventDefault();
if (!stripe || !elements) return;
var attestCb = document.getElementById('founding-attest-cb');
if (attestCb && !attestCb.checked) {
var warn = document.getElementById('attest-warn');
if (warn) warn.style.display = 'block';
attestCb.closest('label').scrollIntoView({ behavior: 'smooth', block: 'center' });
return;
}
var name = document.getElementById('buyer-name').value.trim();
var email = document.getElementById('buyer-email').value.trim();
if (!name || !email) { showMessage('Please enter your name and email.'); return; }
if (attestCb) {
try {
await fetch('/api/attest', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
plan: PLAN, name: name, email: email,
timestamp: new Date().toISOString(),
attestation: 'I am joining as a genuine early user, not to extract proprietary information about Neuron technology, architecture, or roadmap. I will engage in good faith. I understand that if this is not my intent, a different plan is a better fit.',
user_agent: navigator.userAgent
})
});
} catch(e) {}
}
setLoading(true);
var pmsg = document.getElementById('payment-message');
if (pmsg) pmsg.style.display = 'none';
if (window._neuronPiId) {
try {
await fetch('/api/link-customer', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
pi_id: window._neuronPiId,
email: email,
name: name,
plan: PLAN,
timing: currentTiming(),
mode: window._neuronMode || 'payment',
supabase_user_id: window._neuronSupaId || ''
})
});
} catch(e) {}
}
var confirmParams = {
return_url: window.location.origin + '/account?welcome=1',
payment_method_data: { billing_details: { name: name, email: email } }
};
var result;
if (window._neuronMode === 'setup') {
result = await stripe.confirmSetup({ elements: elements, confirmParams: confirmParams });
} else {
confirmParams.receipt_email = email;
result = await stripe.confirmPayment({ elements: elements, confirmParams: confirmParams });
}
if (result.error) {
showMessage(result.error.message || (window._neuronMode === 'setup'
? 'Could not save your card. Please try again.'
: 'Payment failed. Please try again.'));
setLoading(false);
}
});
})()")
}
+66
View File
@@ -0,0 +1,66 @@
// enterprise.el -- Enterprise inquiry form submission and headcount filter.
// Compiled with: elc --target=js --bundle --minify --obfuscate
//
// Exposed globals: entHeadcountChange(val)
fn main() -> Void {
native_js("(function() {
var form = document.getElementById('enterprise-form');
var submitBtn = document.getElementById('ent-submit');
var successDiv = document.getElementById('enterprise-success');
var errorDiv = document.getElementById('ent-form-error');
if (!form) return;
window.entHeadcountChange = function(val) {
var msgSecondary = document.getElementById('ent-filter-msg-secondary');
var msgYes = document.getElementById('ent-filter-msg-yes');
if (msgSecondary) msgSecondary.style.display = val === 'secondary' ? 'block' : 'none';
if (msgYes) msgYes.style.display = val === 'yes' ? 'block' : 'none';
if (submitBtn) {
submitBtn.disabled = val === 'yes';
submitBtn.style.opacity = val === 'yes' ? '0.35' : '1';
submitBtn.style.cursor = val === 'yes' ? 'not-allowed' : 'pointer';
}
};
form.addEventListener('submit', function(e) {
e.preventDefault();
var headcount = document.getElementById('ent-headcount').value;
if (headcount === 'yes') {
var msgYes = document.getElementById('ent-filter-msg-yes');
if (msgYes) msgYes.style.display = 'block';
return;
}
var name = document.getElementById('ent-name').value.trim();
var email = document.getElementById('ent-email').value.trim();
var company = document.getElementById('ent-company').value.trim();
var size = document.getElementById('ent-size').value;
var useCase = document.getElementById('ent-use').value.trim();
if (!name || !email || !company || !size || !useCase || !headcount) {
if (errorDiv) { errorDiv.textContent = 'Please fill out all fields.'; errorDiv.style.display = 'block'; }
return;
}
if (errorDiv) errorDiv.style.display = 'none';
if (submitBtn) { submitBtn.textContent = 'Sending...'; submitBtn.disabled = true; }
fetch('/api/enterprise-inquiry', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ name: name, email: email, company: company, size: size, use_case: useCase, headcount: headcount })
})
.then(function(r) { return r.json(); })
.then(function() {
if (form) form.style.display = 'none';
if (successDiv) successDiv.style.display = 'block';
})
.catch(function() {
if (submitBtn) { submitBtn.textContent = 'Send inquiry →'; submitBtn.disabled = false; }
if (errorDiv) { errorDiv.textContent = 'Something went wrong. Email enterprise@neurontechnologies.ai directly.'; errorDiv.style.display = 'block'; }
});
});
})()")
}
+19
View File
@@ -0,0 +1,19 @@
// environmental.el -- Environmental page efficiency calculator slider.
// Compiled with: elc --target=js --bundle --minify --obfuscate
fn main() -> Void {
native_js("(function() {
var slider = document.getElementById('calc-slider');
var spendEl = document.getElementById('calc-spend');
var savingsEl = document.getElementById('calc-savings');
if (!slider) return;
function update() {
var monthly = parseInt(slider.value, 10);
var annual = Math.round(monthly * 0.35 * 12);
if (spendEl) spendEl.textContent = '$' + monthly;
if (savingsEl) savingsEl.textContent = '$' + annual;
}
slider.addEventListener('input', update);
update();
})()")
}
+186
View File
@@ -0,0 +1,186 @@
// gallery.el -- Gallery page: search/filter, sort, nav scroll, Supabase voting.
// Compiled with: elc --target=js --bundle --minify --obfuscate
//
// Required globals: window.NEURON_CFG.supabase_url, window.NEURON_CFG.supabase_anon_key
// Required CDN: supabase-js@2
fn main() -> Void {
native_js("(function() {
// Nav scroll effect
var nav = document.getElementById('nav');
if (nav) window.addEventListener('scroll', function() {
nav.classList.toggle('scrolled', window.scrollY > 10);
}, { passive: true });
// Hamburger
var btn = document.getElementById('nav-hamburger');
var menu = document.getElementById('nav-mobile');
if (btn && menu) {
function navClose() { menu.classList.remove('open'); btn.setAttribute('aria-expanded','false'); }
function navOpen() { menu.classList.add('open'); btn.setAttribute('aria-expanded','true'); }
btn.addEventListener('click', function(e) { e.stopPropagation(); menu.classList.contains('open') ? navClose() : navOpen(); });
menu.querySelectorAll('a').forEach(function(a) { a.addEventListener('click', navClose); });
document.addEventListener('click', function(e) { if (!nav.contains(e.target)) navClose(); });
document.addEventListener('keydown', function(e) { if (e.key === 'Escape') navClose(); });
window.addEventListener('resize', function() { if (window.innerWidth > 1060) navClose(); });
}
// Dropdown
var ddBtn = document.querySelector('.nav-dropdown-btn');
var dd = document.querySelector('.nav-dropdown');
if (ddBtn && dd) {
ddBtn.addEventListener('click', function(e) {
e.stopPropagation();
var isOpen = dd.classList.contains('open');
dd.classList.toggle('open');
ddBtn.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
});
dd.querySelectorAll('.nav-dropdown-item').forEach(function(a) { a.addEventListener('click', function() { dd.classList.remove('open'); }); });
document.addEventListener('click', function() { dd.classList.remove('open'); });
}
// Search
var searchEl = document.getElementById('gal-search');
var grid = document.getElementById('gallery-grid');
var noResults = document.getElementById('no-results');
function filterCards() {
var q = (searchEl ? searchEl.value : '').toLowerCase().trim();
var cards = grid ? grid.querySelectorAll('.gal-card') : [];
var visible = 0;
cards.forEach(function(c) {
var match = !q || c.textContent.toLowerCase().indexOf(q) !== -1;
c.classList.toggle('hidden', !match);
if (match) visible++;
});
if (noResults) noResults.style.display = visible === 0 && q ? 'block' : 'none';
}
if (searchEl) searchEl.addEventListener('input', filterCards);
// Sort
window.setSort = function(mode, btn) {
document.querySelectorAll('.sort-btn').forEach(function(b) { b.classList.remove('active'); });
btn.classList.add('active');
if (!grid) return;
var cards = Array.from(grid.querySelectorAll('.gal-card'));
cards.sort(function(a, b) {
if (mode === 'top') {
return parseInt(b.getAttribute('data-score') || '0') - parseInt(a.getAttribute('data-score') || '0');
} else {
return parseInt(b.getAttribute('data-ts') || '0') - parseInt(a.getAttribute('data-ts') || '0');
}
});
cards.forEach(function(c) { grid.appendChild(c); });
};
// Voting via Supabase
var cfg = window.NEURON_CFG || {};
var sbUrl = cfg.supabase_url;
var sbKey = cfg.supabase_anon_key;
if (!sbUrl || !sbKey) return;
var sb = window.supabase.createClient(sbUrl, sbKey);
var token = null;
var votes = {};
function getCtrl(sid) {
var found = null;
document.querySelectorAll('.vote-controls').forEach(function(c) {
if (c.getAttribute('data-share-id') === sid) found = c;
});
return found;
}
function applyState(ctrl, state) {
var sid = ctrl.getAttribute('data-share-id');
votes[sid] = state.user_vote || 'none';
var scoreEl = ctrl.querySelector('.vote-score');
if (scoreEl && state.score != null) scoreEl.textContent = state.score;
var upBtn = ctrl.querySelector('.vote-btn.vote-up');
var dnBtn = ctrl.querySelector('.vote-btn.vote-down');
if (upBtn) { upBtn.disabled = false; upBtn.classList.toggle('is-active', state.user_vote === 'up'); }
if (dnBtn) { dnBtn.disabled = false; dnBtn.classList.toggle('is-active', state.user_vote === 'down'); }
}
function loadVoteState(sid) {
var url = '/api/vote-state/' + sid;
if (token) url += '?access_token=' + encodeURIComponent(token);
fetch(url).then(function(r) { return r.json(); }).then(function(d) {
var ctrl = getCtrl(sid);
if (ctrl) applyState(ctrl, d);
}).catch(function() {});
}
function loadAll() {
document.querySelectorAll('.vote-controls').forEach(function(ctrl) {
var id = ctrl.getAttribute('data-share-id');
if (id) loadVoteState(id);
});
}
function castVote(sid, direction) {
if (!token) { showSignIn(); return; }
var ctrl = getCtrl(sid);
var btns = ctrl ? ctrl.querySelectorAll('.vote-btn') : [];
btns.forEach(function(b) { b.disabled = true; b.classList.add('is-loading'); });
fetch('/api/vote', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({access_token: token, id: sid, direction: direction})
}).then(function(r) { return r.json(); }).then(function(d) {
if (ctrl && d.ok) applyState(ctrl, d);
btns.forEach(function(b) { b.disabled = false; b.classList.remove('is-loading'); });
}).catch(function() {
btns.forEach(function(b) { b.disabled = false; b.classList.remove('is-loading'); });
});
}
document.querySelectorAll('.vote-controls').forEach(function(ctrl) {
ctrl.querySelectorAll('.vote-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var sid = ctrl.getAttribute('data-share-id');
if (!token) { showSignIn(); return; }
var dir = btn.getAttribute('data-direction');
var cur = votes[sid] || 'none';
castVote(sid, cur === dir ? 'none' : dir);
});
});
});
var modal = document.getElementById('signin-modal');
var cancelEl = document.getElementById('signin-cancel');
var sendEl = document.getElementById('signin-send');
var emailEl = document.getElementById('signin-email');
var msgEl = document.getElementById('signin-msg');
function showSignIn() { if (modal) modal.classList.add('open'); if (emailEl) emailEl.focus(); }
if (cancelEl) cancelEl.addEventListener('click', function() { modal.classList.remove('open'); });
if (modal) modal.addEventListener('click', function(e) { if (e.target === modal) modal.classList.remove('open'); });
if (sendEl) sendEl.addEventListener('click', function() {
var email = emailEl ? emailEl.value.trim() : '';
if (!email) { if (msgEl) msgEl.textContent = 'Please enter your email.'; return; }
sendEl.disabled = true;
if (msgEl) msgEl.textContent = 'Sending...';
sb.auth.signInWithOtp({ email: email, options: { emailRedirectTo: window.location.href } })
.then(function(r) {
sendEl.disabled = false;
if (msgEl) msgEl.textContent = r.error
? (r.error.message || 'Error. Try again.')
: 'Check your email for a sign-in link.';
});
});
if (emailEl) emailEl.addEventListener('keydown', function(e) { if (e.key === 'Enter' && sendEl) sendEl.click(); });
sb.auth.onAuthStateChange(function(event, session) {
token = session ? session.access_token : null;
if (token && modal) modal.classList.remove('open');
loadAll();
});
sb.auth.getSession().then(function(r) {
token = r.data && r.data.session ? r.data.session.access_token : null;
loadAll();
});
})()")
}
+67
View File
@@ -0,0 +1,67 @@
// main.el -- Share page voting, copy-for-platform social sharing.
// Compiled with: elc --target=js --bundle --minify --obfuscate
//
// Required globals: window.NEURON_CFG.id, window.NEURON_CFG.card_url
// Exposed globals: copyForPlatform(platform, btn)
fn main() -> Void {
native_js("(function() {
var cfg = window.NEURON_CFG || {};
var shareId = cfg.id || '';
var cardUrl = cfg.card_url || '';
// Copy-for-platform: format and copy share text for different social platforms
window.copyForPlatform = function(platform, btn) {
var text = '';
var url = window.location.href;
if (platform === 'tiktok') {
text = 'Neuron said something interesting. Watch this. ' + url;
} else if (platform === 'snapchat') {
text = url;
} else {
text = url;
}
navigator.clipboard.writeText(text).then(function() {
if (btn) {
btn.classList.add('copied');
setTimeout(function() { btn.classList.remove('copied'); }, 1500);
}
}).catch(function() {});
};
// Vote buttons on share page
var voteUp = document.getElementById('vote-up');
var voteDown = document.getElementById('vote-down');
var voteScore = document.getElementById('vote-score');
var voted = null;
function updateVoteUI(direction, score) {
if (voteUp) voteUp.classList.toggle('voted-up', direction === 'up');
if (voteDown) voteDown.classList.toggle('voted-down', direction === 'down');
if (voteScore && score != null) voteScore.textContent = score;
}
function castVote(direction) {
var next = voted === direction ? 'none' : direction;
voted = next === 'none' ? null : next;
fetch('/api/vote', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ id: shareId, direction: next })
}).then(function(r) { return r.json(); }).then(function(d) {
if (d.ok) updateVoteUI(next === 'none' ? null : next, d.score);
}).catch(function() {});
}
if (voteUp) voteUp.addEventListener('click', function() { castVote('up'); });
if (voteDown) voteDown.addEventListener('click', function() { castVote('down'); });
// Load initial vote state
if (shareId) {
fetch('/api/vote-state/' + shareId)
.then(function(r) { return r.json(); })
.then(function(d) { updateVoteUI(d.user_vote || null, d.score); })
.catch(function() {});
}
})()")
}
+42
View File
@@ -0,0 +1,42 @@
// marketplace.el -- Marketplace developer interest form submission.
// Compiled with: elc --target=js --bundle --minify --obfuscate
fn main() -> Void {
native_js("(function() {
var form = document.getElementById('dev-form');
if (!form) return;
form.addEventListener('submit', async function(e) {
e.preventDefault();
var msg = document.getElementById('dev-msg');
var btn = form.querySelector('button[type=submit]');
btn.disabled = true;
btn.textContent = 'Sending...';
try {
var r = await fetch('/api/developer-interest', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
name: document.getElementById('dev-name').value,
email: document.getElementById('dev-email').value,
idea: document.getElementById('dev-idea').value
})
});
msg.style.display = 'block';
if (r.ok) {
msg.textContent = 'Got it. Will review it personally and reach out.';
msg.style.color = 'var(--navy)';
form.reset();
} else {
msg.textContent = 'Something went wrong. Email developers@neurontechnologies.ai directly.';
msg.style.color = '#c44';
}
} catch(err) {
msg.style.display = 'block';
msg.textContent = 'Connection error. Email developers@neurontechnologies.ai directly.';
msg.style.color = '#c44';
}
btn.disabled = false;
btn.textContent = 'Send interest →';
});
})()")
}
+56
View File
@@ -0,0 +1,56 @@
// nav.el -- Navigation hamburger menu and Mission dropdown.
// Compiled with: elc --target=js --bundle --minify --obfuscate
fn main() -> Void {
native_js("(function() {
var btn = document.getElementById('nav-hamburger');
var menu = document.getElementById('nav-mobile');
var nav = document.getElementById('nav');
if (!btn || !menu) return;
function close() {
menu.classList.remove('open');
btn.setAttribute('aria-expanded', 'false');
}
function open() {
menu.classList.add('open');
btn.setAttribute('aria-expanded', 'true');
}
function toggle() {
if (menu.classList.contains('open')) { close(); } else { open(); }
}
btn.addEventListener('click', function(e) { e.stopPropagation(); toggle(); });
var ddBtn = document.querySelector('.nav-dropdown-btn');
var dd = document.querySelector('.nav-dropdown');
if (ddBtn && dd) {
ddBtn.addEventListener('click', function(e) {
e.stopPropagation();
var isOpen = dd.classList.contains('open');
dd.classList.toggle('open');
ddBtn.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
});
dd.querySelectorAll('.nav-dropdown-item').forEach(function(a) {
a.addEventListener('click', function() { dd.classList.remove('open'); });
});
document.addEventListener('click', function() { dd.classList.remove('open'); });
}
menu.querySelectorAll('a').forEach(function(a) {
a.addEventListener('click', close);
});
document.addEventListener('click', function(e) {
if (!nav.contains(e.target)) { close(); }
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') { close(); }
});
window.addEventListener('resize', function() {
if (window.innerWidth > 1060) { close(); }
});
})()")
}
+99
View File
@@ -0,0 +1,99 @@
// styles.el -- Landing page JS: nav scroll, scroll-reveal, founding counter polling,
// checkout button routing.
// Compiled with: elc --target=js --bundle --minify --obfuscate
fn main() -> Void {
native_js("(function() {
'use strict';
// Nav scroll effect
var nav = document.getElementById('nav');
if (nav) {
window.addEventListener('scroll', function() {
if (window.scrollY > 40) {
nav.classList.add('scrolled');
} else {
nav.classList.remove('scrolled');
}
}, { passive: true });
}
// Auto-open chat if ?open=chat in URL
if (typeof URLSearchParams !== 'undefined' && new URLSearchParams(window.location.search).get('open') === 'chat') {
setTimeout(function() { if (typeof neuronDemoToggle === 'function') neuronDemoToggle(); }, 600);
}
// Scroll reveal via IntersectionObserver
var revealEls = document.querySelectorAll('.reveal');
if ('IntersectionObserver' in window) {
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target);
}
});
}, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' });
revealEls.forEach(function(el) { observer.observe(el); });
} else {
revealEls.forEach(function(el) { el.classList.add('visible'); });
}
// Founding counter polling
var prevSold = null;
function updateFoundingUI(data) {
var remaining = data.remaining;
var sold = data.sold;
var total = data.total;
var pct = Math.round((sold / total) * 100);
var flash = prevSold !== null && sold > prevSold;
prevSold = sold;
var spotLabel = document.querySelector('.founding-spots-label');
if (spotLabel) spotLabel.textContent = 'Only ' + remaining + ' left';
var spotFill = document.querySelector('.founding-spots-fill');
if (spotFill) spotFill.style.width = pct + '%';
var spotSub = document.querySelector('.founding-spots-sub');
if (spotSub) spotSub.textContent = sold + ' of ' + total + ' claimed';
var bannerCount = document.querySelector('.founding-banner-count');
if (bannerCount) {
bannerCount.textContent = remaining;
if (flash) {
bannerCount.style.color = '#0078D4';
setTimeout(function() { bannerCount.style.color = ''; }, 1200);
}
}
var bannerFill = document.querySelector('.founding-banner-fill');
if (bannerFill) bannerFill.style.width = pct + '%';
}
function pollFoundingCount() {
fetch('/api/founding-count')
.then(function(r) { return r.json(); })
.then(function(data) { updateFoundingUI(data); })
.catch(function() {});
}
pollFoundingCount();
setInterval(pollFoundingCount, 90000);
// Hide chat widget on non-marketing pages
if (window.location.pathname.indexOf('/checkout') === 0 ||
window.location.pathname.indexOf('/account') === 0 ||
window.location.pathname.indexOf('/legal') === 0 ||
window.location.pathname.indexOf('/marketplace/success') === 0) {
var demoBtn = document.getElementById('neuron-demo-btn');
var demoPanel = document.getElementById('neuron-demo-panel');
if (demoBtn) demoBtn.style.display = 'none';
if (demoPanel) demoPanel.style.display = 'none';
}
// Checkout buttons
var checkoutBtns = document.querySelectorAll('[data-checkout]');
checkoutBtns.forEach(function(btn) {
btn.addEventListener('click', function() {
window.location.href = '/checkout?plan=' + btn.getAttribute('data-checkout');
});
});
})()")
}
+42 -8
View File
@@ -341,7 +341,7 @@ body::before{content:'';position:fixed;inset:0;pointer-events:none;z-index:0;bac
<a href=\"https://neurontechnologies.ai\" class=\"cta-btn\">Try Neuron &#8599;</a>
</div>
</div>
<script>window.NEURON_CFG=window.NEURON_CFG||{};window.NEURON_CFG.id=\"" + id + "\";window.NEURON_CFG.card_url=\"" + card_url + "\";</script><script src=\"/assets/js/94727a87c328.js\" defer></script>
<script>window.NEURON_CFG=window.NEURON_CFG||{};window.NEURON_CFG.id=\"" + id + "\";window.NEURON_CFG.card_url=\"" + card_url + "\";</script><script src=\"/js/main.js\" defer></script>
</body>
</html>"
}
@@ -843,6 +843,19 @@ fn handle_request(method: String, path: String, body: String) -> String {
return content
}
// Compiled client-side JS: /js/*
// Served from dist/js/ (compiled by elc --target=js at build time).
// LANDING_ROOT/js maps to the dist/js output directory in the image.
if str_starts_with(path, "/js/") {
let rel: String = str_slice(path, 4, str_len(path))
let abs: String = src_dir + "/js/" + rel
let content: String = read_asset(abs)
if str_eq(content, "") {
return "{\"__status__\":404,\"error\":\"not found\"}"
}
return content
}
// Brand assets: /brand/*
if str_starts_with(path, "/brand/") {
let rel: String = str_slice(path, 7, str_len(path))
@@ -1073,16 +1086,36 @@ fn handle_request(method: String, path: String, body: String) -> String {
if str_eq(msg, "") {
return "{\"error\":\"message required\"}"
}
// Rate limit: 25 requests per uid per hour (stored in process state)
// Rate limit: 10 chats per uid per day (UTC day, keyed by uid).
// State key: "__rl_<uid>" "<count>|<day_number>"
// day_number = unix_timestamp / 86400 (integer UTC day)
// Returns rate_limited JSON with reset_at (next midnight UTC) so
// the frontend can show a real countdown.
let rate_uid: String = json_get(body, "uid")
if !str_eq(rate_uid, "") {
let rate_key: String = "__rate__" + rate_uid
let rate_val: String = state_get(rate_key)
let rate_count: Int = if str_eq(rate_val, "") { 0 } else { str_to_int(rate_val) }
if rate_count >= 25 {
return "{\"response\":\"You've hit the rate limit. Come back in an hour.\"}"
let now_ts: Int = unix_timestamp()
let today_day: Int = now_ts / 86400
let next_reset: Int = (today_day + 1) * 86400
let rl_key: String = "__rl_" + rate_uid
let rl_val: String = state_get(rl_key)
let rl_count: Int = 0
let rl_day: Int = 0
if !str_eq(rl_val, "") {
// format: "count|day"
let parts: [String] = str_split(rl_val, "|")
if native_list_len(parts) >= 2 {
let rl_count = str_to_int(native_list_get(parts, 0))
let rl_day = str_to_int(native_list_get(parts, 1))
}
}
state_set(rate_key, int_to_str(rate_count + 1))
// Reset count if it's a new day
if rl_day != today_day {
let rl_count = 0
}
if rl_count >= 10 {
return "{\"rate_limited\":true,\"reset_at\":" + int_to_str(next_reset) + "}"
}
state_set(rl_key, int_to_str(rl_count + 1) + "|" + int_to_str(today_day))
}
// Turnstile: verify on first message only (tokens are single-use).
// Per-message verification breaks chat flow. Forms get full verification.
@@ -1890,6 +1923,7 @@ println(" POST /api/checkout → Stripe checkout session")
println(" POST /api/webhooks/stripe → Stripe webhook")
println(" GET /marketplace/success → post-purchase success page")
println(" GET /assets/* → static files")
println(" GET /js/* → compiled client-side JS (dist/js/)")
println(" GET /brand/* → brand files")
println(" GET /api/supabase-config → public Supabase config (URL + anon key)")
println("")
+1 -1
View File
@@ -132,7 +132,7 @@ fn marketplace() -> String {
</div>
</div>
<script src=\"/assets/js/ce12d682c9e6.js\" defer></script>
<script src=\"/js/marketplace.js\" defer></script>
</section>
+1 -1
View File
@@ -52,6 +52,6 @@ fn nav() -> String {
</div>
</nav>
<script src=\"/assets/js/529d45d105c9.js\" defer></script>
<script src=\"/js/nav.js\" defer></script>
"
}
+3 -400
View File
@@ -1839,6 +1839,7 @@ fn page_open() -> String {
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-Y1EE43X9RN');
gtag('config', 'AW-18140150015');
</script>
<!-- Open Graph -->
@@ -1967,7 +1968,7 @@ fn page_open() -> String {
fn page_close() -> String {
return "
<script src=\"/assets/js/407e72cd7182.js\" defer></script>
<script src=\"/js/chat-widget.js\" defer></script>
<!-- Neuron Demo Chat Widget -->
<div id=\"neuron-demo-btn\">
@@ -2023,405 +2024,7 @@ fn page_close() -> String {
</div>
</div>
<script>
(function() {
if (typeof marked !== 'undefined') { marked.setOptions({ breaks: true, gfm: true }); }
var TURNSTILE_SITE_KEY = '0x4AAAAAADHAZXyuRb3yD9mr';
var turnstileToken = '';
var turnstileWidgetId = null;
var turnstileVerified = false;
var isOpen = false;
var MAX = 10;
// Share preview modal helpers
// Captures the rendered (marked.js) HTML from the AI bubble and shows a
// preview before publishing. The actual /api/share POST + gallery insert
// only fires when the user clicks Publish in the modal.
var SHARE_CARD_CSS = \"*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}body{font-family:'IBM Plex Sans',system-ui,sans-serif;background:#FAFAF8;color:#0D0D14;padding:1.25rem .75rem;min-height:100vh}.chat-frame{background:#fff;border:1px solid rgba(0,0,0,.09);box-shadow:0 4px 32px rgba(0,0,0,.07),0 1px 4px rgba(0,0,0,.04);padding:1.25rem;display:flex;flex-direction:column;gap:1rem;max-width:560px;margin:0 auto}.chat-row-user{display:flex;flex-direction:row-reverse}.chat-row-ai{display:flex;flex-direction:row;align-items:flex-end;gap:.625rem}.bubble-user{background:#0052A0;color:#fff;border-radius:18px 18px 4px 18px;padding:11px 15px;max-width:78%;font-size:.875rem;line-height:1.55;word-break:break-word}.bubble-ai{background:#FAFAF8;color:#0D0D14;border:1px solid rgba(0,0,0,.07);border-radius:18px 18px 18px 4px;padding:11px 15px;max-width:88%;font-size:.875rem;font-weight:300;line-height:1.65;word-break:break-word;box-shadow:0 2px 6px rgba(0,0,0,.05)}.bubble-ai p{margin:0}.bubble-ai p+p{margin-top:.6rem}.bubble-ai ul,.bubble-ai ol{margin:.5rem 0 .5rem 1.25rem;padding:0}.bubble-ai li+li{margin-top:.25rem}.bubble-ai strong{font-weight:600}.bubble-ai em{font-style:italic}.bubble-ai code{font-family:'IBM Plex Mono','Menlo',monospace;font-size:.8rem;background:rgba(0,0,0,.05);padding:1px 4px;border-radius:3px}.bubble-ai pre{background:rgba(0,0,0,.05);padding:.75rem;border-radius:6px;overflow-x:auto;font-size:.8rem;margin:.5rem 0}.bubble-ai pre code{background:none;padding:0}.bubble-ai blockquote{border-left:3px solid rgba(0,82,160,.3);margin:.5rem 0;padding:.25rem 0 .25rem .75rem;color:#3A3A4A}.bubble-ai h1,.bubble-ai h2,.bubble-ai h3,.bubble-ai h4{font-weight:600;margin:.5rem 0 .25rem}.bubble-ai h1{font-size:1.05rem}.bubble-ai h2{font-size:1rem}.bubble-ai h3{font-size:.95rem}.bubble-ai h4{font-size:.9rem}.bubble-ai a{color:#0052A0;text-decoration:underline}.ai-col{display:flex;flex-direction:column;gap:.25rem}.ai-label{font-size:.6rem;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:#0052A0}.avatar{width:26px;height:26px;border-radius:50%;flex-shrink:0;background:#fff;border:1px solid rgba(0,82,160,.15);display:flex;align-items:center;justify-content:center;font-size:.7rem;color:#0052A0;font-weight:600}\";
function _esc(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;'); }
function _buildPreviewSrcdoc(question, answerHtml) {
return '<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><style>' + SHARE_CARD_CSS + '</style></head><body><div class=\"chat-frame\"><div class=\"chat-row-user\"><div class=\"bubble-user\">' + _esc(question || '(no prior question)') + '</div></div><div class=\"chat-row-ai\"><div class=\"avatar\">N</div><div class=\"ai-col\"><span class=\"ai-label\">Neuron</span><div class=\"bubble-ai\">' + (answerHtml || '') + '</div></div></div></div></body></html>';
}
var _sharePending = null;
function openSharePreview(question, answerHtml, answerPlain, originBtn) {
_sharePending = { question: question, answerHtml: answerHtml, answerPlain: answerPlain, btn: originBtn };
var modal = document.getElementById('neuron-share-preview-modal');
var frame = document.getElementById('neuron-share-preview-frame');
if (!modal || !frame) return;
frame.srcdoc = _buildPreviewSrcdoc(question, answerHtml);
modal.style.display = 'flex';
}
function closeSharePreview() {
var modal = document.getElementById('neuron-share-preview-modal');
if (modal) modal.style.display = 'none';
var frame = document.getElementById('neuron-share-preview-frame');
if (frame) frame.srcdoc = '';
_sharePending = null;
}
async function publishSharePreview() {
if (!_sharePending) return;
var pending = _sharePending;
var publishBtn = document.getElementById('neuron-share-preview-publish');
if (publishBtn) { publishBtn.disabled = true; publishBtn.textContent = 'Publishing...'; }
if (pending.btn) pending.btn.style.opacity = '0.4';
try {
var r = await fetch('/api/share', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
question: pending.question,
answer: pending.answerPlain,
answer_html: pending.answerHtml,
answer_plaintext: pending.answerPlain
})
});
var d = await r.json();
if (d && d.id) {
window.open('/share/' + d.id, '_blank');
}
} catch(e) {}
if (pending.btn) pending.btn.style.opacity = '1';
if (publishBtn) { publishBtn.disabled = false; publishBtn.textContent = 'Publish to gallery'; }
closeSharePreview();
}
// Wire modal buttons once DOM is ready. The modal lives outside the chat
// panel so it works whether the panel is open or closed.
function _wireShareModal() {
var pubBtn = document.getElementById('neuron-share-preview-publish');
var cnlBtn = document.getElementById('neuron-share-preview-cancel');
var clsBtn = document.getElementById('neuron-share-preview-close');
var modal = document.getElementById('neuron-share-preview-modal');
if (pubBtn) pubBtn.addEventListener('click', publishSharePreview);
if (cnlBtn) cnlBtn.addEventListener('click', closeSharePreview);
if (clsBtn) clsBtn.addEventListener('click', closeSharePreview);
if (modal) modal.addEventListener('click', function(ev) { if (ev.target === modal) closeSharePreview(); });
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', _wireShareModal);
else _wireShareModal();
// Persistent session storage - survives page refreshes
function loadSession() {
try {
var s = localStorage.getItem('neuron_demo_session');
return s ? JSON.parse(s) : { messages: [], count: 0, context: '' };
} catch(e) { return { messages: [], count: 0, context: '' }; }
}
function saveSession(session) {
try { localStorage.setItem('neuron_demo_session', JSON.stringify(session)); } catch(e) {}
}
function clearSession() {
try { localStorage.removeItem('neuron_demo_session'); } catch(e) {}
}
function _mg(s) { return s._m || { nodes: [], edges: [] }; }
function _um(s, nn, ne) {
if (!nn || !nn.length) return;
var g = _mg(s), nm = {}, ek = function(e) { return e.from+'->'+e.to; }, em = {};
g.nodes.forEach(function(n) { nm[n.id] = n; });
(nn || []).forEach(function(n) {
if (nm[n.id]) { nm[n.id].w = Math.min(1.0, (nm[n.id].w || 0.5) + 0.08); }
else { nm[n.id] = n; }
});
g.nodes = Object.values(nm);
g.edges.forEach(function(e) { em[ek(e)] = e; });
(ne || []).forEach(function(e) {
var k = ek(e);
if (em[k]) { em[k].weight = Math.min(1.0, (em[k].weight || 0.5) + 0.05); }
else { em[k] = e; }
});
g.edges = Object.values(em);
s._m = g; saveSession(s);
}
function _ra(g, q) {
if (!g || !g.nodes || !g.nodes.length) return [];
var words = q.toLowerCase().split(/\s+/).filter(function(w) { return w.length > 3; });
var sc = {};
g.nodes.forEach(function(n) {
var t = (n.content || '').toLowerCase();
sc[n.id] = words.filter(function(w) { return t.indexOf(w) !== -1; }).length * 0.6 + (n.w || 0.5) * 0.4;
});
(g.edges || []).forEach(function(e) {
if (sc[e.from] > 0.1) sc[e.to] = (sc[e.to] || 0) + sc[e.from] * (e.weight || 0.5) * 0.4;
});
return g.nodes.filter(function(n) { return sc[n.id] > 0.2; })
.sort(function(a,b) { return sc[b.id]-sc[a.id]; }).slice(0,5)
.map(function(n) { return { id: n.id, content: n.content, score: sc[n.id] }; });
}
// ?reset=1 clears the session and reloads clean
if (window.location.search.indexOf('reset=1') !== -1) {
clearSession();
var clean = window.location.pathname;
window.history.replaceState({}, '', clean);
}
var session = loadSession();
// Ensure every user has a stable unique session ID.
if (!session.uid) {
session.uid = 'u' + Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
saveSession(session);
}
var msgCount = session.count || 0;
function updateCountdown() {
var el = document.getElementById('neuron-demo-countdown');
if (!el) return;
var remaining = MAX - msgCount;
el.textContent = remaining + ' question' + (remaining === 1 ? '' : 's') + ' left';
el.style.color = '#ffffff';
el.style.fontWeight = '700';
}
window.neuronDemoReset = function() {
try { localStorage.removeItem('neuron_demo_session'); } catch(e) {}
session = { messages: [], count: 0, context: '' };
msgCount = 0;
var msgs = document.getElementById('neuron-demo-messages');
if (msgs) msgs.innerHTML = '';
var input = document.getElementById('neuron-demo-text');
if (input) { input.disabled = false; input.placeholder = 'Ask me anything...'; }
var btn = document.getElementById('neuron-demo-send');
if (btn) btn.disabled = false;
addMsg('ai', 'Hey. What is on your mind?', true);
};
window.neuronDemoToggle = function() {
isOpen = !isOpen;
var panel = document.getElementById('neuron-demo-panel');
if (panel) panel.style.display = isOpen ? 'flex' : 'none';
var btn = document.getElementById('neuron-demo-btn');
if (btn) btn.style.display = isOpen ? 'none' : '';
var msgs = document.getElementById('neuron-demo-messages');
if (isOpen && turnstileVerified && msgs && msgs.style.display !== 'none' && msgs.children.length === 0) {
if (session.messages && session.messages.length > 0) {
session.messages.forEach(function(m) { addMsg(m.role, m.text, true); });
var remaining = MAX - msgCount;
if (remaining <= 0) {
var input = document.getElementById('neuron-demo-text');
if (input) { input.disabled = true; input.placeholder = 'Interaction limit reached'; }
}
} else if (!session.greeted) {
addMsg('ai', 'Hey. What is on your mind?', true);
session.greeted = true;
try { localStorage.setItem('neuron_demo_session', JSON.stringify(session)); } catch(e) {}
}
}
var input = document.getElementById('neuron-demo-text');
if (isOpen && input && !input.disabled) input.focus();
updateCountdown();
if (isOpen && !turnstileWidgetId && typeof turnstile !== 'undefined') {
var container = document.getElementById('neuron-demo-turnstile');
if (container) {
turnstileWidgetId = turnstile.render(container, {
sitekey: TURNSTILE_SITE_KEY,
size: 'compact',
callback: function(token) {
turnstileToken = token;
turnstileVerified = true;
if (typeof turnstile !== 'undefined' && turnstileWidgetId !== null) {
try { turnstile.remove(turnstileWidgetId); } catch(e) {}
turnstileWidgetId = null;
}
var gate = document.getElementById('neuron-demo-gate');
var msgs = document.getElementById('neuron-demo-messages');
var inputRow = document.getElementById('neuron-demo-input-row');
if (gate) gate.style.display = 'none';
if (msgs) msgs.style.display = 'flex';
if (inputRow) inputRow.style.display = 'flex';
if (session.messages && session.messages.length > 0) {
session.messages.forEach(function(m) { addMsg(m.role, m.text, true); });
var remaining = MAX - msgCount;
if (remaining <= 0) {
var input = document.getElementById('neuron-demo-text');
if (input) { input.disabled = true; input.placeholder = 'Interaction limit reached'; }
}
} else if (!session.greeted) {
addMsg('ai', 'Hey. What is on your mind?', true);
session.greeted = true;
try { localStorage.setItem('neuron_demo_session', JSON.stringify(session)); } catch(e) {}
}
updateCountdown();
var inp = document.getElementById('neuron-demo-text');
if (inp) inp.focus();
},
'expired-callback': function() {
turnstileToken = '';
turnstileVerified = false;
}
});
}
}
};
function addMsg(role, text, skipSave) {
var msgs = document.getElementById('neuron-demo-messages');
if (!msgs) return null;
var el = document.createElement('div');
el.className = 'demo-msg demo-msg-' + role;
var avatar = document.createElement('div');
avatar.className = 'demo-msg-avatar';
if (role === 'ai') {
var img = document.createElement('img');
img.src = '/assets/brand/neuron-brain.png';
img.alt = 'Neuron';
avatar.appendChild(img);
} else {
var svgNS = 'http://www.w3.org/2000/svg';
var svg = document.createElementNS(svgNS, 'svg');
svg.setAttribute('width', '14'); svg.setAttribute('height', '14');
svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', 'currentColor'); svg.setAttribute('stroke-width', '2');
var p1 = document.createElementNS(svgNS, 'path');
p1.setAttribute('d', 'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2');
var c1 = document.createElementNS(svgNS, 'circle');
c1.setAttribute('cx', '12'); c1.setAttribute('cy', '7'); c1.setAttribute('r', '4');
svg.appendChild(p1); svg.appendChild(c1);
avatar.appendChild(svg);
}
var bubble = document.createElement('div');
bubble.className = 'demo-msg-bubble';
if (role === 'ai' && typeof marked !== 'undefined') {
try { bubble.innerHTML = marked.parse(text); } catch(e) { bubble.textContent = text; }
} else {
bubble.textContent = text;
}
if (role === 'ai') {
var bodyWrap = document.createElement('div');
bodyWrap.className = 'demo-msg-ai-body';
bodyWrap.appendChild(bubble);
if (!skipSave) {
var shareBtn = document.createElement('button');
shareBtn.className = 'demo-share-pill';
shareBtn.title = 'Share this response';
shareBtn.textContent = 'Share ';
// Capture rendered HTML on click; preview before publish.
shareBtn.onclick = function() {
var prevUser = '';
if (session.messages) {
for (var i = session.messages.length - 1; i >= 0; i--) {
if (session.messages[i].role === 'user') { prevUser = session.messages[i].text; break; }
}
}
var answerHtml = bubble.innerHTML;
var answerPlain = text;
openSharePreview(prevUser, answerHtml, answerPlain, shareBtn);
};
bodyWrap.appendChild(shareBtn);
}
el.appendChild(avatar);
el.appendChild(bodyWrap);
} else {
el.appendChild(avatar);
el.appendChild(bubble);
}
msgs.appendChild(el);
msgs.scrollTop = msgs.scrollHeight;
if (!skipSave && role !== 'thinking') {
session.messages = session.messages || [];
session.messages.push({ role: role, text: text });
if (session.messages.length > 200) session.messages = session.messages.slice(-200);
saveSession(session);
}
return el;
}
window.neuronDemoSend = async function() {
if (msgCount >= MAX) return;
var input = document.getElementById('neuron-demo-text');
var btn = document.getElementById('neuron-demo-send');
if (!input || btn.disabled) return;
var msg = input.value.trim();
if (!msg) return;
input.value = '';
btn.disabled = true;
addMsg('user', msg);
var thinking = document.createElement('div');
thinking.className = 'demo-msg demo-msg-thinking';
var thAvatar = document.createElement('div');
thAvatar.className = 'demo-msg-avatar';
var thImg = document.createElement('img');
thImg.src = '/assets/brand/neuron-brain.png';
thImg.alt = 'Neuron';
thAvatar.appendChild(thImg);
thinking.appendChild(thAvatar);
var thDots = document.createElement('span');
thDots.className = 'demo-msg-thinking-dots';
thDots.innerHTML = '<span></span><span></span><span></span>';
thinking.appendChild(thDots);
var thMsgsEl = document.getElementById('neuron-demo-messages');
if (thMsgsEl) {
thMsgsEl.appendChild(thinking);
thMsgsEl.scrollTop = thMsgsEl.scrollHeight;
}
if (turnstileVerified && !session._cfSent) { session._cfSent = true; }
try {
var hist = (session.messages || []).slice(-50).filter(function(m){ return m.role !== 'thinking'; }).map(function(m){
return {role: m.role === 'ai' ? 'assistant' : 'user', content: m.text};
});
var activated_nodes = _ra(session._m, msg);
var questionsRemaining = (MAX - msgCount) - 1;
if (questionsRemaining < 0) questionsRemaining = 0;
// 30s frontend timeout surfaces a real error if the soul hangs
// instead of leaving the thinking bubble spinning forever.
var ctrl = new AbortController();
var timeoutId = setTimeout(function() { ctrl.abort(); }, 30000);
var r;
try {
r = await fetch('/api/demo', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
signal: ctrl.signal,
body: JSON.stringify({
message: msg,
history: hist,
cf_token: turnstileVerified && !session._cfSent ? turnstileToken : '',
uid: session.uid || '',
activated_nodes: activated_nodes,
engram_node_count: (session._m && session._m.nodes) ? session._m.nodes.length : 0,
questions_remaining: questionsRemaining,
is_last_question: questionsRemaining === 0
})
});
} finally {
clearTimeout(timeoutId);
}
var d = await r.json();
if (thinking) thinking.remove();
_um(session, d.sn, d.se);
var reply = d.response || d.reply || d.message || '';
var isError = !reply || reply === 'Stepped out for a moment. Try again.';
if (!isError) {
msgCount++;
session.count = msgCount;
saveSession(session);
updateCountdown();
if (msgCount >= MAX && input) {
input.disabled = true;
input.placeholder = 'Interaction limit reached';
}
}
addMsg('ai', reply || 'Stepped out for a moment. Try again.');
} catch(e) {
if (thinking) thinking.remove();
var msg = (e && e.name === 'AbortError')
? 'Took too long to respond try again.'
: 'Stepped out for a moment. Try again.';
addMsg('ai', msg);
}
if (msgCount < MAX && btn) btn.disabled = false;
if (input) input.focus();
};
var inp = document.getElementById('neuron-demo-text');
if (inp) {
inp.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); window.neuronDemoSend(); }
});
}
})();
</script>
<script src=\"/js/styles.js\" defer></script>
</body>
</html>
"