← back to Abrams
public/ideas.html
126 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Abrams — Business Ideas + SWOT Ledger</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<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: 1680px; margin: 0 auto; padding: 100px 24px 60px; }
#ideas { display: grid; grid-template-columns: repeat(auto-fill, minmax(420px, 1fr)); gap: 24px; align-items: start; }
.idea-card { background: var(--bg-2); border: 1px solid var(--hairline); padding: 32px; }
.idea-card .head { display: flex; justify-content: space-between; align-items: baseline; gap: 24px; flex-wrap: wrap; margin-bottom: 16px; }
.idea-card h2 { font-family: var(--serif); font-size: 32px; font-weight: 400; margin: 0; }
.idea-card .tag { font-family: var(--serif); font-style: italic; color: var(--fg-2); margin: 0 0 18px; }
.idea-card .swot { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 24px 0; }
.idea-card .swot .quad { background: var(--bg-3); border-left: 3px solid; padding: 16px 20px; }
.idea-card .swot .S { border-color: var(--ok); }
.idea-card .swot .W { border-color: var(--warn); }
.idea-card .swot .O { border-color: var(--accent); }
.idea-card .swot .T { border-color: var(--err); }
.idea-card .swot .quad h4 { font-family: var(--mono); font-size: 11px; letter-spacing: .25em; margin: 0 0 10px; }
.idea-card .swot .quad ul { padding-left: 18px; margin: 0; }
.idea-card .swot .quad li { font-size: 13px; color: var(--fg); line-height: 1.6; padding: 3px 0; }
.idea-card .revenue, .idea-card .moat, .idea-card .next3 { margin: 20px 0; padding: 14px 16px; background: var(--bg-3); border: 1px solid var(--hairline); }
.idea-card .revenue h4, .idea-card .moat h4, .idea-card .next3 h4 { font-family: var(--mono); font-size: 11px; letter-spacing: .25em; color: var(--accent); margin: 0 0 8px; }
.idea-card .revenue ul, .idea-card .next3 ul { margin: 0; padding-left: 18px; }
.idea-card .moat p { color: var(--fg); margin: 0; font-size: 14px; line-height: 1.6; }
@media (max-width: 800px) { .idea-card .swot { grid-template-columns: 1fr; } }
</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">
<h1 style="font-family:var(--serif);font-size:48px;font-weight:400;margin:0 0 8px;">Business Ideas — Full SWOT</h1>
<p style="color:var(--fg-2);max-width:720px;margin:0 0 24px;">Every site, every vertical, every possible expansion — full Strengths / Weaknesses / Opportunities / Threats plus revenue lines + moat + next 3 moves. Generated fresh on every YOLO tick.</p>
<div class="controls">
<span class="lbl">Sort</span>
<select id="sort">
<option value="newest">Newest generated</option>
<option value="stage">Stage (live first)</option>
<option value="name">Name A-Z</option>
<option value="category">Category</option>
</select>
<span class="lbl">Density</span>
<input type="range" id="density" min="1" max="3" value="1" />
<span class="lbl">Filter</span>
<input id="filter" type="search" placeholder="search names + taglines" style="background:var(--bg-2);border:1px solid var(--hairline);color:var(--fg);padding:8px 12px;font-family:var(--mono);font-size:12px;min-width:240px;" />
</div>
<div id="ideas"></div>
</div>
<footer>© 2026 Steven Abrams</footer>
<script src="/static/js/corner-nav.js"></script>
<script src="/static/js/gamify.js"></script>
<script>
function esc(s){ return String(s||'').replace(/[&<>"']/g, m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m])); }
let allIdeas = [];
function renderIdeas() {
const sortBy = document.getElementById('sort').value;
const filter = (document.getElementById('filter').value || '').toLowerCase();
const rank = s => ({live:0, dev:1, idea:2}[s] ?? 9);
let ideas = allIdeas.filter(i => !filter || (i.name+' '+i.tagline+' '+i.category).toLowerCase().includes(filter));
if (sortBy === 'stage') ideas.sort((a,b)=>rank(a.stage)-rank(b.stage));
if (sortBy === 'name') ideas.sort((a,b)=>a.name.localeCompare(b.name));
if (sortBy === 'category') ideas.sort((a,b)=>(a.category||'').localeCompare(b.category||''));
if (sortBy === 'newest') ideas.sort((a,b)=>(b.generated_at||'').localeCompare(a.generated_at||''));
document.getElementById('ideas').innerHTML = ideas.map(i => `
<div class="idea-card" id="${esc(i.slug)}">
<div class="head">
<h2>${esc(i.name)}</h2>
<span style="font-family:var(--mono);font-size:11px;color:var(--accent);letter-spacing:.2em;">${esc((i.stage||'idea').toUpperCase())} · ${esc(i.category||'')}</span>
</div>
<p class="tag">${esc(i.tagline||'')}</p>
<div class="swot">
<div class="quad S"><h4>STRENGTHS</h4><ul>${(i.strengths||[]).map(x=>`<li>${esc(x)}</li>`).join('')}</ul></div>
<div class="quad W"><h4>WEAKNESSES</h4><ul>${(i.weaknesses||[]).map(x=>`<li>${esc(x)}</li>`).join('')}</ul></div>
<div class="quad O"><h4>OPPORTUNITIES</h4><ul>${(i.opportunities||[]).map(x=>`<li>${esc(x)}</li>`).join('')}</ul></div>
<div class="quad T"><h4>THREATS</h4><ul>${(i.threats||[]).map(x=>`<li>${esc(x)}</li>`).join('')}</ul></div>
</div>
<div class="revenue"><h4>REVENUE LINES</h4><ul>${(i.revenue_lines||[]).map(x=>`<li>${esc(x)}</li>`).join('')}</ul></div>
<div class="moat"><h4>COMPETITIVE MOAT</h4><p>${esc(i.competitive_moat||'')}</p></div>
<div class="next3"><h4>NEXT 3 MOVES</h4><ul>${(i.next_3_moves||[]).map(x=>`<li>${esc(x)}</li>`).join('')}</ul></div>
</div>
`).join('') || '<div style="color:var(--fg-3);padding:48px;text-align:center;font-family:var(--mono);">no SWOTs match filter</div>';
}
function applyDensity(v) {
document.querySelectorAll('.idea-card .swot').forEach(el => el.style.gridTemplateColumns = v === '1' ? '1fr 1fr' : (v === '2' ? '1fr 1fr 1fr 1fr' : '1fr'));
}
// hydrate sort + filter + density from localStorage BEFORE first render (Steve standing rule)
const savedSort = localStorage.getItem('abrams.ideas.sort');
if (savedSort) {
const sortEl = document.getElementById('sort');
if ([...sortEl.options].some(o => o.value === savedSort)) sortEl.value = savedSort;
}
const savedFilter = localStorage.getItem('abrams.ideas.filter');
if (savedFilter) document.getElementById('filter').value = savedFilter;
const sd = localStorage.getItem('abrams.ideas.density');
if (sd) { document.getElementById('density').value = sd; }
fetch('/api/ideas').then(r=>r.json()).then(j => { allIdeas = j.ideas || []; renderIdeas(); applyDensity(document.getElementById('density').value); });
document.getElementById('sort').addEventListener('change', e => { localStorage.setItem('abrams.ideas.sort', e.target.value); renderIdeas(); applyDensity(document.getElementById('density').value); });
document.getElementById('filter').addEventListener('input', e => { localStorage.setItem('abrams.ideas.filter', e.target.value); renderIdeas(); applyDensity(document.getElementById('density').value); });
document.getElementById('density').addEventListener('input', e => {
const v = e.target.value;
applyDensity(v);
localStorage.setItem('abrams.ideas.density', v);
});
</script>
</body>
</html>