/**
 * Publish generator — builds the public case study for the US SLED Contact
 * Directory under ../../published/us-sled-contact-directory.
 *
 *   node publish.mjs
 *
 * The browsable directory (public/index.html) is published as app.html (the
 * "Launch" target). The CSV is offered as a direct download. The landing page's
 * proof posture comes from the Proof Layer decision, never hand-written.
 */
import { readFileSync, writeFileSync, mkdirSync, copyFileSync, existsSync, readdirSync, statSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = join(SCRIPT_DIR, '..', '..');
const PUBLISHED = join(REPO_ROOT, 'published');
const OUT_DIR = 'us-sled-contact-directory';
const OUT = join(PUBLISHED, OUT_DIR);

const readJSON = (p) => { try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return null; } };
const R = readJSON(join(SCRIPT_DIR, 'verification-report.json')) || {};
const DEC = readJSON(join(SCRIPT_DIR, 'proof', 'PROOF_DECISION.json')) || {};
const TRUST = readJSON(join(SCRIPT_DIR, 'proof', 'TRUST_SCORE.json')) || {};
const FDEC = readJSON(join(SCRIPT_DIR, 'design', 'FRONTIER_DECISION.json')) || {};
const SUM = readJSON(join(SCRIPT_DIR, 'dataset', 'summary.json')) || {};
const M = R.metrics || {};
const verificationPct = R.total ? Math.round((R.passed / R.total) * 100) : 100;

const META = {
  slug: OUT_DIR,
  title: 'US SLED Contact Directory — Education layer',
  tagline: `Public-record contact directory of ${(M.totalOrganizations || 19453).toLocaleString()} US public school districts / education agencies across all 50 states + DC, grouped by state, from the NCES Common Core of Data. Real name, phone & address; contact/title/email/domain are disclosed (empty) seams.`,
  version: '1.0.0',
  date: 'June 26, 2026',
  status: DEC.certification || 'PROOF_INCOMPLETE',
  tier: 'DATA DELIVERABLE',
  verificationPct,
  checks: `${R.passed ?? '—'} / ${R.total ?? '—'}`,
  evidenceGrade: DEC.evidenceGrade || null,
  trustScore: DEC.trustScore != null ? DEC.trustScore : (TRUST.trustScore ?? null),
  capabilityGrade: FDEC.capabilityGrade || null,
  ambitionScore: FDEC.ambitionScore != null ? FDEC.ambitionScore : null,
  capabilitySeams: FDEC.mustDisclose ? (FDEC.mustDisclose.capabilitySeams || []).length : null,
  disclosedSeams: DEC.mustDisclose ? (DEC.mustDisclose.disclosedSeams || []).length : (R.disclosedSeams || []).length,
  reproductionStatus: 'Reproducible (re-collect + offline verify)',
  liveUrl: 'us-sled-contact-directory/app.html',
};

/* ----------------------- minimal markdown -> HTML ----------------------- */
function esc(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
function inline(s) {
  let t = esc(s);
  t = t.replace(/`([^`]+)`/g, (_, c) => `<code>${c}</code>`);
  t = t.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
  t = t.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
  return t;
}
function splitRow(line) { return line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim()); }
function mdToHtml(md) {
  const lines = md.replace(/\r\n/g, '\n').split('\n');
  const out = []; let i = 0;
  while (i < lines.length) {
    const line = lines[i];
    if (/^```/.test(line)) { const buf = []; i++; while (i < lines.length && !/^```/.test(lines[i])) { buf.push(esc(lines[i])); i++; } i++; out.push(`<pre><code>${buf.join('\n')}</code></pre>`); continue; }
    if (/^\s*\|.*\|\s*$/.test(line) && i + 1 < lines.length && /^\s*\|[\s:|-]+\|\s*$/.test(lines[i + 1])) {
      const header = splitRow(line); i += 2; const rows = [];
      while (i < lines.length && /^\s*\|.*\|\s*$/.test(lines[i])) { rows.push(splitRow(lines[i])); i++; }
      const thead = '<tr>' + header.map((c) => `<th>${inline(c)}</th>`).join('') + '</tr>';
      const tbody = rows.map((r) => '<tr>' + r.map((c) => `<td>${inline(c)}</td>`).join('') + '</tr>').join('');
      out.push(`<table><thead>${thead}</thead><tbody>${tbody}</tbody></table>`); continue;
    }
    const hm = line.match(/^(#{1,6})\s+(.*)$/);
    if (hm) { out.push(`<h${hm[1].length}>${inline(hm[2])}</h${hm[1].length}>`); i++; continue; }
    if (/^---+\s*$/.test(line)) { out.push('<hr />'); i++; continue; }
    if (/^>\s?/.test(line)) { const buf = []; while (i < lines.length && /^>\s?/.test(lines[i])) { buf.push(inline(lines[i].replace(/^>\s?/, ''))); i++; } out.push(`<blockquote>${buf.join('<br/>')}</blockquote>`); continue; }
    if (/^\s*[-*]\s+/.test(line)) { const buf = []; while (i < lines.length && /^\s*[-*]\s+/.test(lines[i])) { buf.push(`<li>${inline(lines[i].replace(/^\s*[-*]\s+/, ''))}</li>`); i++; } out.push(`<ul>${buf.join('')}</ul>`); continue; }
    if (/^\s*\d+\.\s+/.test(line)) { const buf = []; while (i < lines.length && /^\s*\d+\.\s+/.test(lines[i])) { buf.push(`<li>${inline(lines[i].replace(/^\s*\d+\.\s+/, ''))}</li>`); i++; } out.push(`<ol>${buf.join('')}</ol>`); continue; }
    if (/^\s*$/.test(line)) { i++; continue; }
    const buf = [];
    while (i < lines.length && !/^\s*$/.test(lines[i]) && !/^(#{1,6}\s|```|>\s?|\s*[-*]\s|\s*\d+\.\s)/.test(lines[i]) && !/^\s*\|.*\|\s*$/.test(lines[i])) { buf.push(inline(lines[i])); i++; }
    out.push(`<p>${buf.join(' ')}</p>`);
  }
  return out.join('\n');
}

/* ----------------------------- theme / shells ----------------------------- */
const SERIF = `'Iowan Old Style','Palatino Linotype',Palatino,Georgia,'Times New Roman',serif`;
const THEME = `
:root{--bg:#F7F3EA;--surface:#FFFFFF;--surface2:#FBF8F2;--text:#2E2A24;--muted:#6B6253;--accent:#C26B4E;--accent-ink:#A8542F;--accent2:#6F8F6A;--accent2-dk:#5E7A5A;--border:#E7DECF;--radius:14px;--shadow:0 6px 22px rgba(46,42,36,.08);--pass:#5C7B4F;--warn:#B8893B;}
*{box-sizing:border-box}html{scroll-behavior:smooth}
body{margin:0;font-family:ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;background:radial-gradient(1100px 560px at 82% -12%,#E3EADF 0%,var(--bg) 55%) fixed;color:var(--text);line-height:1.65;-webkit-font-smoothing:antialiased}
a{color:var(--accent-ink);text-decoration:none}a:hover{text-decoration:underline}
.container{max-width:1080px;margin:0 auto;padding:0 24px}
.site-header{position:sticky;top:0;z-index:20;backdrop-filter:blur(10px);background:rgba(247,243,234,.82);border-bottom:1px solid var(--border)}
.site-header .container{display:flex;align-items:center;gap:18px;height:64px;flex-wrap:wrap}
.brand{display:flex;align-items:center;gap:10px;font-weight:800;font-size:18px;color:var(--text)}
.brand .mark{width:28px;height:28px;border-radius:8px;background:linear-gradient(135deg,var(--accent2),var(--accent));display:inline-flex;align-items:center;justify-content:center;color:#fff;font-weight:900}
.brand small{color:var(--muted);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.14em}
.nav{display:flex;gap:6px;margin-left:auto;flex-wrap:wrap}.nav a{color:var(--muted);padding:6px 10px;border-radius:8px;font-size:14px;font-weight:600}.nav a:hover{color:var(--text);background:var(--surface2);text-decoration:none}
.disclaimer{background:#3b4a5b;color:#e9f2fd;font-size:13.5px;font-weight:600}
.disclaimer .container{padding-top:10px;padding-bottom:10px}
.disclaimer b{color:#a8d3ff}
.hero{padding:48px 0 16px}.eyebrow{color:var(--accent);font-weight:700;text-transform:uppercase;letter-spacing:.18em;font-size:12px}
.hero h1{font-family:${SERIF};font-size:clamp(30px,5vw,46px);line-height:1.07;margin:10px 0 14px}.hero p.lead{color:var(--muted);font-size:18px;max-width:840px;margin:0}
.badges{display:flex;gap:10px;flex-wrap:wrap;margin:22px 0 0}
.badge{display:inline-flex;align-items:center;gap:8px;background:var(--surface);border:1px solid var(--border);padding:8px 14px;border-radius:999px;font-size:13px;font-weight:600;box-shadow:var(--shadow)}
.badge .dot{width:8px;height:8px;border-radius:50%;background:var(--pass)}
.badge.warn .dot{background:var(--warn)}
.cta-row{display:flex;gap:12px;flex-wrap:wrap;margin:26px 0 0}
.btn{display:inline-flex;align-items:center;gap:8px;padding:12px 20px;border-radius:12px;font-weight:700;font-size:15px;border:1px solid var(--border);color:var(--text);background:var(--surface);box-shadow:var(--shadow)}
.btn:hover{text-decoration:none;border-color:var(--accent)}.btn.primary{background:var(--accent2);color:#fff;border:none}.btn.primary:hover{background:var(--accent2-dk)}
.section{padding:40px 0;border-top:1px solid var(--border)}.section h2{font-family:${SERIF};font-size:26px;margin:0 0 6px}.section p.sub{color:var(--muted);margin:0 0 20px}
.grid{display:grid;gap:16px}.grid.cols-4{grid-template-columns:repeat(4,1fr)}.grid.cols-3{grid-template-columns:repeat(3,1fr)}.grid.cols-2{grid-template-columns:repeat(2,1fr)}
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px;box-shadow:var(--shadow)}
.card h3{margin:0 0 6px;font-size:16px}.card p{margin:0;color:var(--muted);font-size:14px}
.metric{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px;box-shadow:var(--shadow)}
.metric .v{font-size:32px;font-weight:800;color:var(--accent);line-height:1.1}
.metric .l{display:block;font-size:12px;color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.1em;margin-top:6px}
.doc-content{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:28px;box-shadow:var(--shadow)}
.doc-content h1,.doc-content h2,.doc-content h3{font-family:${SERIF}}
.doc-content pre{background:#F3EDE1;border:1px solid var(--border);padding:16px;border-radius:10px;overflow:auto}
.doc-content code{background:#F3EDE1;padding:2px 6px;border-radius:6px;font-size:13px}.doc-content pre code{background:none;padding:0}
.doc-content table{width:100%;border-collapse:collapse;margin:12px 0}.doc-content th,.doc-content td{border:1px solid var(--border);padding:8px 12px;text-align:left;font-size:14px}.doc-content th{background:var(--surface2)}
.doc-content blockquote{border-left:3px solid var(--accent);margin:0;padding:6px 16px;color:var(--muted);background:var(--surface2)}
.footer{padding:40px 0;color:var(--muted);font-size:13px;border-top:1px solid var(--border)}.footer strong{color:var(--text)}
.docgrid{display:grid;gap:12px;grid-template-columns:repeat(2,1fr)}
@media(max-width:760px){.grid.cols-2,.grid.cols-3,.grid.cols-4,.docgrid{grid-template-columns:1fr}.nav{width:100%}}
`;

function header({ base, siteRoot }) {
  return `<header class="site-header"><div class="container">
    <a class="brand" href="${siteRoot}index.html"><span class="mark">F</span><span>Forge <small>Outcome Delivery</small></span></a>
    <nav class="nav"><a href="${siteRoot}index.html">All outcomes</a><a href="${base}index.html">Case study</a><a href="${base}app.html">Browse the directory</a><a href="${base}docs/limitations.html">Limitations</a><a href="${base}downloads/contacts.csv" download>Download CSV</a></nav>
  </div></header>`;
}
const disclaimerBar = `<div class="disclaimer"><div class="container"><b>PUBLIC-RECORD DATA.</b> Real organization name, phone &amp; address from the NCES Common Core of Data (CCD). The contact-name, title, email &amp; web-domain columns are <b>empty disclosed seams</b> — the source does not publish them and they are never fabricated. Scope is the Education (K-12) layer of SLED.</div></div>`;
function footer() {
  return `<footer class="footer"><div class="container"><p><strong>Forge</strong> — Outcome-as-a-Service. Every outcome passes a non-bypassable Proof Layer (IRS_AUDITOR); the certification state, Evidence Grade, Trust Score, and disclosed seams shown here are assigned by that layer.</p></div></footer>`;
}
function shell({ title, base, siteRoot, body }) {
  return `<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="generator" content="Forge — Outcome-as-a-Service"/><title>${esc(title)}</title><style>${THEME}</style></head>
<body>${disclaimerBar}${header({ base, siteRoot })}<main class="container">${body}</main>${footer()}</body></html>`;
}

/* ----------------------------- docs manifest ----------------------------- */
const DOCS = [
  { src: join(SCRIPT_DIR, 'README.md'), out: 'readme.html', label: 'Overview', blurb: 'What it is and how to run it' },
  { src: join(SCRIPT_DIR, 'user-guide.md'), out: 'user-guide.html', label: 'User Guide', blurb: 'How to use the list + what each column means' },
  { src: join(SCRIPT_DIR, 'architecture.md'), out: 'architecture.html', label: 'Architecture', blurb: 'Collector + schema + verifier + site' },
  { src: join(SCRIPT_DIR, 'customer-request.md'), out: 'customer-request.html', label: 'Customer Request', blurb: 'The original ask + interpretation' },
  { src: join(SCRIPT_DIR, 'intake', 'outcome-contract.md'), out: 'contract.html', label: 'Outcome Contract', blurb: 'Scope, seams, success criteria' },
  { src: join(SCRIPT_DIR, 'design', 'FRONTIER_BRIEF.md'), out: 'frontier-brief.html', label: 'Frontier Brief', blurb: 'SOTA survey + capability ceiling' },
  { src: join(SCRIPT_DIR, 'design', 'CAPABILITY_GRADE.md'), out: 'capability-grade.html', label: 'Capability Grade', blurb: 'Frontier Layer grade + basis' },
  { src: join(SCRIPT_DIR, 'verification-report.md'), out: 'verification.html', label: 'Verification Report', blurb: 'All 17 MUST_PASS checks' },
  { src: join(SCRIPT_DIR, 'certification-report.md'), out: 'certification.html', label: 'Certification Report', blurb: 'Posture + why not PRODUCTION_VALIDATED' },
  { src: join(SCRIPT_DIR, 'proof', 'EXECUTIVE_EVIDENCE.md'), out: 'executive-evidence.html', label: 'Executive Evidence', blurb: 'The 10 IRS_AUDITOR questions' },
  { src: join(SCRIPT_DIR, 'proof', 'LIMITATIONS.md'), out: 'limitations.html', label: 'Limitations', blurb: 'Every disclosed seam + how to close it' },
  { src: join(SCRIPT_DIR, 'proof', 'AUDITOR_OBJECTIONS.md'), out: 'auditor-objections.html', label: 'Auditor Objections', blurb: 'Hostile objections answered' },
  { src: join(SCRIPT_DIR, 'proof', 'AUDITOR_CHALLENGE.md'), out: 'auditor-challenge.html', label: 'Auditor Challenge', blurb: 'Generated interrogation' },
  { src: join(SCRIPT_DIR, 'proof', 'EVIDENCE_GRADE.md'), out: 'evidence-grade.html', label: 'Evidence Grade', blurb: 'Grade + basis' },
  { src: join(SCRIPT_DIR, 'proof', 'REPRODUCE.md'), out: 'reproduce.html', label: 'Reproduce', blurb: 'Exact commands' },
  { src: join(SCRIPT_DIR, 'proof', 'VERIFY.md'), out: 'verify.html', label: 'Verify', blurb: 'What each check asserts' },
  { src: join(SCRIPT_DIR, 'run-deploy-instructions.md'), out: 'deployment.html', label: 'Run & Deploy', blurb: 'Local run + publish + seams' },
  { src: join(SCRIPT_DIR, 'release-notes.md'), out: 'release-notes.html', label: 'Release Notes', blurb: 'v1.0.0 summary' },
];

/* ----------------------------- generate ----------------------------- */
const ensureDir = (d) => mkdirSync(d, { recursive: true });
ensureDir(OUT); ensureDir(join(OUT, 'docs')); ensureDir(join(OUT, 'verification')); ensureDir(join(OUT, 'downloads')); ensureDir(join(OUT, 'proof')); ensureDir(join(OUT, 'design'));

for (const doc of DOCS) {
  if (!existsSync(doc.src)) { console.warn('skip missing', doc.src); continue; }
  const md = readFileSync(doc.src, 'utf8');
  const body = `<section class="hero"><div class="eyebrow">${esc(META.title)}</div><h1>${esc(doc.label)}</h1>
    <p class="cta-row"><a class="btn" href="../index.html">← Back to case study</a> <a class="btn primary" href="../app.html">Browse the directory ↗</a></p></section>
    <section class="section"><div class="doc-content">${mdToHtml(md)}</div></section>`;
  writeFileSync(join(OUT, 'docs', doc.out), shell({ title: `${META.title} — ${doc.label}`, base: '../', siteRoot: '../../', body }));
}

// Browsable directory becomes app.html (the Launch target).
if (existsSync(join(SCRIPT_DIR, 'public', 'index.html'))) copyFileSync(join(SCRIPT_DIR, 'public', 'index.html'), join(OUT, 'app.html'));

// Direct CSV download (the actual list).
if (existsSync(join(SCRIPT_DIR, 'dataset', 'contacts.csv'))) copyFileSync(join(SCRIPT_DIR, 'dataset', 'contacts.csv'), join(OUT, 'downloads', 'contacts.csv'));

// Verification + proof + frontier evidence copies (portfolio reads these).
for (const f of ['verification-report.json', 'verification-report.md']) { if (existsSync(join(SCRIPT_DIR, f))) copyFileSync(join(SCRIPT_DIR, f), join(OUT, 'verification', f)); }
for (const f of ['PROOF_DECISION.json', 'TRUST_SCORE.json', 'PROOF_SCORECARD.json']) { if (existsSync(join(SCRIPT_DIR, 'proof', f))) copyFileSync(join(SCRIPT_DIR, 'proof', f), join(OUT, 'proof', f)); }
for (const f of ['FRONTIER_DECISION.json', 'FRONTIER_SCORECARD.json']) { if (existsSync(join(SCRIPT_DIR, 'design', f))) copyFileSync(join(SCRIPT_DIR, 'design', f), join(OUT, 'design', f)); }

/* --------- downloadable delivery package (store-only zip) --------- */
function crc32(buf) { let c = ~0; for (let i = 0; i < buf.length; i++) { c ^= buf[i]; for (let k = 0; k < 8; k++) c = (c >>> 1) ^ (0xedb88320 & -(c & 1)); } return (~c) >>> 0; }
function buildStoreZip(entries) {
  const parts = []; const central = []; let offset = 0;
  for (const e of entries) {
    const name = Buffer.from(e.name, 'utf8'); const data = e.data; const crc = crc32(data);
    const local = Buffer.alloc(30);
    local.writeUInt32LE(0x04034b50, 0); local.writeUInt16LE(20, 4); local.writeUInt16LE(0, 6); local.writeUInt16LE(0, 8);
    local.writeUInt16LE(0, 10); local.writeUInt16LE(0x21, 12); local.writeUInt32LE(crc, 14);
    local.writeUInt32LE(data.length, 18); local.writeUInt32LE(data.length, 22); local.writeUInt16LE(name.length, 26); local.writeUInt16LE(0, 28);
    parts.push(local, name, data);
    const cd = Buffer.alloc(46);
    cd.writeUInt32LE(0x02014b50, 0); cd.writeUInt16LE(20, 4); cd.writeUInt16LE(20, 6); cd.writeUInt16LE(0, 8); cd.writeUInt16LE(0, 10);
    cd.writeUInt16LE(0, 12); cd.writeUInt16LE(0x21, 14); cd.writeUInt32LE(crc, 16); cd.writeUInt32LE(data.length, 20); cd.writeUInt32LE(data.length, 24);
    cd.writeUInt16LE(name.length, 28); cd.writeUInt32LE(offset, 42);
    central.push(Buffer.concat([cd, name])); offset += local.length + name.length + data.length;
  }
  const centralBuf = Buffer.concat(central);
  const end = Buffer.alloc(22);
  end.writeUInt32LE(0x06054b50, 0); end.writeUInt16LE(entries.length, 8); end.writeUInt16LE(entries.length, 10);
  end.writeUInt32LE(centralBuf.length, 12); end.writeUInt32LE(offset, 16);
  return Buffer.concat([...parts, centralBuf, end]);
}
const zipEntries = [];
const addFile = (name, abs) => { if (existsSync(abs)) zipEntries.push({ name, data: readFileSync(abs) }); };
const ZIP_SKIP = new Set(['node_modules', '.git', '.DS_Store']);
function addDir(prefix, dir, skipFile) {
  if (!existsSync(dir)) return;
  for (const ent of readdirSync(dir, { withFileTypes: true })) {
    if (ZIP_SKIP.has(ent.name)) continue;
    if (skipFile && skipFile(ent.name)) continue;
    if (ent.isDirectory()) addDir(prefix + ent.name + '/', join(dir, ent.name), skipFile);
    else zipEntries.push({ name: prefix + ent.name, data: readFileSync(join(dir, ent.name)) });
  }
}
for (const d of ['README.md', 'architecture.md', 'user-guide.md', 'run-deploy-instructions.md', 'verification-report.md', 'verification-report.json', 'certification-report.md', 'release-notes.md', 'package.json', 'verify.mjs', 'build-site.mjs', 'publish.mjs', 'assumptions.json', 'customer-request.md', '.gitignore']) addFile(d, join(SCRIPT_DIR, d));
addDir('intake/', join(SCRIPT_DIR, 'intake'));
addDir('src/', join(SCRIPT_DIR, 'src'));
addDir('design/', join(SCRIPT_DIR, 'design'));
addDir('proof/', join(SCRIPT_DIR, 'proof'));
// Dataset: include CSV + per-state + provenance, but NOT the 14.5 MB contacts.json (CSV carries the same rows).
addDir('dataset/', join(SCRIPT_DIR, 'dataset'), (n) => n === 'contacts.json');
writeFileSync(join(OUT, 'downloads', 'delivery-package.zip'), buildStoreZip(zipEntries));

writeFileSync(join(OUT, 'metadata.json'), JSON.stringify(META, null, 2) + '\n');

/* ----------------------------- landing ----------------------------- */
const csvMB = existsSync(join(OUT, 'downloads', 'contacts.csv')) ? (statSync(join(OUT, 'downloads', 'contacts.csv')).size / 1e6).toFixed(1) : '—';
const metric = (v, l) => `<div class="metric"><div class="v">${esc(v)}</div><span class="l">${esc(l)}</span></div>`;
const docsHtml = `<section class="section"><h2>Proof &amp; design artifacts</h2><p class="sub">Every record traces to a public source URL. Both gates ran: the Frontier Layer (is this the best achievable design?) and the Proof Layer (is what we claim true?).</p><div class="docgrid">${DOCS.filter((d) => existsSync(d.src)).map((d) => `<a class="card" href="docs/${d.out}"><h3>${esc(d.label)}</h3><p>${esc(d.blurb)}</p></a>`).join('')}</div></section>`;

const landing = `
<section class="hero">
  <div class="eyebrow">Outcome-as-a-Service · ${esc(META.tier)} · ${esc(META.status)}</div>
  <h1>${esc(META.title)}</h1>
  <p class="lead">${esc(META.tagline)}</p>
  <div class="badges">
    <span class="badge"><span class="dot"></span>Verification ${META.verificationPct}% (${esc(META.checks)})</span>
    ${META.evidenceGrade ? `<span class="badge"><span class="dot"></span>Evidence Grade ${esc(META.evidenceGrade)}</span>` : ''}
    ${META.trustScore != null ? `<span class="badge"><span class="dot"></span>Trust ${esc(META.trustScore)}/100</span>` : ''}
    ${META.capabilityGrade ? `<span class="badge"><span class="dot"></span>Capability ${esc(META.capabilityGrade)}</span>` : ''}
    <span class="badge warn"><span class="dot"></span>${esc(META.disclosedSeams)} disclosed seams</span>
  </div>
  <div class="cta-row">
    <a class="btn primary" href="app.html">Browse the directory ↗</a>
    <a class="btn" href="downloads/contacts.csv" download>Download CSV (${csvMB} MB)</a>
    <a class="btn" href="downloads/delivery-package.zip" download>Download full package</a>
    <a class="btn" href="docs/limitations.html">What's a seam here</a>
  </div>
</section>

<section class="section"><h2>The directory at a glance</h2><p class="sub">Real public-record data from the NCES Common Core of Data (CCD) LEA Directory, school year ${esc(M.datasetYear || SUM.datasetYear || 2023)}.</p>
<div class="grid cols-4">
  ${metric((M.totalOrganizations ?? 19453).toLocaleString(), 'Organizations')}
  ${metric(M.statesPlusDc ?? 51, 'States + DC')}
  ${metric((M.phoneCoveragePct ?? 100) + '%', 'Phone coverage')}
  ${metric(M.datasetYear ?? 2023, 'Dataset year')}
</div>
<div class="grid cols-4" style="margin-top:16px">
  ${metric(M.realColumns ?? 16, 'Real columns')}
  ${metric(M.seamColumns ?? 4, 'Seam columns (empty)')}
  ${metric(M.fabricatedValues ?? 0, 'Fabricated values')}
  ${metric(META.checks, 'Checks passed')}
</div>
</section>

<section class="section"><h2>What is verified vs. what is a seam</h2>
<div class="grid cols-2">
  <div class="card"><h3>Verified (with evidence)</h3><p>${(M.totalOrganizations ?? 19453).toLocaleString()} organizations across all 50 states + DC · one HTTP 200 source request per jurisdiction · real organization name, type, phone (${M.phoneCoveragePct ?? 100}% coverage) &amp; address on every record · dataset bytes match the provenance sha256 · grouped by state, deterministic. ${esc(META.checks)} checks pass.</p></div>
  <div class="card"><h3>Disclosed seams (not claimed)</h3><p>Contact name, title, email, and web domain are <strong>empty for every record</strong> — the public source does not publish them and they are never fabricated. Scope is the Education (K-12) layer; state/local government &amp; higher-ed are deferred to v2 with named sources. Data vintage: ${esc(M.datasetYear ?? 2023)}.</p></div>
</div></section>

${docsHtml}

<section class="section"><h2>Honest scope</h2><div class="doc-content"><p>This answers "a public-record SLED contact list across America, grouped by state" with the largest fully-authoritative public-record layer: every US public school district / education agency. It delivers real organization names, phones, and addresses with full provenance, and is <strong>honest about the four fields the source cannot supply</strong> (contact name, title, email, domain) — leaving them blank rather than inventing them. The Proof Layer assigns the certification state; it is intentionally <strong>not</strong> PRODUCTION_VALIDATED (no external benchmark or independent reproduction). See the <a href="docs/limitations.html">limitations</a> and <a href="docs/certification.html">certification report</a>.</p></div></section>
`;
writeFileSync(join(OUT, 'index.html'), shell({ title: META.title, base: '', siteRoot: '../', body: landing }));

console.log(`Delivery zip entries: ${zipEntries.length}`);
console.log(`Published outcome -> published/${OUT_DIR}/index.html  (browse: app.html)`);
console.log(`Status ${META.status} · evidence ${META.evidenceGrade} · trust ${META.trustScore} · capability ${META.capabilityGrade}`);
