[object Object]

← back to AbramsEgo

feat: local sell-it landing + pricing + waitlist (publish gated)

0e2c42bbea779c39ced51f228d46bda049f314fa · 2026-07-01 11:17:00 -0700 · Steve (AbramsEgo)

- public/landing.html: dark-theme marketing page (reuses index.html CSS vars) —
  hero 'the dashboard that pays for itself', self-funding P&L angle, 6-feature
  grid, 3-tier pricing ($0 self-host / $19 hosted / $49 pro), waitlist capture.
  Proof-strip pulls REAL /api/data numbers (not mocked).
- server.js: GET /landing (auth-gated) + POST /api/waitlist -> data/waitlist.jsonl
  with email validation; both registered before static middleware.
- .gitignore: exclude data/waitlist.jsonl + data/revenue-ledger.jsonl (PII).
- memos/: go-live memo (Stripe/public/checkout/fulfillment) with officer
  APPROVE/REVISE/BLOCK blocks — queue-dir write was harness-blocked, kept in-repo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files touched

Diff

commit 0e2c42bbea779c39ced51f228d46bda049f314fa
Author: Steve (AbramsEgo) <steve@designerwallcoverings.com>
Date:   Wed Jul 1 11:17:00 2026 -0700

    feat: local sell-it landing + pricing + waitlist (publish gated)
    
    - public/landing.html: dark-theme marketing page (reuses index.html CSS vars) —
      hero 'the dashboard that pays for itself', self-funding P&L angle, 6-feature
      grid, 3-tier pricing ($0 self-host / $19 hosted / $49 pro), waitlist capture.
      Proof-strip pulls REAL /api/data numbers (not mocked).
    - server.js: GET /landing (auth-gated) + POST /api/waitlist -> data/waitlist.jsonl
      with email validation; both registered before static middleware.
    - .gitignore: exclude data/waitlist.jsonl + data/revenue-ledger.jsonl (PII).
    - memos/: go-live memo (Stripe/public/checkout/fulfillment) with officer
      APPROVE/REVISE/BLOCK blocks — queue-dir write was harness-blocked, kept in-repo.
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
 .gitignore                                     |   2 +
 build-queue/{tasks => done}/04-sell-landing.md |   0
 memos/070126A-abramsego-sellit-golive.md       |  52 +++++
 public/landing.html                            | 261 +++++++++++++++++++++++++
 server.js                                      |  27 +++
 5 files changed, 342 insertions(+)

diff --git a/.gitignore b/.gitignore
index 8a1f5c2..424e29b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,5 @@ data/kamatera-fleet.json
 
 # ephemeral singleton lock (never commit)
 build-queue/.loop.lock/
+data/waitlist.jsonl
+data/revenue-ledger.jsonl
diff --git a/build-queue/tasks/04-sell-landing.md b/build-queue/done/04-sell-landing.md
similarity index 100%
rename from build-queue/tasks/04-sell-landing.md
rename to build-queue/done/04-sell-landing.md
diff --git a/memos/070126A-abramsego-sellit-golive.md b/memos/070126A-abramsego-sellit-golive.md
new file mode 100644
index 0000000..8918026
--- /dev/null
+++ b/memos/070126A-abramsego-sellit-golive.md
@@ -0,0 +1,52 @@
+# GATED — Take "Sell AbramsEgo" from local preview to a real, paid product
+
+> **NOTE:** This memo was meant for `~/.claude/yolo-queue/pending-approval/070126A-abramsego-sellit-golive.md`,
+> but the harness blocks autonomous writes to that sensitive path. It lives here in-repo instead.
+> Steve: copy it into the approval queue, or just act on it here.
+
+**Created:** 2026-07-01 · **Owner:** vp-special-projects (product-builder side) · **Repo:** `~/Projects/AbramsEgo/`
+
+## Where it stands (DONE — local, non-gated, committed)
+The sell-it surface is built and running behind Basic Auth on pm2 `abramsego` (:9773):
+- `public/landing.html` — polished dark-theme marketing page: hero ("the dashboard that pays for itself"), the self-funding P&L angle, 6-feature grid, **3-tier pricing ($0 self-host / $19/mo hosted / $49/mo Pro)**, and a waitlist capture. The proof-strip pulls REAL numbers from `/api/data` (fleet count, 30d cost, self-funding %, canary count) so it isn't mocked.
+- `GET /landing` route (still auth-gated) + `POST /api/waitlist` → appends to `data/waitlist.jsonl` (gitignored — it's PII).
+- **No Stripe, no public exposure, no charge, no domain.** Nothing customer-facing yet.
+
+Verified: `/landing` returns 200 with auth / 401 without; waitlist POST validates email, appends a row, rejects bad input.
+
+## GATED — the steps to actually sell (each needs Steve: APPROVE / REVISE / BLOCK)
+
+### 1. Stripe product + prices
+Create in Stripe: one **Product "AbramsEgo Hosted"** with two recurring prices — **$19/mo** and **$49/mo (Pro)** — and treat **$0 Self-host** as a no-charge tier (template download / repo access, no Stripe object). Capture the price IDs into the secrets registry (`STRIPE_PRICE_HOSTED`, `STRIPE_PRICE_PRO`) via the `secrets` skill.
+- Cost to set up: **$0** (Stripe fees are per-transaction, 2.9% + 30¢, on real sales only).
+- Why gated: creating live Stripe pricing = money/identity + a pricing decision that's yours.
+- **Decision for Steve:** confirm the $19 / $49 numbers; monthly-only or add annual? Which Stripe account (DW vs a new AbramsEgo account)?
+- Officer rec: **APPROVE** (with final prices confirmed) — [ ] APPROVE  [ ] REVISE  [ ] BLOCK
+
+### 2. Public, unauthenticated landing
+Serve `/landing` (and only `/landing` + `/api/waitlist`) WITHOUT Basic Auth so real prospects can see it, while the dashboard itself (`/`, `/api/*`) stays gated. Requires a small middleware change (allowlist `/landing` + `/api/waitlist` like `/api/healthz` already is) and a public host to be reachable.
+- Why gated: exposing anything to the open internet is customer-facing + indexable.
+- **Decision for Steve:** host on Kamatera behind nginx at a subdomain, or keep it Mac2-local via a tunnel? Indexed (SEO) or unlisted at first?
+- Officer rec: **REVISE** — soft launch: public landing at an unlisted subdomain, `noindex`, waitlist-only, no checkout yet. [ ] APPROVE  [ ] REVISE  [ ] BLOCK
+
+### 3. Checkout
+Wire the pricing CTAs to **Stripe Checkout** (hosted payment page) or Payment Links — cheapest correct path is a Stripe **Payment Link** per price (no server code, no card handling on our box). Success/cancel URLs return to `/landing`.
+- Cost: **$0** to wire; Stripe's per-sale fee only.
+- Why gated: takes real payment = money. Needs #1 first.
+- **Decision for Steve:** Payment Links (zero-code, fastest) vs a `/api/checkout` session endpoint (more control, more surface)? Recommend Payment Links to start.
+- Officer rec: **APPROVE** (Payment Links, after #1) — [ ] APPROVE  [ ] REVISE  [ ] BLOCK
+
+### 4. Fulfillment
+Define what a paying customer gets and automate delivery:
+- **Self-host ($0):** access to the template (private repo invite or a zip download link).
+- **Hosted ($19) / Pro ($49):** provision a hosted instance (pm2 process + subdomain per customer, or a shared multi-tenant instance) and email credentials. On a Stripe `checkout.session.completed` webhook → create the account + send the welcome email via George.
+- Why gated: sends real email to real customers + provisions infra + spend (hosting). Needs #1–#3.
+- **Decision for Steve:** manual fulfillment for the first N customers (I email you each new paid signup, you provision), or build the webhook→provision automation up front? Recommend **manual-first** — validate demand before automating.
+- Officer rec: **REVISE** — manual fulfillment for launch, automate once there's real volume. [ ] APPROVE  [ ] REVISE  [ ] BLOCK
+
+## Suggested sequence
+Soft launch first: **#2 (public waitlist-only landing, noindex)** to gauge interest with zero payment risk. Only once there's a real waitlist do #1 → #3 → #4. Every irreversible/money step stays behind demand you can see in `data/waitlist.jsonl`.
+
+## Cost summary
+- Build so far: **$0 (local)**.
+- Go-live: **$0 setup** across Stripe + landing + checkout; ongoing = Stripe 2.9%+30¢ per real sale + whatever hosting the Hosted/Pro tiers consume (only if sold).
diff --git a/public/landing.html b/public/landing.html
new file mode 100644
index 0000000..a53a67b
--- /dev/null
+++ b/public/landing.html
@@ -0,0 +1,261 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<title>AbramsEgo · the dashboard that pays for itself</title>
+<meta name="description" content="A self-hostable AI-agent command center with a self-funding P&L — fleet monitoring, canaries, token+energy cost accounting, and a grounded AI chat. A dashboard that pays for itself." />
+<style>
+  /* — reuse the command-center dark theme + CSS vars — */
+  :root{
+    --bg:#0a0e14; --panel:#111823; --panel2:#0d141d; --line:#1e2a3a; --ink:#dbe6f3;
+    --dim:#7d8fa6; --accent:#4d9fff; --good:#3fd18b; --warn:#ffb454; --bad:#ff5d6c;
+    --gold:#ffd166; --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
+  }
+  *{box-sizing:border-box}
+  body{margin:0;background:radial-gradient(1200px 600px at 70% -10%,#12203300,#0a0e14),var(--bg);
+    color:var(--ink);font:14px/1.55 var(--mono);-webkit-font-smoothing:antialiased}
+  a{color:var(--accent);text-decoration:none}
+  header{display:flex;align-items:center;gap:14px;padding:12px 20px;border-bottom:1px solid var(--line);
+    position:sticky;top:0;background:rgba(10,14,20,.9);backdrop-filter:blur(8px);z-index:5}
+  .logo{font-weight:700;letter-spacing:.5px;font-size:16px}
+  .logo b{color:var(--accent)}
+  .nav{margin-left:auto;display:flex;gap:18px;align-items:center;font-size:12px}
+  .nav a{color:var(--dim)}.nav a:hover{color:var(--ink)}
+  .btn{display:inline-block;background:var(--accent);color:#04101f;font-weight:700;border:0;
+    border-radius:9px;padding:11px 18px;cursor:pointer;font-family:var(--mono);font-size:13px;
+    letter-spacing:.3px;transition:transform .08s ease,filter .12s ease}
+  .btn:hover{filter:brightness(1.08)}.btn:active{transform:translateY(1px)}
+  .btn.ghost{background:transparent;color:var(--ink);border:1px solid var(--line)}
+  .btn.gold{background:var(--gold);color:#241a00}
+  main{max-width:1120px;margin:0 auto;padding:0 20px 64px}
+  section{padding:52px 0;border-bottom:1px solid #ffffff08}
+  .kicker{font-size:11px;letter-spacing:2px;text-transform:uppercase;color:var(--accent);margin-bottom:14px}
+  h1{font-size:clamp(30px,6vw,52px);line-height:1.08;margin:0 0 18px;letter-spacing:-.5px;font-weight:800}
+  h1 .hl{color:var(--gold)}
+  .lede{font-size:17px;color:var(--dim);max-width:640px;margin:0 0 26px}
+  .hero-cta{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
+  .hero-note{font-size:11px;color:var(--dim);margin-top:14px}
+  h2{font-size:24px;margin:0 0 8px;letter-spacing:-.3px}
+  .sub{color:var(--dim);margin:0 0 28px;font-size:14px}
+  /* — self-funding proof strip — */
+  .proof{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-top:34px}
+  @media(max-width:760px){.proof{grid-template-columns:repeat(2,1fr)}}
+  .stat{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);
+    border-radius:12px;padding:16px}
+  .stat .k{font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.6px}
+  .stat .v{font-size:26px;font-weight:800;margin-top:6px}
+  .stat .n{font-size:11px;color:var(--dim);margin-top:4px}
+  /* — features — */
+  .grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
+  @media(max-width:860px){.grid{grid-template-columns:1fr}}
+  .card{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);
+    border-radius:12px;padding:18px}
+  .card .ico{font-size:22px}
+  .card h3{margin:10px 0 6px;font-size:15px}
+  .card p{margin:0;color:var(--dim);font-size:13px}
+  /* — pricing — */
+  .tiers{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:8px}
+  @media(max-width:860px){.tiers{grid-template-columns:1fr}}
+  .tier{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);
+    border-radius:14px;padding:22px;display:flex;flex-direction:column}
+  .tier.feat{border-color:#4d9fff66;box-shadow:0 0 0 1px #4d9fff22, 0 10px 40px -20px #4d9fff55}
+  .tier .badge{align-self:flex-start;font-size:10px;letter-spacing:1px;text-transform:uppercase;
+    color:var(--accent);border:1px solid #4d9fff44;background:#4d9fff0f;border-radius:20px;padding:3px 9px;margin-bottom:12px}
+  .tier .name{font-size:16px;font-weight:700}
+  .tier .price{font-size:34px;font-weight:800;margin:8px 0 2px}
+  .tier .price small{font-size:13px;font-weight:600;color:var(--dim)}
+  .tier .for{color:var(--dim);font-size:12px;margin-bottom:16px}
+  .tier ul{list-style:none;padding:0;margin:0 0 20px;flex:1}
+  .tier li{padding:6px 0;border-bottom:1px dashed #ffffff08;font-size:13px;color:var(--ink)}
+  .tier li:before{content:"✓";color:var(--good);margin-right:8px}
+  .tier li.no{color:var(--dim)}.tier li.no:before{content:"—";color:var(--dim)}
+  .pricing-note{text-align:center;color:var(--dim);font-size:11px;margin-top:16px}
+  /* — waitlist — */
+  .wl{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);
+    border-radius:14px;padding:26px;max-width:560px;margin:0 auto;text-align:center}
+  .wl form{display:flex;gap:10px;margin-top:16px}
+  @media(max-width:520px){.wl form{flex-direction:column}}
+  .wl input{flex:1;background:#0c1420;border:1px solid var(--line);color:var(--ink);
+    border-radius:9px;padding:12px 14px;font-family:var(--mono);font-size:14px}
+  .wl input:focus{outline:none;border-color:var(--accent)}
+  .wl .msg{margin-top:12px;font-size:12px;min-height:16px}
+  .wl .msg.ok{color:var(--good)}.wl .msg.err{color:var(--bad)}
+  footer{max-width:1120px;margin:0 auto;padding:26px 20px;color:var(--dim);font-size:11px;
+    display:flex;gap:14px;flex-wrap:wrap;align-items:center}
+  .tag{font-size:10px;color:var(--warn);border:1px solid #ffb45444;background:#ffb4540f;
+    border-radius:20px;padding:3px 9px}
+</style>
+</head>
+<body>
+<header>
+  <div class="logo"><b>Abrams</b>Ego</div>
+  <nav class="nav">
+    <a href="#features">Features</a>
+    <a href="#pricing">Pricing</a>
+    <a href="/index.html">Live demo →</a>
+    <a class="btn ghost" href="#waitlist">Join waitlist</a>
+  </nav>
+</header>
+
+<main>
+  <!-- HERO -->
+  <section id="hero">
+    <div class="kicker">Self-hostable AI-agent command center</div>
+    <h1>The dashboard that <span class="hl">pays for itself.</span></h1>
+    <p class="lede">
+      AbramsEgo watches your whole agent fleet — local + cloud — and keeps its own
+      books. It accounts for every token and watt it burns, tracks the revenue your
+      agents earn, and shows you one number: <b>how much of its own running cost it
+      covers.</b> Most dashboards are pure cost. This one has a P&amp;L.
+    </p>
+    <div class="hero-cta">
+      <a class="btn gold" href="#waitlist">Get early access</a>
+      <a class="btn ghost" href="/index.html">See the live dashboard →</a>
+    </div>
+    <div class="hero-note">Read-only · Basic-auth gated · runs on your box · your data never leaves it.</div>
+
+    <!-- self-funding proof strip -->
+    <div class="proof">
+      <div class="stat"><div class="k">Fleet watched</div><div class="v" id="s-fleet">—</div><div class="n">local + Kamatera procs</div></div>
+      <div class="stat"><div class="k">Cost accounted · 30d</div><div class="v gold" id="s-cost">—</div><div class="n">tokens + energy est</div></div>
+      <div class="stat"><div class="k">Self-funding today</div><div class="v" id="s-pct">—</div><div class="n">of run cost covered</div></div>
+      <div class="stat"><div class="k">Canaries live</div><div class="v" id="s-can">—</div><div class="n">health heartbeats</div></div>
+    </div>
+  </section>
+
+  <!-- FEATURES -->
+  <section id="features">
+    <div class="kicker">What's inside</div>
+    <h2>Everything you need to run agents like a business</h2>
+    <p class="sub">One page, zero external dependencies, deployed behind your own auth.</p>
+    <div class="grid">
+      <div class="card"><div class="ico">🛰</div><h3>Unified fleet view</h3>
+        <p>Local pm2 + remote Kamatera processes in one board — up/down, restarts, memory, CPU. Stale-while-revalidate so it's always instant.</p></div>
+      <div class="card"><div class="ico">🐤</div><h3>Canary board</h3>
+        <p>Every health heartbeat your agents write gets surfaced with a verdict + age, so a silently-dead job can't hide for twelve days.</p></div>
+      <div class="card"><div class="ico">💰</div><h3>Self-funding P&amp;L</h3>
+        <p>Token cost <i>and</i> energy cost, per day and per window, against real dated income — with a break-even target and a live self-funding %.</p></div>
+      <div class="card"><div class="ico">📈</div><h3>Cost trend + per-model</h3>
+        <p>7-day daily cost trend and a 30-day per-model breakdown, straight from your cost ledger. Know what's actually expensive.</p></div>
+      <div class="card"><div class="ico">🧠</div><h3>Model + session usage</h3>
+        <p>Per-model calls, tokens and spend, plus active Claude sessions and the most recent sub-agent runs — each stamped with created date + time.</p></div>
+      <div class="card"><div class="ico">🤖</div><h3>Grounded AI chat</h3>
+        <p>Ask "is it paying for itself?" and get an answer grounded in the live snapshot — via your local Ollama, so the chat itself costs $0.</p></div>
+    </div>
+  </section>
+
+  <!-- PRICING -->
+  <section id="pricing">
+    <div class="kicker">Pricing</div>
+    <h2>Start free. Pay when you want it hosted.</h2>
+    <p class="sub">The full dashboard is yours to self-host at no cost. Paid tiers are for when you'd rather we run it.</p>
+    <div class="tiers">
+      <div class="tier">
+        <div class="name">Self-host</div>
+        <div class="price">$0</div>
+        <div class="for">forever · your box, your data</div>
+        <ul>
+          <li>Full command-center dashboard</li>
+          <li>Fleet + canaries + P&amp;L</li>
+          <li>Local Ollama AI chat</li>
+          <li>Basic-auth gate included</li>
+          <li class="no">You host &amp; update it</li>
+          <li class="no">Community support</li>
+        </ul>
+        <a class="btn ghost" href="#waitlist">Get the template</a>
+      </div>
+      <div class="tier feat">
+        <div class="badge">Most popular</div>
+        <div class="name">Hosted</div>
+        <div class="price">$19<small>/mo</small></div>
+        <div class="for">we run it · you just log in</div>
+        <ul>
+          <li>Everything in Self-host</li>
+          <li>Managed hosting + updates</li>
+          <li>Daily snapshot backups</li>
+          <li>Email alerts on canary FAIL</li>
+          <li>Hosted uptime for the dashboard</li>
+          <li class="no">Single fleet</li>
+        </ul>
+        <a class="btn" href="#waitlist">Join waitlist</a>
+      </div>
+      <div class="tier">
+        <div class="name">Pro</div>
+        <div class="price">$49<small>/mo</small></div>
+        <div class="for">multi-fleet · teams</div>
+        <ul>
+          <li>Everything in Hosted</li>
+          <li>Multiple fleets / machines</li>
+          <li>Team logins &amp; roles</li>
+          <li>Revenue engine integrations</li>
+          <li>Priority support</li>
+          <li>Custom cost-ledger sources</li>
+        </ul>
+        <a class="btn ghost" href="#waitlist">Talk to us</a>
+      </div>
+    </div>
+    <p class="pricing-note">Prices indicative · billing not yet live — join the waitlist and you'll be first to know when checkout opens.</p>
+  </section>
+
+  <!-- WAITLIST -->
+  <section id="waitlist">
+    <div class="wl">
+      <div class="kicker" style="margin-bottom:8px">Early access</div>
+      <h2 style="margin-bottom:6px">Be first in line</h2>
+      <p class="sub" style="margin:0">Drop your email and we'll reach out the moment hosted plans go live. No spam — one launch note.</p>
+      <form id="wl-form">
+        <input id="wl-email" type="email" required autocomplete="email" placeholder="you@company.com" />
+        <button class="btn gold" type="submit">Notify me</button>
+      </form>
+      <div class="msg" id="wl-msg"></div>
+    </div>
+  </section>
+</main>
+
+<footer>
+  <span><b style="color:var(--ink)">AbramsEgo</b> — self-funding AI-agent command center.</span>
+  <span class="tag">Preview · publish gated</span>
+  <span style="margin-left:auto"><a href="/index.html">Live dashboard →</a></span>
+</footer>
+
+<script>
+const $ = (id)=>document.getElementById(id);
+const usd = (n)=> n==null? '—' : '$'+Number(n).toFixed(n<1?4:2);
+
+// Pull real numbers from the live snapshot so the landing is honest, not mocked.
+(async function fillProof(){
+  try{
+    const d = await (await fetch('/api/data')).json();
+    if(!d || !d.builtAt) return;
+    const lf=d.localFleet||{}, k=d.kamatera||{};
+    const totalFleet=(lf.total||0)+(k.total||0);
+    $('s-fleet').textContent = totalFleet || '—';
+    const c30=(d.cost&&d.cost.month)||{};
+    $('s-cost').textContent = usd(c30.total);
+    const pct=(d.pnl&&d.pnl.today&&d.pnl.today.selfFundingPct);
+    const el=$('s-pct'); el.textContent=(pct==null?'0':pct)+'%';
+    el.className='v '+((pct||0)>=100?'good':(pct||0)>0?'warn':'gold');
+    const cans=(d.canaries&&d.canaries.count)||0;
+    $('s-can').textContent = cans || '—';
+  }catch(e){ /* snapshot not ready — leave dashes */ }
+})();
+
+// Waitlist capture → POST /api/waitlist
+$('wl-form').addEventListener('submit', async (e)=>{
+  e.preventDefault();
+  const email=$('wl-email').value.trim();
+  const msg=$('wl-msg');
+  if(!email){ msg.className='msg err'; msg.textContent='Enter an email.'; return; }
+  msg.className='msg'; msg.textContent='…adding you';
+  try{
+    const r = await fetch('/api/waitlist',{method:'POST',headers:{'Content-Type':'application/json'},
+      body:JSON.stringify({email, tier:'hosted', source:'landing'})});
+    const j = await r.json();
+    if(r.ok && j.ok){ msg.className='msg ok'; msg.textContent='You’re on the list — we’ll be in touch. 🎉'; $('wl-form').reset(); }
+    else { msg.className='msg err'; msg.textContent=j.error||'Could not add you — try again.'; }
+  }catch(err){ msg.className='msg err'; msg.textContent='Network error — try again.'; }
+});
+</script>
+</body>
+</html>
diff --git a/server.js b/server.js
index f4f751b..aad33c4 100644
--- a/server.js
+++ b/server.js
@@ -596,6 +596,33 @@ app.post('/api/chat', async (req, res) => {
   res.json({ answer, engine: 'local-template', cost_usd: 0 });
 });
 
+// --- SELL-IT landing (local, publish-gated) --------------------------------
+// Explicit route so /landing serves the marketing page even though it's still
+// behind the Basic-Auth middleware above. Going public/unauth is a gated step.
+const LANDING_FILE = path.join(__dirname, 'public', 'landing.html');
+app.get('/landing', (req, res) => res.sendFile(LANDING_FILE));
+
+// Waitlist email capture from the landing page → appends to data/waitlist.jsonl.
+// This is NOT a money mechanism — it only collects interest; no Stripe, no
+// charge, no send. One row per signup; basic email validation + dedupe-friendly.
+const WAITLIST_FILE = path.join(DATA_DIR, 'waitlist.jsonl');
+const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+app.post('/api/waitlist', (req, res) => {
+  const b = req.body || {};
+  const email = (b.email || '').toString().trim().toLowerCase().slice(0, 254);
+  if (!EMAIL_RE.test(email)) return res.status(400).json({ error: 'valid email required' });
+  const row = {
+    ts: new Date().toISOString(),
+    email,
+    tier: (b.tier || '').toString().slice(0, 40),
+    source: (b.source || 'landing').toString().slice(0, 60),
+    ua: (req.headers['user-agent'] || '').toString().slice(0, 200),
+  };
+  try { fs.appendFileSync(WAITLIST_FILE, JSON.stringify(row) + '\n'); }
+  catch (e) { return res.status(500).json({ error: 'write failed' }); }
+  res.json({ ok: true, added: { email: row.email, ts: row.ts } });
+});
+
 app.use(express.static(path.join(__dirname, 'public'), { extensions: ['html'] }));
 
 app.listen(PORT, '127.0.0.1', () => {

← ef6ad60 auto-save: 2026-07-01T11:11:52 (2 files) — build-queue/tasks  ·  back to AbramsEgo  ·  feat(chat): local-ollama AI chat panel ($0, cap-relieving) 672e1bc →