← back to Abrams
public/mag-20.html
114 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Abrams — Mag-20</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: 1400px; margin: 0 auto; padding: 100px 24px 60px; }
table.mag { width: 100%; border-collapse: collapse; font-family: var(--mono); font-size: 11px; }
table.mag th, table.mag td { padding: 6px 8px; border: 1px solid var(--hairline); text-align: center; vertical-align: middle; }
table.mag th.rule { text-align: left; font-family: var(--sans); font-size: 12px; padding: 12px 16px; min-width: 280px; max-width: 320px; line-height: 1.4; white-space: normal; }
table.mag th.proj { writing-mode: vertical-rl; transform: rotate(180deg); padding: 12px 4px; font-size: 10px; letter-spacing: .1em; min-width: 28px; max-width: 28px; }
table.mag td.pass { background: rgba(74,222,128,.18); color: #4ade80; font-weight: 600; }
table.mag td.fail { background: rgba(239,68,68,.18); color: #ef4444; font-weight: 600; }
table.mag td.na { background: var(--bg-3); color: var(--fg-3); }
table.mag td.cat { color: var(--accent); font-size: 10px; letter-spacing: .15em; }
table.mag td.sev-critical { color: var(--err); }
table.mag td.sev-high { color: var(--warn); }
table.mag td.sev-medium { color: var(--fg-2); }
table.mag td.sev-low { color: var(--fg-3); }
table.mag tr.score-row td { background: #000; color: var(--accent); font-weight: 600; padding: 10px 8px; }
.legend { display: flex; gap: 18px; margin: 20px 0; font-family: var(--mono); font-size: 11px; letter-spacing: .15em; }
.legend span.pass { color: #4ade80; } .legend span.fail { color: #ef4444; } .legend span.na { color: var(--fg-3); }
.summary-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; margin: 24px 0; }
.summary-card { background: var(--bg-2); border: 1px solid var(--hairline); padding: 18px; }
.summary-card .lbl { font-family: var(--mono); font-size: 10px; color: var(--fg-3); letter-spacing: .25em; text-transform: uppercase; margin-bottom: 6px; }
.summary-card .val { font-family: var(--serif); font-size: 32px; color: var(--accent); }
</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 6px;">Mag-20</h1>
<p style="color:var(--fg-2);max-width:760px;margin:0 0 12px;">The 20 standing-rule patterns every Abrams / DW-family / agentabrams.com property must comply with. Live audit matrix; refreshed every 15 min by the YOLO tick.</p>
<div class="legend">
<span class="pass">■ PASS</span>
<span class="fail">■ FAIL</span>
<span class="na">■ N/A</span>
</div>
<div class="summary-cards" id="summary"></div>
<div style="overflow-x:auto;border:1px solid var(--hairline);">
<table class="mag" id="matrix"></table>
</div>
<p style="color:var(--fg-3);font-family:var(--mono);font-size:10px;letter-spacing:.2em;text-transform:uppercase;margin-top:16px;" id="meta"></p>
</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])); }
fetch('/api/mag-20').then(r=>r.json()).then(d => {
const patterns = d.patterns || [];
const results = d.results || { projects: [], summary: {} };
const projects = results.projects || [];
// summary cards
const totalPass = projects.reduce((a,p)=>a+(p.score||0), 0);
const totalFail = projects.reduce((a,p)=>a+(p.fail||0), 0);
const totalNa = projects.reduce((a,p)=>a+(p.na||0), 0);
const cells = totalPass + totalFail + totalNa;
const compliance = cells > 0 ? Math.round(100 * totalPass / (totalPass + totalFail)) : 0;
document.getElementById('summary').innerHTML = `
<div class="summary-card"><div class="lbl">Compliance</div><div class="val">${compliance}%</div></div>
<div class="summary-card"><div class="lbl">Pass cells</div><div class="val" style="color:#4ade80;">${totalPass}</div></div>
<div class="summary-card"><div class="lbl">Fail cells</div><div class="val" style="color:#ef4444;">${totalFail}</div></div>
<div class="summary-card"><div class="lbl">N/A cells</div><div class="val" style="color:var(--fg-3);">${totalNa}</div></div>
<div class="summary-card"><div class="lbl">Projects</div><div class="val">${projects.length}</div></div>
<div class="summary-card"><div class="lbl">Rules</div><div class="val">${patterns.length}</div></div>
`;
// matrix header
let html = '<thead><tr><th class="rule">Rule</th><th>Cat</th><th>Sev</th>';
projects.forEach(p => { html += `<th class="proj" title="${esc(p.name)}">${esc(p.name)}</th>`; });
html += '</tr></thead><tbody>';
patterns.forEach(pat => {
html += `<tr><th class="rule" title="${esc(pat.source||'')}">${pat.id}. ${esc(pat.rule)}</th>`;
html += `<td class="cat">${esc((pat.category||'').toUpperCase())}</td>`;
html += `<td class="sev-${esc(pat.severity)}">${esc((pat.severity||'').toUpperCase())}</td>`;
projects.forEach(p => {
const cell = p.checks?.[pat.id] || { status: 'n/a' };
const cls = cell.status === 'pass' ? 'pass' : (cell.status === 'fail' ? 'fail' : 'na');
const mark = cell.status === 'pass' ? '✓' : (cell.status === 'fail' ? '✗' : '–');
html += `<td class="${cls}" title="${esc(cell.evidence||'')}">${mark}</td>`;
});
html += '</tr>';
});
// score row
html += '<tr class="score-row"><td colspan="3">SCORE (pass / 20)</td>';
projects.forEach(p => { html += `<td>${p.score||0}</td>`; });
html += '</tr></tbody>';
document.getElementById('matrix').innerHTML = html;
document.getElementById('meta').textContent = `Last audit: ${new Date(results.generated_at).toLocaleString()} · ${projects.length} projects × ${patterns.length} rules = ${cells} checks`;
});
</script>
</body>
</html>