← back to AbramsEgo
feat(sell): landing demo video + fulfillment runbook
fb3f1117e8bed3f9200265104e2ee9d8e7ceb22e · 2026-07-01 17:37:34 -0700 · Steve
50s silent Playwright tour of the :9773 dashboard (norma per-panel pattern,
red-highlight dwell on 11 panels), 1.6MB 720p h264, self-hosted at
/assets/demo/. Embedded in landing features section — muted/loop/poster,
lazy preload with IntersectionObserver autoplay. memos/fulfillment.md is
the manual-first Hosted/$19 + Pro/$49 provisioning + refund runbook.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Files touched
A memos/fulfillment.mdA public/assets/demo/abramsego-tour-poster.jpgA public/assets/demo/abramsego-tour.mp4M public/landing.htmlA scripts/demo-video.js
Diff
commit fb3f1117e8bed3f9200265104e2ee9d8e7ceb22e
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jul 1 17:37:34 2026 -0700
feat(sell): landing demo video + fulfillment runbook
50s silent Playwright tour of the :9773 dashboard (norma per-panel pattern,
red-highlight dwell on 11 panels), 1.6MB 720p h264, self-hosted at
/assets/demo/. Embedded in landing features section — muted/loop/poster,
lazy preload with IntersectionObserver autoplay. memos/fulfillment.md is
the manual-first Hosted/$19 + Pro/$49 provisioning + refund runbook.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
memos/fulfillment.md | 58 ++++++++++++++++++++
public/assets/demo/abramsego-tour-poster.jpg | Bin 0 -> 96530 bytes
public/assets/demo/abramsego-tour.mp4 | Bin 0 -> 1611368 bytes
public/landing.html | 24 +++++++++
scripts/demo-video.js | 78 +++++++++++++++++++++++++++
5 files changed, 160 insertions(+)
diff --git a/memos/fulfillment.md b/memos/fulfillment.md
new file mode 100644
index 0000000..0ab4786
--- /dev/null
+++ b/memos/fulfillment.md
@@ -0,0 +1,58 @@
+# Fulfillment runbook — what happens after a checkout completes
+
+**Status:** TEST mode only (Stripe test keys, no real charges). This is the
+manual-first playbook for when a Hosted/$19 or Pro/$49 checkout completes —
+today in TEST, later live once Steve flips the switch. Provisioning is done
+by Steve by hand; nothing auto-provisions.
+
+## The moment of purchase
+
+1. Customer clicks a pricing CTA on `/landing.html` → `/api/checkout` creates a
+ Stripe Checkout Session (TEST) for the chosen tier → customer pays on
+ Stripe's hosted page.
+2. Stripe calls our webhook (`checkout.session.completed`). The server records
+ the sale to `data/revenue-ledger.jsonl` (engine: `sell`) and it shows up in
+ the dashboard P&L within one refresh.
+3. **Nothing else is automatic.** The customer's email + tier sit in the ledger
+ entry; Steve fulfills manually from there.
+
+## Manual provisioning — Steve's steps (target: within 24h of purchase)
+
+### Hosted — $19/mo
+1. Read the ledger entry (email, tier, Stripe customer id).
+2. Create the customer's instance on Kamatera: copy of the AbramsEgo app on its
+ own port, fresh Basic Auth credentials, empty data dir (their fleet, not ours).
+3. Point a subdomain at it (e.g. `<name>.abramsego.com`) with SSL.
+4. Email the customer from steve@designerwallcoverings.com: their URL, their
+ login, a 3-line quickstart (how to point collectors at their own pm2/ledger).
+
+### Pro — $49/mo
+Everything in Hosted, plus:
+5. Enable the revenue engines on their instance (`/api/revenue/record` open to
+ their key) and the grounded-chat panel wired to their Ollama or ours.
+6. Offer a 30-minute setup call (calendar link in the welcome email).
+
+## What the customer receives
+
+- **Hosted:** a private, Basic-auth-gated dashboard URL watching *their*
+ processes, with the cost ledger, canary board, and P&L panels live; a welcome
+ email with credentials and quickstart.
+- **Pro:** the above + revenue-engine recording, grounded AI chat, and the
+ setup call.
+- **Self-host (free):** no fulfillment — the repo/docs link is on the landing
+ page; they run it themselves.
+
+## Refund path
+
+- Simple rule: **full refund, no questions, within 30 days** — issued by Steve
+ in the Stripe dashboard (Payments → refund).
+- After refund: tear down the instance (stop pm2 process, remove subdomain),
+ append a negative entry to `revenue-ledger.jsonl` so the P&L stays honest.
+- Subscription cancels: customer emails us or uses the Stripe customer portal
+ (once enabled live); instance stays up until the end of the paid period.
+
+## Rails
+
+- Live keys, real charges, public exposure of `/landing.html`, DNS for customer
+ subdomains — all Steve-gated. Until go-live, every "sale" is a TEST-mode
+ rehearsal of this exact runbook.
diff --git a/public/assets/demo/abramsego-tour-poster.jpg b/public/assets/demo/abramsego-tour-poster.jpg
new file mode 100644
index 0000000..ad6db38
Binary files /dev/null and b/public/assets/demo/abramsego-tour-poster.jpg differ
diff --git a/public/assets/demo/abramsego-tour.mp4 b/public/assets/demo/abramsego-tour.mp4
new file mode 100644
index 0000000..bea2e6b
Binary files /dev/null and b/public/assets/demo/abramsego-tour.mp4 differ
diff --git a/public/landing.html b/public/landing.html
index a0bc33f..1844f84 100644
--- a/public/landing.html
+++ b/public/landing.html
@@ -54,6 +54,11 @@
.card .ico{font-size:22px}
.card h3{margin:10px 0 6px;font-size:15px}
.card p{margin:0;color:var(--dim);font-size:13px}
+ /* — demo video — */
+ .demo{margin-top:26px;text-align:center}
+ .demo video{width:100%;max-width:920px;border:1px solid var(--line);border-radius:14px;
+ box-shadow:0 20px 60px -30px #000;display:block;margin:0 auto;background:#0c1420}
+ .demo .cap{color:var(--dim);font-size:11px;margin-top:10px}
/* — pricing — */
.tiers{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:8px}
@media(max-width:860px){.tiers{grid-template-columns:1fr}}
@@ -144,6 +149,25 @@
<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>
+
+ <!-- 50s silent tour of the live dashboard (autoplays muted when scrolled into view) -->
+ <div class="demo">
+ <video id="tour" muted loop playsinline preload="none"
+ poster="/assets/demo/abramsego-tour-poster.jpg"
+ src="/assets/demo/abramsego-tour.mp4"></video>
+ <div class="cap">50-second tour of the live dashboard — P&L, cost trend, fleet, canaries, officers, sessions and grounded chat.</div>
+ </div>
+ <script>
+ (function(){
+ var v = document.getElementById('tour');
+ if (!('IntersectionObserver' in window)) { v.preload = 'metadata'; v.controls = true; return; }
+ new IntersectionObserver(function(es, obs){
+ es.forEach(function(e){
+ if (e.isIntersecting) { v.play().catch(function(){ v.controls = true; }); obs.disconnect(); }
+ });
+ }, { threshold: 0.35 }).observe(v);
+ })();
+ </script>
</section>
<!-- PRICING -->
diff --git a/scripts/demo-video.js b/scripts/demo-video.js
new file mode 100644
index 0000000..a193782
--- /dev/null
+++ b/scripts/demo-video.js
@@ -0,0 +1,78 @@
+#!/usr/bin/env node
+// Capture a ~50s click-through tour of the :9773 dashboard (norma-videos pattern:
+// per-panel dwell with a red highlight box; muted/no-TTS cut for the landing page).
+// Run: NODE_PATH=~/.claude/skills/app-demo-video/node_modules node scripts/demo-video.js
+// Output: tmp/demo-raw/<hash>.webm — converted to mp4 by scripts/demo-video.sh
+const { chromium } = require('playwright');
+const path = require('path');
+
+const BASE = process.env.EGO_URL || 'http://127.0.0.1:9773/';
+const OUT_DIR = path.join(__dirname, '..', 'tmp', 'demo-raw');
+const W = 1280, H = 720;
+
+// h2 text prefixes, in tour order (matched with startsWith on the emoji+title)
+const TOUR = [
+ '💰 Self-Funding P&L',
+ '🔌 Cost to run',
+ '📈 Cost trend',
+ '🛰 Fleet · Kamatera',
+ '🖥 Fleet · Local',
+ '🐤 Canary board',
+ '🏛 Officers',
+ '🏆 Wins',
+ '💬 Live Claude sessions',
+ '🧠 Model usage',
+ '🤖 Ask AbramsEgo',
+];
+const DWELL_MS = 3600; // per-panel hold
+const INTRO_MS = 4500; // top-of-page hold
+
+(async () => {
+ // skill's playwright (1.59.1) expects browser build 1217, which isn't cached;
+ // point at the cached headless-shell build instead of downloading a new one
+ const shell = process.env.HOME + '/Library/Caches/ms-playwright/chromium_headless_shell-1228/chrome-headless-shell-mac-arm64/chrome-headless-shell';
+ const browser = await chromium.launch({ executablePath: shell });
+ const ctx = await browser.newContext({
+ viewport: { width: W, height: H },
+ recordVideo: { dir: OUT_DIR, size: { width: W, height: H } },
+ httpCredentials: { username: 'admin', password: 'DW2024!' },
+ });
+ const page = await ctx.newPage();
+ await page.goto(BASE, { waitUntil: 'networkidle' }).catch(() => {});
+ // let the snapshot panels populate
+ await page.waitForTimeout(3000);
+
+ await page.addStyleTag({ content: `
+ .demo-hl { outline: 3px solid #ff4d4d !important; outline-offset: 4px;
+ border-radius: 8px; transition: outline-color .3s; }
+ html { scroll-behavior: smooth; }
+ `});
+
+ await page.waitForTimeout(INTRO_MS);
+
+ for (const title of TOUR) {
+ const hit = await page.evaluate((t) => {
+ const h = [...document.querySelectorAll('h2')]
+ .find(el => el.textContent.trim().startsWith(t.slice(0, 6)) && el.textContent.includes(t.slice(2).trim().split(' ')[0]));
+ if (!h) return false;
+ const panel = h.closest('section,div.panel,article') || h.parentElement;
+ document.querySelectorAll('.demo-hl').forEach(e => e.classList.remove('demo-hl'));
+ panel.classList.add('demo-hl');
+ panel.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ return true;
+ }, title);
+ if (!hit) console.error('panel not found:', title);
+ await page.waitForTimeout(DWELL_MS);
+ }
+
+ // close on the top metrics bar
+ await page.evaluate(() => {
+ document.querySelectorAll('.demo-hl').forEach(e => e.classList.remove('demo-hl'));
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ });
+ await page.waitForTimeout(3000);
+
+ await ctx.close();
+ await browser.close();
+ console.log('webm written to', OUT_DIR);
+})();
← 2261b68 queue: task 34 done — landing CTAs + PUBLIC_LANDING flag lan
·
back to AbramsEgo
·
queue: task 35 done — landing demo video + fulfillment runbo 3c094d7 →