← back to Abrams
v0.8: competitors + info-hub pages
43ae8ef81d64162de22901a98bea12b00237caf7 · 2026-05-14 09:30:31 -0700 · Steve Abrams
- /competitors renders product-strategist 17-competitor + 8-vector analysis via inline md2html
- /api/competitors serves raw md
- press-kit downloads expanded: competitors.md + info-hub.json (5 assets total)
- corner-nav drawer: Competitors link
- nudges full feature surface: 8 pages (panel · thesis · ideas · trademark · mag-20 · press · hub · competitors · wall · chat)
Files touched
M data/build-log.jsonlA public/competitors.htmlM public/js/corner-nav.jsM public/press.htmlM server.js
Diff
commit 43ae8ef81d64162de22901a98bea12b00237caf7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu May 14 09:30:31 2026 -0700
v0.8: competitors + info-hub pages
- /competitors renders product-strategist 17-competitor + 8-vector analysis via inline md2html
- /api/competitors serves raw md
- press-kit downloads expanded: competitors.md + info-hub.json (5 assets total)
- corner-nav drawer: Competitors link
- nudges full feature surface: 8 pages (panel · thesis · ideas · trademark · mag-20 · press · hub · competitors · wall · chat)
---
data/build-log.jsonl | 1 +
public/competitors.html | 87 +++++++++++++++++++++++++++++++++++++++++++++++++
public/js/corner-nav.js | 1 +
public/press.html | 8 +++++
server.js | 1 +
5 files changed, 98 insertions(+)
diff --git a/data/build-log.jsonl b/data/build-log.jsonl
index 0f59bb2d..6521c8c7 100644
--- a/data/build-log.jsonl
+++ b/data/build-log.jsonl
@@ -58,3 +58,4 @@
{"ts":"2026-05-14T16:25:17.599Z","type":"counters","count":7}
{"ts":"2026-05-14T16:25:33.168Z","type":"server-up","port":9898,"bind":"0.0.0.0"}
{"ts":"2026-05-14T16:28:29.144Z","type":"server-up","port":9898,"bind":"0.0.0.0"}
+{"ts":"2026-05-14T16:30:05.090Z","type":"server-up","port":9898,"bind":"0.0.0.0"}
diff --git a/public/competitors.html b/public/competitors.html
new file mode 100644
index 00000000..00a1dcc9
--- /dev/null
+++ b/public/competitors.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<title>Abrams — Competitive Landscape</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;1,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
+<link rel="stylesheet" href="/static/css/panel.css" />
+<style>
+ .container { max-width: 980px; margin: 0 auto; padding: 110px 32px 80px; }
+ .md h1 { font-family: var(--serif); font-size: 48px; font-weight: 400; margin: 0 0 8px; }
+ .md h2 { font-family: var(--serif); font-size: 30px; font-weight: 400; margin: 40px 0 16px; padding-bottom: 8px; border-bottom: 1px solid var(--hairline); }
+ .md h3 { font-family: var(--serif); font-size: 22px; font-weight: 500; margin: 28px 0 8px; color: var(--accent); }
+ .md p { color: var(--fg); font-size: 15px; line-height: 1.75; margin: 12px 0; }
+ .md ul, .md ol { padding-left: 22px; color: var(--fg); }
+ .md li { font-size: 14px; line-height: 1.7; padding: 3px 0; }
+ .md strong { color: var(--fg); font-weight: 600; }
+ .md hr { border: 0; border-top: 1px solid var(--hairline); margin: 32px 0; }
+ .md code { background: #000; color: var(--accent); padding: 2px 6px; font-family: var(--mono); font-size: 12px; }
+ .md a { color: var(--accent); }
+ .md blockquote { border-left: 3px solid var(--accent); padding: 8px 22px; margin: 16px 0; background: var(--bg-2); font-style: italic; color: var(--fg-2); }
+ .md table { width: 100%; border-collapse: collapse; margin: 16px 0; }
+ .md table th, .md table td { padding: 8px 12px; border-bottom: 1px solid var(--hairline); text-align: left; font-size: 13px; }
+ .md table th { color: var(--accent); font-family: var(--mono); font-size: 11px; letter-spacing: .15em; text-transform: uppercase; }
+</style>
+</head>
+<body>
+
+<div class="chrome">
+ <a class="logo" href="/">ABRAMS<span class="dot">.</span></a>
+ <div class="nav-burger" role="button" aria-label="open menu">
+ <span class="bars"><span></span><span></span><span></span></span>
+ <span class="label">MENU</span>
+ </div>
+</div>
+
+<div class="container">
+ <div id="md" class="md" style="color:var(--fg-3);">loading competitive analysis…</div>
+ <p style="color:var(--fg-3);font-family:var(--mono);font-size:11px;letter-spacing:.15em;margin-top:36px;">
+ Generated by product-strategist subagent · download as <a href="/api/competitors" download="competitors.md">competitors.md</a>
+ </p>
+</div>
+
+<footer>© 2026 Steven Abrams</footer>
+
+<script src="/static/js/corner-nav.js"></script>
+<script src="/static/js/gamify.js"></script>
+<script>
+// Minimal markdown-to-HTML — handles h1/h2/h3, lists, bold, links, paragraphs, hr
+function md2html(src) {
+ const lines = src.split('\n');
+ let html = '', inUl = false, inOl = false, inP = false;
+ const closeP = () => { if (inP) { html += '</p>'; inP = false; } };
+ const closeLists = () => { if (inUl) { html += '</ul>'; inUl = false; } if (inOl) { html += '</ol>'; inOl = false; } };
+ const inline = s => s
+ .replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
+ .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
+ .replace(/\*([^*]+)\*/g, '<em>$1</em>')
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
+ for (const raw of lines) {
+ const line = raw.trimEnd();
+ if (/^---+$/.test(line)) { closeP(); closeLists(); html += '<hr>'; continue; }
+ if (/^# (.*)/.test(line)) { closeP(); closeLists(); html += `<h1>${inline(line.replace(/^# /, ''))}</h1>`; continue; }
+ if (/^## (.*)/.test(line)) { closeP(); closeLists(); html += `<h2>${inline(line.replace(/^## /, ''))}</h2>`; continue; }
+ if (/^### (.*)/.test(line)) { closeP(); closeLists(); html += `<h3>${inline(line.replace(/^### /, ''))}</h3>`; continue; }
+ if (/^\s*- (.*)/.test(line)) { closeP(); if (inOl){html+='</ol>';inOl=false;} if (!inUl){html += '<ul>'; inUl = true;} html += `<li>${inline(line.replace(/^\s*- /, ''))}</li>`; continue; }
+ if (/^\s*\d+\.\s(.*)/.test(line)) { closeP(); if (inUl){html+='</ul>';inUl=false;} if (!inOl){html += '<ol>'; inOl = true;} html += `<li>${inline(line.replace(/^\s*\d+\.\s/, ''))}</li>`; continue; }
+ if (line === '') { closeP(); closeLists(); continue; }
+ closeLists();
+ if (!inP) { html += '<p>'; inP = true; }
+ html += inline(line) + ' ';
+ }
+ closeP(); closeLists();
+ return html;
+}
+
+fetch('/api/competitors').then(r => r.text()).then(src => {
+ document.getElementById('md').style.color = '';
+ document.getElementById('md').innerHTML = md2html(src);
+}).catch(e => {
+ document.getElementById('md').innerHTML = '<p style="color:var(--err);">Competitor analysis not yet generated — the subagent run may have failed.</p>';
+});
+</script>
+</body>
+</html>
diff --git a/public/js/corner-nav.js b/public/js/corner-nav.js
index 0b0173de..e48e2eef 100644
--- a/public/js/corner-nav.js
+++ b/public/js/corner-nav.js
@@ -9,6 +9,7 @@
{ label: 'Mag-20 Audit', href: '/mag-20' },
{ label: 'Press Kit', href: '/press' },
{ label: 'Info Hub', href: '/hub' },
+ { label: 'Competitors', href: '/competitors' },
{ label: 'Leaderboard', href: '/#leaderboard' },
{ label: 'Trademark', href: '/trademark' },
{ label: 'Wall', href: '/wall' },
diff --git a/public/press.html b/public/press.html
index 94bbdf89..13825798 100644
--- a/public/press.html
+++ b/public/press.html
@@ -84,6 +84,14 @@
<div><div class="nm">Mag-20 Audit Matrix · JSON</div><div class="meta">Live compliance grid · refreshed every 15 min</div></div>
<a href="/api/mag-20" download="mag-20.json">DOWNLOAD</a>
</div>
+ <div class="download">
+ <div><div class="nm">Competitive Landscape · MD</div><div class="meta">17 competitors · 8 differentiation vectors</div></div>
+ <a href="/api/competitors" download="competitors.md">DOWNLOAD</a>
+ </div>
+ <div class="download">
+ <div><div class="nm">Operator Info Hub · JSON</div><div class="meta">27 curated trusted sources for independent operators</div></div>
+ <a href="/api/info-hub" download="info-hub.json">DOWNLOAD</a>
+ </div>
</div>
<footer>© 2026 Steven Abrams · <a href="/">panel</a> · <a href="https://agentabrams.com">agentabrams.com</a></footer>
diff --git a/server.js b/server.js
index 9384306c..de0be4eb 100644
--- a/server.js
+++ b/server.js
@@ -98,6 +98,7 @@ app.get('/api/gamify/leaderboard', (_, res) => {
app.get('/mag-20', (_, res) => res.sendFile(path.join(ROOT, 'public', 'mag-20.html')));
app.get('/press', (_, res) => res.sendFile(path.join(ROOT, 'public', 'press.html')));
app.get('/hub', (_, res) => res.sendFile(path.join(ROOT, 'public', 'hub.html')));
+app.get('/competitors', (_, res) => res.sendFile(path.join(ROOT, 'public', 'competitors.html')));
app.get('/api/build-log', (req, res) => res.json({ events: readJsonl(BUILD_LOG, parseInt(req.query.n || '50', 10)) }));
app.get('/api/wall', (req, res) => res.json({ posts: readJsonl(WALL_FILE, parseInt(req.query.n || '100', 10)) }));
← f9efb49b v0.7: info-hub from search-specialist subagent (27 trusted s
·
back to Abrams
·
tick: refresh inventory + SWOT at 2026-05-14T16:32:06Z 6a97ad43 →