// lib/tools/adapters/mines/investor-presentation.js
//
// Investor-friendly single-scroll presentation microsite for a Mines project.
//
// The technical surface lives at /mines/[id]/results (NI 43-101 tables, data
// quality, provenance). THIS is the accessible counterpart: an editorial
// investment-memo microsite a non-technical stakeholder reads cold.
//
// Output is one self-contained HTML string (inline CSS + a tiny scroll-reveal
// script) that renders both standalone and inside the results-page sandboxed
// iframe (srcDoc, allow-scripts). All figures come from cited DB rows / labelled
// operator scenarios — never invented. The optional `narrative` argument carries
// LLM-authored prose (authored by the mines-report-packager agent); every slot
// has a data-driven fallback so the builder also works with no narrative.

// Belt comparables — real public S-K 1300 / 43-101 §13 figures for the
// Cananea–La Caridad district. These establish the geological model and the
// in-ground value range for a successful porphyry discovery; they are NOT a
// prediction of this asset's tonnage or grade.
const COMPARABLES = [
  { name: 'La Caridad', status: 'Producing', dist: '~25 km SE', tonnes: '2,100 Mt', cu: '0.21% Cu', note: '9,737 Mlb contained Cu' },
  { name: 'Cananea / Buenavista', status: 'Producing', dist: 'Same belt', tonnes: '2,413 Mt', cu: '0.44% Cu', note: 'one of the largest Cu mines on earth' },
  { name: 'La Colorada breccia', status: 'Historic', dist: 'Same belt', tonnes: '7 Mt', cu: '6.0% Cu', note: 'high-grade breccia analogue' },
];

// ── formatting ────────────────────────────────────────────────────────────────
function esc(s) {
  return String(s ?? '')
    .replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;').replaceAll("'", '&#39;');
}
function num(v, d = 0) {
  if (v == null || !Number.isFinite(Number(v))) return '—';
  return new Intl.NumberFormat('en-US', { maximumFractionDigits: d }).format(Number(v));
}
function usd(n) {
  if (n == null || !Number.isFinite(Number(n))) return '—';
  const v = Number(n);
  if (v >= 1e9) return `$${(v / 1e9).toFixed(2)}B`;
  if (v >= 1e6) return `$${Math.round(v / 1e6)}M`;
  if (v >= 1e3) return `$${Math.round(v / 1e3)}k`;
  return `$${Math.round(v)}`;
}
function jp(s) { try { return JSON.parse(s) || {}; } catch { return {}; } }

// ── data load ───────────────────────────────────────────────────────────────
function loadData(db, project) {
  const pid = project.id;
  const all = (sql, ...a) => db.prepare(sql).all(...a);
  const one = (sql, ...a) => db.prepare(sql).get(...a);

  const facts = all('SELECT fact_type, value_text, page_or_location FROM mines_extracted_facts WHERE project_id = ?', pid);
  const factBy = new Map();
  for (const f of facts) if (!factBy.has(f.fact_type)) factBy.set(f.fact_type, f);

  const scen = all(
    "SELECT scenario, assumptions_json, result_json FROM mines_valuation_scenarios WHERE project_id = ? AND scenario LIKE 'operator_%'", pid
  ).map(r => ({ key: r.scenario.replace('operator_', ''), a: jp(r.assumptions_json), r: jp(r.result_json) }));
  const order = ['low', 'base', 'high'];
  scen.sort((x, y) => order.indexOf(x.key) - order.indexOf(y.key));

  const intercepts = all(
    `SELECT sample_id, width_m, cu_pct, ag_ppm, other_grades_json
       FROM mines_assay_samples WHERE project_id = ? AND sample_type = 'drill_core'
      ORDER BY (cu_pct * width_m) DESC LIMIT 5`, pid
  );
  const trench = one(
    `SELECT COUNT(*) n, AVG(cu_pct) avg_cu, MAX(cu_pct) max_cu, AVG(ag_ppm) avg_ag
       FROM mines_assay_samples WHERE project_id = ? AND sample_type = 'trench_channel'`, pid
  );
  const drill = one('SELECT COUNT(*) n, SUM(total_depth_m) m FROM mines_drill_holes WHERE project_id = ? AND extracted_by_agent_id = ?', pid, 'mines-geo-analyst');

  const targets = facts.filter(f => f.fact_type.startsWith('target_'))
    .map(f => ({ name: f.fact_type.replace('target_', '').replace(/_/g, ' '), text: f.value_text }));

  const price = (c) => one('SELECT price_usd, source FROM mines_market_snapshots WHERE lower(commodity)=lower(?) ORDER BY fetched_at DESC LIMIT 1', c);

  return {
    factBy, scen, intercepts, trench, drill, targets,
    priceCu: price('Cu'), priceAg: price('Ag'),
    base: scen.find(s => s.key === 'base') || scen[0] || null,
  };
}

// ── narrative defaults (used when the agent supplies no prose) ───────────────
function defaultNarrative(project, d) {
  const lo = d.scen[0]?.r?.in_situ_value_usd, hi = d.scen[d.scen.length - 1]?.r?.in_situ_value_usd;
  const fact = (t) => d.factBy.get(t)?.value_text || '';
  return {
    hero_headline: 'District-scale copper optionality, next to two world-class mines.',
    hero_subhead:
      `${project.name} sits in the Cananea–La Caridad porphyry belt of Sonora, Mexico — one of the most productive copper districts on earth. Four drill-confirmed targets, one untested high-priority blanket, and no resource yet declared: the value today is exploration optionality.`,
    opportunity: [
      `The case for ${project.name} is location and evidence. It lies in the same belt — and within ~25 km — of La Caridad and Cananea, two of the largest copper mines in the world. The same geology that built them runs through this ground.`,
      `Teck drilled it. Their 2011–2012 program returned copper across multiple targets and walked away mid-story, leaving the highest-priority target (a chalcocite blanket at El Choclo NW) untested. That is the optionality a partner is buying.`,
      `No mineral resource has been declared, and we do not pretend one exists. What follows is the evidence on the ground and a clearly-labelled conceptual scenario — not a valuation opinion.`,
    ],
    findings_lead:
      'The asset is not a concept on a map. There is drilled copper, surface geochemistry, and a geological model anchored to producing neighbours.',
    upside_lead:
      'Four defined targets, each a distinct shot on goal. El Choclo NW — a leached-capping blanket target — was proposed for drilling but never tested before Teck withdrew.',
    risk_lead:
      'We lead with the risk because credibility is the point. This is exploration: no declared resource, grade continuity at depth unproven. The mitigation is a focused, sequenced work program.',
    the_ask:
      `${project.name}'s operator (Grupo Factor) is seeking an incoming partner under NDA. The near-term path: re-interpret the geophysics, vector with clay-alteration (SWIR) core analysis, then drill the untested El Choclo NW blanket — and, if it confirms, define a maiden resource.`,
    value_range_label: (lo != null && hi != null) ? `${usd(lo)} – ${usd(hi)}` : '—',
  };
}

// ── section renderers ─────────────────────────────────────────────────────────
function kicker(label) {
  return `<span class="kick"><svg class="kick-mark" width="22" height="8" viewBox="0 0 22 8" aria-hidden="true"><path d="M0 4h14" stroke="currentColor" stroke-width="1.4"/><circle cx="19" cy="4" r="2.4" fill="currentColor"/></svg>${esc(label)}</span>`;
}

function heroSection(project, d, n) {
  const chips = [
    project.deposit_type || 'Porphyry copper (±Ag)',
    `${project.asset_stage || 'exploration'} stage`,
    `${project.state_province || 'Sonora'}, ${project.country || 'Mexico'}`,
  ].map(c => `<span class="chip">${esc(c)}</span>`).join('');
  return `
  <header class="hero reveal">
    ${kicker('MadarX · Mines · Investor brief')}
    <h1 class="hero-title">${esc(project.name)}</h1>
    <p class="hero-head">${esc(n.hero_headline)}</p>
    <p class="hero-sub">${esc(n.hero_subhead)}</p>
    <div class="hero-value">
      <span class="hv-label">Indicative value range</span>
      <span class="hv-num">${esc(n.value_range_label)}</span>
      <span class="hv-note">conceptual exploration target — not a mineral resource</span>
    </div>
    <div class="chips">${chips}</div>
  </header>`;
}

function opportunitySection(n) {
  const paras = (n.opportunity || []).map(p => `<p>${esc(p)}</p>`).join('');
  return `
  <section class="band">
    <div class="band-head reveal">${kicker('The opportunity')}<h2>Why this ground</h2></div>
    <div class="prose reveal">${paras}</div>
  </section>`;
}

function valueSection(d, n) {
  if (!d.scen.length) return '';
  const cards = d.scen.map(s => {
    const emphasized = s.key === 'base';
    const mt = s.a?.resource_tonnes != null ? `${num(s.a.resource_tonnes / 1e6)} Mt` : '—';
    const gr = s.a?.grade_cu_pct != null ? `${num(s.a.grade_cu_pct, 2)}% Cu` : '';
    return `
    <div class="vcard${emphasized ? ' vcard-base' : ''} reveal">
      <span class="vcard-key">${esc(s.key)} scenario</span>
      <span class="vcard-num">${esc(usd(s.r?.in_situ_value_usd))}</span>
      <span class="vcard-meta">${esc(mt)}${gr ? ' @ ' + esc(gr) : ''}</span>
    </div>`;
  }).join('');
  const cu = d.priceCu?.price_usd, ag = d.priceAg?.price_usd;
  return `
  <section class="band">
    <div class="band-head reveal">${kicker('Indicative value')}<h2>A range, honestly labelled</h2></div>
    <div class="disclaimer reveal">No resource is declared, so no definitive valuation is produced. The figures below are an operator-supplied conceptual exploration target (tonnage and grade as a range) — order-of-magnitude in-situ optionality, <strong>not</strong> a mineral resource, reserve, or valuation opinion.</div>
    <div class="vgrid">${cards}</div>
    <p class="fineprint reveal">In-situ = contained recoverable metal × price × a 3% in-situ factor (exploration stage). Prices: Cu ${esc(usd(cu))}/lb · Ag ${esc(usd(ag))}/oz${d.priceCu?.source ? ` (${esc(d.priceCu.source)})` : ''}.</p>
  </section>`;
}

function findingsSection(d, n) {
  const rows = d.intercepts.map(i => {
    const o = jp(i.other_grades_json);
    const area = o.area ? `<span class="ic-area">${esc(o.area)}</span>` : '';
    return `
    <li class="ic reveal">
      <span class="ic-hole">${esc(i.sample_id)}</span>
      <span class="ic-stat"><b>${esc(num(i.width_m, 1))}</b> m</span>
      <span class="ic-stat"><b>${esc(num(i.cu_pct, 2))}%</b> Cu</span>
      <span class="ic-stat">${esc(num(i.ag_ppm, 1))} g/t Ag</span>
      ${area}
    </li>`;
  }).join('');
  const comps = COMPARABLES.map(c => `
    <li class="comp reveal">
      <span class="comp-name">${esc(c.name)}</span>
      <span class="comp-status">${esc(c.status)} · ${esc(c.dist)}</span>
      <span class="comp-fig"><b>${esc(c.tonnes)}</b> @ ${esc(c.cu)}</span>
      <span class="comp-note">${esc(c.note)}</span>
    </li>`).join('');
  const trenchLine = d.trench?.n
    ? `<p class="prose reveal">Surface trenching adds the third leg: ${esc(num(d.trench.n))} channel samples averaging <b>${esc(num(d.trench.avg_cu, 3))}% Cu</b>, peaking at ${esc(num(d.trench.max_cu, 2))}% Cu.</p>`
    : '';
  return `
  <section class="band">
    <div class="band-head reveal">${kicker('What we found')}<h2>Proof, not promises</h2></div>
    <p class="prose reveal">${esc(n.findings_lead)}</p>
    <div class="two-col">
      <div>
        <h3 class="col-h reveal">Best drill intercepts</h3>
        <ul class="ic-list">${rows || '<li class="ic reveal"><span class="ic-hole">No drill intercepts recorded</span></li>'}</ul>
      </div>
      <div>
        <h3 class="col-h reveal">The neighbours</h3>
        <ul class="comp-list">${comps}</ul>
      </div>
    </div>
    ${trenchLine}
  </section>`;
}

function upsideSection(d, n) {
  if (!d.targets.length) return '';
  const items = d.targets.map((t, i) => {
    const priority = /nw|choclo nw/i.test(t.name);
    return `
    <li class="tgt${priority ? ' tgt-priority' : ''} reveal">
      <span class="tgt-no">${String(i + 1).padStart(2, '0')}</span>
      <div class="tgt-body">
        <span class="tgt-name">${esc(t.name)}${priority ? '<span class="tgt-flag">untested · high priority</span>' : ''}</span>
        <span class="tgt-text">${esc(t.text)}</span>
      </div>
    </li>`;
  }).join('');
  return `
  <section class="band">
    <div class="band-head reveal">${kicker('Exploration upside')}<h2>Four shots on goal</h2></div>
    <p class="prose reveal">${esc(n.upside_lead)}</p>
    <ul class="tgt-list">${items}</ul>
  </section>`;
}

function askSection(n) {
  const tick = '<svg class="tick" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 8.5l3 3 7-8" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>';
  const steps = [
    'Re-interpret existing geophysics across all four targets',
    'Clay-alteration (SWIR) core analysis to vector the porphyry centre',
    'Drill the untested El Choclo NW chalcocite-blanket target',
    'Define a maiden mineral resource if Phase-1 confirms continuity',
  ].map(s => `<li class="step reveal">${tick}<span>${esc(s)}</span></li>`).join('');
  return `
  <section class="band band-ask">
    <div class="band-head reveal">${kicker('Risk & the ask')}<h2>What we are seeking</h2></div>
    <p class="prose reveal risk">${esc(n.risk_lead)}</p>
    <p class="prose reveal">${esc(n.the_ask)}</p>
    <h3 class="col-h reveal">Near-term work program</h3>
    <ul class="step-list">${steps}</ul>
  </section>`;
}

// ── shell ─────────────────────────────────────────────────────────────────────
function shell(project, body) {
  const today = new Date().toISOString().slice(0, 10);
  return `<!doctype html><html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<script>document.documentElement.className+=' js';</script>
<title>${esc(project.name)} — Investor brief</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=Spectral:wght@300;400;500&family=Hanken+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
  :root{
    --ink:#111318; --sub:#5b626c; --faint:#8a8f99; --mars:#c2452a;
    --line:rgba(17,19,24,.09); --line-soft:rgba(17,19,24,.06);
    --bg:#ffffff; --bg-warm:#fcfcfb; --bg-amber:#fffaf0;
    --serif:'Spectral',Iowan Old Style,Palatino,Georgia,serif;
    --sans:'Hanken Grotesk',system-ui,-apple-system,Segoe UI,sans-serif;
    --mono:ui-monospace,SFMono-Regular,Menlo,monospace;
    --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-6:24px; --sp-8:32px; --sp-12:48px; --sp-16:64px; --sp-24:96px;
  }
  *{box-sizing:border-box}
  html{-webkit-text-size-adjust:100%}
  body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);
    font-size:17px;line-height:1.65;-webkit-font-smoothing:antialiased;font-feature-settings:"kern";}
  .wrap{max-width:1080px;margin:0 auto;padding:clamp(32px,6vw,80px) clamp(20px,5vw,56px) clamp(64px,10vw,128px);}
  .kick{display:inline-flex;align-items:center;gap:9px;color:var(--mars);font-family:var(--mono);
    font-size:11px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;margin-bottom:14px;}
  .kick-mark{flex:none}
  h2{font-family:var(--serif);font-weight:400;font-size:clamp(1.7rem,3.6vw,2.7rem);letter-spacing:-.02em;
    line-height:1.05;margin:0 0 var(--sp-8);color:var(--ink);}
  h3.col-h{font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.13em;text-transform:uppercase;
    color:var(--faint);margin:0 0 var(--sp-4);}

  /* hero */
  .hero{padding-bottom:clamp(40px,7vw,88px);border-bottom:1px solid var(--line);}
  .hero-title{font-family:var(--serif);font-weight:300;font-size:clamp(3.2rem,11vw,7.5rem);
    letter-spacing:-.045em;line-height:.92;margin:0 0 var(--sp-6);}
  .hero-head{font-family:var(--serif);font-weight:400;font-size:clamp(1.4rem,3.4vw,2.2rem);
    line-height:1.18;letter-spacing:-.01em;max-width:18ch;margin:0 0 var(--sp-6);color:var(--ink);}
  .hero-sub{font-size:clamp(1rem,1.6vw,1.18rem);color:var(--sub);max-width:62ch;margin:0 0 var(--sp-12);}
  .hero-value{display:inline-flex;flex-direction:column;gap:6px;padding:22px 28px;border:1px solid var(--line);
    border-radius:16px;background:var(--bg-warm);}
  .hv-label{font-family:var(--mono);font-size:10px;font-weight:600;letter-spacing:.13em;text-transform:uppercase;color:var(--faint);}
  .hv-num{font-family:var(--serif);font-weight:300;font-size:clamp(2.4rem,6vw,3.6rem);letter-spacing:-.04em;
    line-height:1;color:var(--mars);font-feature-settings:"tnum","lnum";}
  .hv-note{font-size:12px;color:var(--faint);}
  .chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:var(--sp-8);}
  .chip{display:inline-flex;padding:5px 13px;border:1px solid var(--line);border-radius:999px;
    font-size:12.5px;font-weight:600;color:var(--sub);}

  /* bands */
  .band{padding-top:clamp(48px,8vw,104px);}
  .band-head{margin-bottom:var(--sp-8);}
  .prose{font-size:clamp(1.02rem,1.5vw,1.18rem);color:#374049;max-width:64ch;}
  .prose p{margin:0 0 var(--sp-6);}
  .prose p:last-child{margin-bottom:0;}
  .prose.risk{color:var(--ink);}
  .disclaimer{font-size:14px;line-height:1.6;color:#5a4a2a;background:var(--bg-amber);
    border:1px solid rgba(154,106,18,.22);border-radius:14px;padding:16px 20px;max-width:70ch;margin-bottom:var(--sp-8);}
  .fineprint{font-size:12.5px;color:var(--faint);margin-top:var(--sp-6);max-width:64ch;}

  /* value cards — asymmetric, base emphasized */
  .vgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--sp-4);align-items:end;}
  .vcard{display:flex;flex-direction:column;gap:8px;padding:22px 24px;border:1px solid var(--line-soft);
    border-radius:16px;background:var(--bg);}
  .vcard-base{background:var(--bg-amber);border-color:rgba(194,69,42,.22);padding:32px 26px;}
  .vcard-key{font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:.13em;text-transform:uppercase;color:var(--faint);}
  .vcard-base .vcard-key{color:var(--mars);}
  .vcard-num{font-family:var(--serif);font-weight:300;font-size:clamp(2rem,4.4vw,3rem);letter-spacing:-.04em;
    line-height:1;font-feature-settings:"tnum","lnum";}
  .vcard-meta{font-size:13px;color:var(--sub);}

  /* findings two-column */
  .two-col{display:grid;grid-template-columns:1.1fr .9fr;gap:clamp(24px,5vw,64px);margin-top:var(--sp-6);}
  ul{list-style:none;margin:0;padding:0;}
  .ic-list,.comp-list{display:flex;flex-direction:column;}
  .ic{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 16px;padding:14px 0;border-top:1px solid var(--line-soft);}
  .ic:first-child{border-top:none;}
  .ic-hole{font-family:var(--mono);font-size:13px;font-weight:600;color:var(--ink);min-width:9.5em;}
  .ic-stat{font-size:14px;color:var(--sub);}
  .ic-stat b{font-family:var(--serif);font-weight:500;font-size:17px;color:var(--ink);letter-spacing:-.01em;}
  .ic-area{font-family:var(--mono);font-size:10.5px;color:var(--faint);text-transform:uppercase;letter-spacing:.06em;margin-left:auto;}
  .comp{display:grid;gap:2px;padding:14px 0;border-top:1px solid var(--line-soft);}
  .comp:first-child{border-top:none;}
  .comp-name{font-weight:700;font-size:15px;}
  .comp-status{font-size:12px;color:var(--faint);}
  .comp-fig{font-size:13.5px;color:var(--sub);}
  .comp-fig b{font-family:var(--serif);font-weight:500;color:var(--ink);}
  .comp-note{font-size:12.5px;color:var(--faint);font-style:italic;}

  /* targets */
  .tgt-list{margin-top:var(--sp-6);display:flex;flex-direction:column;}
  .tgt{display:flex;gap:var(--sp-6);padding:22px 0;border-top:1px solid var(--line);}
  .tgt:first-child{border-top:none;}
  .tgt-no{font-family:var(--mono);font-size:13px;color:var(--faint);padding-top:4px;}
  .tgt-priority .tgt-no{color:var(--mars);}
  .tgt-body{display:flex;flex-direction:column;gap:6px;}
  .tgt-name{font-family:var(--serif);font-size:1.3rem;font-weight:500;letter-spacing:-.01em;text-transform:capitalize;display:flex;flex-wrap:wrap;align-items:center;gap:12px;}
  .tgt-flag{font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
    color:var(--mars);border:1px solid rgba(194,69,42,.32);border-radius:999px;padding:3px 9px;}
  .tgt-text{font-size:15px;color:#374049;max-width:62ch;}

  /* ask */
  .band-ask{margin-top:clamp(48px,8vw,104px);padding-top:clamp(48px,8vw,104px);border-top:1px solid var(--line);}
  .step-list{margin-top:var(--sp-4);display:flex;flex-direction:column;gap:var(--sp-3);}
  .step{display:flex;align-items:flex-start;gap:12px;font-size:16px;color:var(--ink);}
  .tick{flex:none;margin-top:4px;color:var(--mars);}

  /* footer */
  .foot{margin-top:clamp(48px,8vw,96px);padding-top:var(--sp-8);border-top:1px solid var(--line);
    font-size:12px;line-height:1.7;color:var(--faint);max-width:74ch;}

  /* motion — progressive enhancement only.
     Default state is VISIBLE so the deck reads with no JS (iOS QuickLook,
     Mail / Files attachment preview, print, downloaded .html opened offline).
     The scroll-reveal animation is armed only once the head script adds .js
     to <html>; in any no-JS context the content never hides. */
  .reveal{opacity:1;transform:none;}
  .js .reveal{opacity:0;transform:translateY(16px);transition:opacity .7s cubic-bezier(.22,1,.36,1),transform .7s cubic-bezier(.22,1,.36,1);}
  .js .reveal.in{opacity:1;transform:none;}
  @media (prefers-reduced-motion:reduce){.js .reveal{opacity:1;transform:none;transition:none;}}

  @media (max-width:760px){
    body{font-size:16px;}
    .vgrid{grid-template-columns:1fr;}
    .vcard-base{padding:24px;}
    .two-col{grid-template-columns:1fr;}
    .ic-area{margin-left:0;}
  }
</style></head>
<body>
  <main class="wrap">
${body}
    <footer class="foot">
      <strong style="color:var(--sub)">How this was made.</strong> Generated by MadarX OS · Mines from the project's NI 43-101 technical report (effective 09 Jun 2025) and structured database. Every figure traces to a database row or a labelled operator scenario. Comparable-asset figures are from public S-K 1300 reports for neighbouring deposits. Metal prices are dated market snapshots. The operator scenario is a conceptual exploration target, <strong>not</strong> a mineral resource, reserve, or valuation opinion. This brief is decision-support analysis, not investment advice. · ${esc(today)}
    </footer>
  </main>
  <script>
    (function(){
      var els=[].slice.call(document.querySelectorAll('.reveal'));
      if(!('IntersectionObserver' in window)){els.forEach(function(e){e.classList.add('in');});return;}
      var io=new IntersectionObserver(function(entries){
        entries.forEach(function(en){if(en.isIntersecting){en.target.classList.add('in');io.unobserve(en.target);}});
      },{threshold:.12,rootMargin:'0px 0px -8% 0px'});
      var i=0;
      els.forEach(function(e){ e.style.transitionDelay=Math.min(i*40,160)+'ms'; i++; io.observe(e); });
    })();
  </script>
</body></html>`;
}

/**
 * Build the investor presentation microsite for a project.
 * @param {object} db        open DatabaseSync handle
 * @param {object} project   mines_projects row
 * @param {object} [narrative] optional LLM-authored prose slots
 * @returns {{ html:string, meta:object }}
 */
export function buildInvestorPresentation(db, project, narrative = {}) {
  const d = loadData(db, project);
  const n = { ...defaultNarrative(project, d), ...(narrative || {}) };
  // value_range_label is always derived from DB scenarios (never LLM-authored).
  n.value_range_label = defaultNarrative(project, d).value_range_label;

  const body = [
    heroSection(project, d, n),
    opportunitySection(n),
    valueSection(d, n),
    findingsSection(d, n),
    upsideSection(d, n),
    askSection(n),
  ].join('\n');

  const html = shell(project, body);
  const meta = {
    scenario_rows: d.scen.length,
    intercepts: d.intercepts.length,
    targets: d.targets.length,
    comparables: COMPARABLES.length,
    narrative_authored: Object.keys(narrative || {}).length > 0,
  };
  return { html, meta };
}
