// pdf-2026.jsx — Génération PDF V5 (8 pages, design Claude Design Phase 5)
// Exposé : window.PDF_2026 = { generate(state, calc) → Promise<{blob,url,filename}> }
//
// Stratégie V5 :
//  1) buildPdfHtml(state, calc) → HTML avec 8 pages .r5pdf-page (format Letter 816×1056px)
//  2) html2canvas rend CHAQUE page séparément (page-breaks propres, pas de slicing)
//  3) jsPDF empaquette en Letter (216 × 279 mm), 1 canvas = 1 page
//  4) Upload Supabase Storage si dispo, sinon download direct
//
// Pages : 1 Cover · 2 Synthèse · 3 Détail · 4 Subventions · 5 Économies 25 ans
//         6 Financement · 7 CGV+signature · 8 Datasheet Thornova
// (5 et 8 omises si pas de solaire → 6 pages pour borne/batterie seule)
//
// NOTE html2canvas : pas de background-clip:text (rendu invisible) → couleurs solides.

(function () {
  const NB = " ";

  // ============================================================================
  // HELPERS
  // ============================================================================
  function escapeHtml(s) {
    if (s == null) return "";
    return String(s)
      .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;").replace(/'/g, "&#39;");
  }
  // entier avec espaces insécables, sans symbole : 87 420
  function num0(n) {
    if (n == null || isNaN(n)) return "—";
    return Math.round(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, NB);
  }
  function money(n) {
    const C = window.RAYCAST_CALC_2026;
    return C ? C.fmtCAD0(n) : (num0(n) + NB + "$");
  }

  // Auto-scope fallback (si state.scope_text vide)
  function buildAutoScope(state, calc) {
    const parts = [];
    if (calc?.solarCalc && calc.solar_sell > 0) {
      parts.push(`système solaire ${calc.solarCalc.kw} kW (${calc.solarCalc.panneaux} panneaux Thornova 500W) sur toiture ${state.solar.roof_type}`);
    }
    if (calc?.battery_sell > 0) parts.push(`stockage ${state.solar?.battery_brand || ""}`);
    if (calc?.borneCalc && calc.borneCalc.qty > 0) parts.push(`${calc.borneCalc.qty} borne(s) VÉ`);
    if (!parts.length) return "Soumission préparée par Raycom Électrique.";
    const city = state.client?.address_city || "QC";
    return `Installation ${parts.join(" + ")} au ${state.client?.address_civic || ""} ${state.client?.address_street || ""}, ${city}.`;
  }

  // Titre dynamique de la cover selon le type de projet
  function projectTitle(state, calc) {
    const pt = state.project_type;
    const brandLabel = { tesla: "Tesla", sig: "Sigenergy", fox_aio: "Fox ESS", fox_h1: "Fox ESS" }[state.solar?.battery_brand] || "";
    if (pt === "borne") return "Borne de recharge VÉ";
    if (pt === "batterie") return `Système ESS ${brandLabel}`.trim();
    if (pt === "solaire") return "Système solaire photovoltaïque";
    // solaire_batterie
    return `Solaire${NB}+ Batteries${brandLabel ? " " + brandLabel : ""}`;
  }
  function projectSubtitle(state, calc) {
    const kw = calc?.solarCalc?.kw || 0;
    const city = state.client?.address_city || "Québec";
    if (state.project_type === "borne") {
      return `Installation d'une borne de recharge niveau 2 par un maître électricien membre de la CMEQ. Résidence à ${city}.`;
    }
    const batt = calc?.batteryOnlyCalc || calc?.battery_sell > 0;
    return `Système de production solaire ${kw}${NB}kW${batt ? " couplé à un stockage d'énergie avec backup" : " en mesurage net"}. Conçu, fourni et installé par un maître électricien membre de la CMEQ. Résidence à ${city}.`;
  }

  // Groupes de détail — REFONTE 2026-06-10 : 1 ligne = 1 produit réel { code, qty, name, desc, pu, price }.
  // Le code vient TOUJOURS d'une source existante (breakdown calc / data-2026 / forfait). Jamais inventé : si absent → "[code à confirmer]" (matériel) ou "" (main-d'œuvre).
  const NEEDCODE = "[code QBO à confirmer]";
  function buildGroups(state, calc) {
    const D = window.RAYCOM_DATA_2026 || {};
    const g = [];

    // === SOLAIRE (panneaux — matériel) ===
    if (calc?.solarCalc && calc.solar_sell > 0) {
      const sc = calc.solarCalc;
      const pan = D.panneau_ref || {};
      const n = sc.panneaux || 0;
      g.push({
        title: `Solaire · ${n} panneaux · ${sc.kw} kW DC`,
        sub: money(calc.solar_sell),
        rows: [{
          code: pan.sku || NEEDCODE,
          qty: `${n}×`,
          name: pan.name || "Thornova BBT54 500W All Black Bifacial",
          desc: `Monocristallin bifacial TopCon n-type · pente ${state.solar?.pitch_key || "—"} · garantie produit 25 ans`,
          pu: n > 0 ? money(calc.solar_sell / n) : "",
          price: money(calc.solar_sell)
        }]
      });
    }

    // === STOCKAGE (Powerwall / Fox / Sig) — ventilé via breakdown calc ===
    const bc = calc?.batteryOnlyCalc;
    if (bc && calc.battery_sell > 0 && Array.isArray(bc.breakdown) && bc.breakdown.length) {
      const bn = bc.brand === "tesla" ? "Tesla Powerwall" : bc.brand === "sig" ? "Sigenergy" : "Fox ESS";
      g.push({
        title: `Stockage ${bn} · ${(bc.kwh || 0).toFixed(1).replace(".", ",")} kWh`,
        sub: money(calc.battery_sell),
        rows: bc.breakdown.map(b => ({
          code: b.code || (b.kind === "labor" ? "" : NEEDCODE),
          qty: `${b.qty || 1}×`,
          name: b.label,
          desc: b.kind === "labor" ? "Main-d'œuvre" : "",
          pu: (b.qty > 1 && b.unit_sell) ? money(b.unit_sell) : "",
          price: money(b.sell)
        }))
      });
    } else if (calc?.battery_sell > 0) {
      // PLUS DE LIGNE GÉNÉRIQUE « Système de stockage » : on signale visiblement le trou de données.
      g.push({
        title: "Stockage d'énergie",
        sub: money(calc.battery_sell),
        rows: [{ code: "", qty: "1×", name: "⚠ Détail batterie indisponible — régénérer la soumission", desc: "Breakdown produit (marque/modèle/codes) non calculé. Ne pas transmettre au client tel quel.", pu: "", price: money(calc.battery_sell) }]
      });
    }

    // === INSTALLATION / FORFAIT (onduleur + mise en service + main-d'œuvre, pour solaire) ===
    if (calc?.forfait) {
      const rows = [{
        code: calc.forfait.code || "",
        qty: "1×",
        name: calc.forfait.label,
        desc: `Onduleur + mise en service + protections + main-d'œuvre (garantie 2 ans) · zone ${calc.zone?.zone_key || "Z1"}`,
        pu: "",
        price: money(calc.install_sell)
      }];
      if (calc.zone_sell > 0) rows.push({ code: "", qty: "—", name: `Déplacement ${calc.zone.zone_key}`, desc: calc.zone.label, pu: "", price: money(calc.zone_sell) });
      g.push({ title: "Installation", sub: money(calc.install_sell + (calc.zone_sell || 0)), rows });
    }

    // === BORNE DE RECHARGE VÉ — ventilée (modèle + forfait base + câblage par type + DCC) ===
    if (calc?.borneCalc && calc.borne_sell > 0) {
      const b = calc.borneCalc;
      const qty = b.qty || 1;
      const cablesSell = (b.cables_breakdown || []).reduce((s, c) => s + (c.sell || 0), 0);
      const baseOnly = (b.forfait_sell || 0) - cablesSell; // forfait base seul (≈495 $/borne)
      const rows = [];
      // 1) Modèle (fourni Raycom) ou borne fournie par le client
      if (b.modele_sell > 0) {
        rows.push({ code: b.modele_sku || NEEDCODE, qty: `${qty}×`, name: b.modele_label, desc: "Borne de recharge niveau 2 fournie et installée par Raycom", pu: qty > 1 ? money(b.modele_sell) : "", price: money(b.modele_sell * qty) });
      } else if (b.modele_key === "fournie_client" || b.modele_label === "—") {
        rows.push({ code: "", qty: `${qty}×`, name: "Borne fournie par le client", desc: "Matériel fourni par le client · installation par Raycom (détaillée ci-dessous)", pu: "", price: money(0) });
      }
      // 2) Forfait installation base
      rows.push({ code: b.forfait_base_code || "", qty: "1×", name: "Forfait installation borne — base", desc: "Main-d'œuvre (≈2 h) · disjoncteur 60A 2P · connecteurs · conformité · mise en service", pu: "", price: money(baseOnly * qty) });
      // 3) Câblage par type
      (b.cables_breakdown || []).forEach(c => {
        if ((c.sell || 0) <= 0) return;
        rows.push({ code: "", qty: `${c.m} m`, name: `Câblage — ${c.label}`, desc: "Câble + conduit + protections selon parcours", pu: "", price: money((c.sell || 0) * qty) });
      });
      // 4) Contrôleur DCC (RVE) si présent
      if (b.dcc_sell > 0) {
        rows.push({ code: b.dcc_code || NEEDCODE, qty: `${qty}×`, name: b.dcc_label || "Contrôleur de charge RVE", desc: "Gestion dynamique de charge — admissible Roulez Vert", pu: qty > 1 ? money(b.dcc_sell) : "", price: money(b.dcc_sell * qty) });
      }
      g.push({ title: `Borne de recharge VÉ · ${qty} borne(s)`, sub: money(calc.borne_sell), rows });
    }

    // === OPTIONS ÉLECTRIQUES ===
    const opts = state.options_electriques || [];
    if (opts.length) {
      g.push({ title: "Options électriques", sub: money(calc.options_sell),
        rows: opts.map(o => ({ code: o.code || "", qty: `${o.qty || 1}×`, name: o.label, desc: "", pu: (o.qty > 1 && o.sell) ? money(o.sell) : "", price: money((o.sell || 0) * (o.qty || 1)) })) });
    }
    // === SERVICES ===
    const svc = state.services || [];
    if (svc.length) {
      g.push({ title: "Services", sub: money(calc.services_sell),
        rows: svc.map(s => ({ code: s.code || "", qty: `${s.qty || 1}×`, name: s.label, desc: "", pu: (s.qty > 1 && s.sell) ? money(s.sell) : "", price: money((s.sell || 0) * (s.qty || 1)) })) });
    }
    return g;
  }

  // Graphique économies 25 ans — SVG calculé depuis les vrais chiffres
  function buildSavingsChart(calc) {
    const savings25 = calc?.savings_25 || 0;
    const annual1 = calc?.savings?.total || (savings25 / 25);
    const payback = isFinite(calc?.payback) && calc.payback > 0 ? calc.payback : 0;
    const cost = calc?.net_cost > 0 ? calc.net_cost : (calc?.ttc || 0);

    // Échelle Y : arrondi au 25k supérieur
    const maxY = Math.max(25000, Math.ceil(Math.max(savings25, cost) / 25000) * 25000);
    const X0 = 40, X1 = 600, YT = 40, YB = 260;
    const xOf = (t) => X0 + (t / 25) * (X1 - X0);
    const yOf = (v) => YB - (Math.min(v, maxY) / maxY) * (YB - YT);

    // Courbe cumulative géométrique (inflation 3%/an)
    const infl = 1.03;
    let cum = 0; const pts = [[xOf(0), yOf(0)]];
    for (let t = 1; t <= 25; t++) { cum += annual1 * Math.pow(infl, t - 1); pts.push([xOf(t), yOf(cum)]); }
    const total = cum;
    const line = pts.map((p, i) => `${i === 0 ? "M" : "L"} ${p[0].toFixed(0)},${p[1].toFixed(0)}`).join(" ");
    const area = `${line} L ${X1},${YB} L ${X0},${YB} Z`;

    const costY = yOf(cost);
    const pbX = payback > 0 ? xOf(payback) : null;
    const pbStr = payback > 0 ? payback.toFixed(1).replace(".", ",") : "—";

    // Y labels (4 paliers)
    const yLabels = [1, 0.75, 0.5, 0.25, 0].map(f => {
      const v = maxY * f; const y = YB - f * (YB - YT);
      const lbl = v >= 1000 ? `${Math.round(v / 1000)}k` : "0";
      return `<text x="32" y="${(y + 4).toFixed(0)}" text-anchor="end" font-size="9" fill="#94A3B8" font-family="JetBrains Mono">${lbl}</text>
              <line x1="40" y1="${y.toFixed(0)}" x2="600" y2="${y.toFixed(0)}" stroke="${f === 0 ? "#E5E7EB" : "#F3F4F6"}" stroke-width="1"/>`;
    }).join("");

    return `
    <svg viewBox="0 0 600 280" preserveAspectRatio="none" style="width:100%;height:100%;display:block;">
      ${yLabels}
      <line x1="40" y1="${costY.toFixed(0)}" x2="600" y2="${costY.toFixed(0)}" stroke="#D1D5DB" stroke-width="2" stroke-dasharray="4 4"/>
      <text x="600" y="${(costY - 4).toFixed(0)}" text-anchor="end" font-size="9" font-weight="600" fill="#64748B">Coût net · ${num0(cost)} $</text>
      <defs>
        <linearGradient id="r5pdf-area" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#EA580C" stop-opacity=".25"/><stop offset="100%" stop-color="#F59E0B" stop-opacity=".05"/></linearGradient>
        <linearGradient id="r5pdf-stroke" x1="0" y1="0" x2="1" y2="0"><stop offset="0%" stop-color="#EA580C"/><stop offset="100%" stop-color="#B45309"/></linearGradient>
      </defs>
      <path d="${area}" fill="url(#r5pdf-area)"/>
      <path d="${line}" fill="none" stroke="url(#r5pdf-stroke)" stroke-width="2.5"/>
      ${pbX != null ? `<line x1="${pbX.toFixed(0)}" y1="40" x2="${pbX.toFixed(0)}" y2="260" stroke="#2563EB" stroke-width="1.5" stroke-dasharray="3 3"/>
        <circle cx="${pbX.toFixed(0)}" cy="${costY.toFixed(0)}" r="6" fill="#2563EB" stroke="#fff" stroke-width="2"/>
        <text x="${pbX.toFixed(0)}" y="30" text-anchor="middle" font-size="9" font-weight="700" fill="#2563EB">Payback · ${pbStr} ans</text>` : ""}
      <circle cx="600" cy="${yOf(total).toFixed(0)}" r="6" fill="#C2410C" stroke="#fff" stroke-width="2"/>
      <text x="600" y="${(yOf(total) - 12).toFixed(0)}" text-anchor="end" font-size="11" font-weight="700" fill="#C2410C" font-family="Inter">${num0(total)} $</text>
      <text x="40" y="278" font-size="9" fill="#94A3B8" font-family="JetBrains Mono">An 0</text>
      <text x="264" y="278" text-anchor="middle" font-size="9" fill="#94A3B8" font-family="JetBrains Mono">10</text>
      <text x="488" y="278" text-anchor="middle" font-size="9" fill="#94A3B8" font-family="JetBrains Mono">20</text>
      <text x="600" y="278" text-anchor="end" font-size="9" fill="#94A3B8" font-family="JetBrains Mono">25 ans</text>
    </svg>`;
  }

  // ============================================================================
  // CSS — porté du design V5 (07-pdf-v5.html), gradient-text → couleurs solides
  // ============================================================================
  const PDF_CSS = `
  #r5pdf-root * { box-sizing: border-box; margin: 0; padding: 0; }
  #r5pdf-root {
    font-family: 'Inter', system-ui, -apple-system, sans-serif;
    color: #0F172A; -webkit-font-smoothing: antialiased;
    --r5-primary:#2563EB; --r5-primary-dark:#1E40AF; --r5-primary-tint:#EFF6FF; --r5-primary-soft:#DBEAFE;
    --r5-orange:#EA580C; --r5-orange-dark:#C2410C; --r5-orange-tint:#FFEDD5; --r5-gold-dark:#B45309;
    --r5-success:#059669; --r5-success-tint:#ECFDF5;
    --r5-ink:#0F172A; --r5-ink-2:#1E293B; --r5-ink-3:#475569; --r5-ink-4:#64748B; --r5-ink-5:#94A3B8;
    --r5-line:#E5E7EB; --r5-line-strong:#D1D5DB; --r5-line-soft:#F3F4F6; --r5-bg-soft:#FAFAF9;
  }
  .r5pdf-page {
    width: 816px; min-height: 1056px; background: #fff;
    position: relative; display: flex; flex-direction: column; overflow: hidden;
    font-size: 12pt; line-height: 1.5;
  }
  .pg-header, .pg-footer { flex-shrink: 0; padding: 18px 40px; font-size: 9pt; color: var(--r5-ink-4); }
  .pg-header { border-bottom: 1px solid var(--r5-line-soft); display: flex; justify-content: space-between; align-items: center; }
  .pg-header-brand { display: flex; align-items: center; gap: 8px; font-weight: 700; color: var(--r5-ink); font-size: 11pt; }
  .pg-header-brand .dot { width: 18px; height: 18px; background: var(--r5-primary); border-radius: 4px; display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 800; font-size: 10pt; }
  .pg-header-meta { font-family: 'JetBrains Mono', monospace; font-size: 9pt; }
  .pg-footer { border-top: 1px solid var(--r5-line-soft); display: flex; justify-content: space-between; align-items: center; margin-top: auto; }
  .pg-footer-meta { display: flex; gap: 16px; }
  .pg-body { flex: 1; padding: 32px 40px; overflow: hidden; }
  .pg-eyebrow { font-size: 9pt; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--r5-primary); margin-bottom: 8px; }
  .pg-title { font-size: 22pt; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; color: var(--r5-ink); margin: 0 0 32px; }
  /* Cover */
  .cover { background: radial-gradient(circle at 100% 100%, rgba(234,88,12,.10), transparent 50%), radial-gradient(circle at 0% 0%, rgba(37,99,235,.08), transparent 60%), linear-gradient(180deg,#FFF 0%,#FAFAF9 100%); display: flex; flex-direction: column; height: 1056px; padding: 60px; }
  .cover-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 80px; }
  .cover-brand { display: flex; align-items: center; gap: 12px; font-weight: 800; letter-spacing: -0.02em; font-size: 22pt; color: var(--r5-ink); }
  .cover-brand-mark { width: 40px; height: 40px; background: var(--r5-primary); border-radius: 10px; display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 800; font-size: 18pt; }
  .cover-rbq { text-align: right; font-size: 9pt; color: var(--r5-ink-3); }
  .cover-rbq strong { color: var(--r5-ink); font-weight: 600; }
  .cover-num { font-family: 'JetBrains Mono', monospace; font-size: 11pt; color: var(--r5-primary); font-weight: 600; margin-bottom: 4px; }
  .cover-eyebrow { font-size: 10pt; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: var(--r5-primary); margin-bottom: 16px; }
  .cover-title { font-size: 44pt; font-weight: 800; letter-spacing: -0.03em; line-height: 1; color: var(--r5-ink); margin: 0 0 14px; max-width: 560px; }
  .cover-subtitle { font-size: 14pt; color: var(--r5-ink-3); margin: 0 0 56px; max-width: 560px; line-height: 1.4; }
  .cover-client { background: #fff; border: 1px solid var(--r5-line); border-radius: 14px; padding: 24px 28px; box-shadow: 0 4px 12px rgba(0,0,0,.04); max-width: 560px; }
  .cover-client-eyebrow { font-size: 9pt; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--r5-ink-4); margin-bottom: 8px; }
  .cover-client-name { font-size: 22pt; font-weight: 700; letter-spacing: -0.02em; color: var(--r5-ink); margin-bottom: 4px; }
  .cover-client-meta { font-size: 11pt; color: var(--r5-ink-3); }
  .cover-client-meta-row { display: flex; gap: 24px; margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--r5-line-soft); font-size: 9pt; }
  .cover-client-meta-row span { display: flex; flex-direction: column; }
  .cover-client-meta-row strong { color: var(--r5-ink-4); font-size: 8pt; text-transform: uppercase; letter-spacing: .06em; font-weight: 600; margin-bottom: 4px; }
  .cover-client-meta-row span b { color: var(--r5-ink); font-size: 11pt; font-weight: 600; }
  .cover-bottom { margin-top: auto; display: flex; justify-content: space-between; align-items: flex-end; }
  .cover-tagline { font-size: 12pt; font-style: italic; color: var(--r5-ink-3); max-width: 320px; }
  .cover-tagline::before { content: '« '; color: var(--r5-orange-dark); }
  .cover-tagline::after { content: ' »'; color: var(--r5-orange-dark); }
  .cover-date { text-align: right; font-size: 10pt; color: var(--r5-ink-4); }
  .cover-date strong { display: block; color: var(--r5-ink); font-weight: 600; font-size: 11pt; margin-top: 4px; }
  /* Synthèse */
  .syn-h1 { font-size: 26pt; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; color: var(--r5-ink); margin: 0 0 8px; }
  .syn-lead { font-size: 12pt; color: var(--r5-ink-3); margin: 0 0 32px; max-width: 580px; }
  .syn-hero-kpi { background: radial-gradient(circle at top right, rgba(234,88,12,.16), transparent 55%), linear-gradient(135deg,#FEF3C7 0%,#FFEDD5 100%); border: 1px solid #FCD34D; border-radius: 14px; padding: 28px 32px; margin-bottom: 24px; }
  .syn-hero-eyebrow { font-size: 9pt; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--r5-orange-dark); margin-bottom: 10px; }
  .syn-hero-value { font-size: 56pt; font-weight: 800; letter-spacing: -0.035em; line-height: 1; color: var(--r5-orange-dark); font-variant-numeric: tabular-nums; margin-bottom: 8px; }
  .syn-hero-value small { font-size: 20pt; font-weight: 500; color: var(--r5-orange-dark); opacity: .65; margin-left: 4px; }
  .syn-hero-meta { font-size: 11pt; color: var(--r5-ink-2); }
  .syn-hero-meta strong { color: var(--r5-ink); font-weight: 600; }
  .syn-kpi-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 12px; margin-bottom: 24px; }
  .syn-kpi { background: #fff; border: 1px solid var(--r5-line); border-radius: 12px; padding: 16px 18px; }
  .syn-kpi.success { background: linear-gradient(180deg,var(--r5-success-tint) 0%,#fff 100%); border-color: #A7F3D0; }
  .syn-kpi.primary { background: linear-gradient(180deg,var(--r5-primary-tint) 0%,#fff 100%); border-color: var(--r5-primary-soft); }
  .syn-kpi-eyebrow { font-size: 8pt; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--r5-ink-4); margin-bottom: 6px; }
  .syn-kpi.success .syn-kpi-eyebrow { color: var(--r5-success); }
  .syn-kpi.primary .syn-kpi-eyebrow { color: var(--r5-primary); }
  .syn-kpi-value { font-size: 22pt; font-weight: 700; letter-spacing: -0.025em; line-height: 1; color: var(--r5-ink); font-variant-numeric: tabular-nums; }
  .syn-kpi.success .syn-kpi-value { color: var(--r5-success); }
  .syn-kpi.primary .syn-kpi-value { color: var(--r5-primary); }
  .syn-kpi-value small { font-size: 10pt; color: var(--r5-ink-4); font-weight: 500; margin-left: 4px; }
  .syn-kpi-meta { font-size: 9pt; color: var(--r5-ink-4); margin-top: 6px; }
  .syn-summary-table { background: #fff; border: 1px solid var(--r5-line); border-radius: 12px; overflow: hidden; }
  .syn-summary-row { display: grid; grid-template-columns: 1fr 120px; padding: 12px 18px; font-size: 10pt; border-top: 1px solid var(--r5-line-soft); }
  .syn-summary-row:first-child { border-top: none; }
  .syn-summary-row-label { color: var(--r5-ink-3); }
  .syn-summary-row-label small { color: var(--r5-ink-5); font-size: 8pt; display: block; margin-top: 2px; }
  .syn-summary-row-value { text-align: right; font-weight: 600; color: var(--r5-ink); font-variant-numeric: tabular-nums; }
  .syn-summary-total { background: linear-gradient(135deg,#FEF3C7 0%,#FFEDD5 100%); border-top: 2px solid #FCD34D; padding: 14px 18px; display: grid; grid-template-columns: 1fr 150px; align-items: baseline; }
  .syn-summary-total-label { font-size: 9pt; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--r5-orange-dark); }
  .syn-summary-total-value { text-align: right; font-size: 18pt; font-weight: 800; letter-spacing: -0.02em; color: var(--r5-orange-dark); font-variant-numeric: tabular-nums; }
  /* Détail */
  .detail-group { margin-bottom: 18px; background: #fff; border: 1px solid var(--r5-line); border-radius: 10px; overflow: hidden; }
  .detail-group-head { padding: 10px 16px; background: var(--r5-bg-soft); border-bottom: 1px solid var(--r5-line); display: flex; justify-content: space-between; align-items: baseline; }
  .detail-group-title { font-size: 9pt; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--r5-ink-3); }
  .detail-group-sub { font-size: 10pt; font-weight: 700; color: var(--r5-ink); font-variant-numeric: tabular-nums; }
  .detail-row { display: grid; grid-template-columns: 40px 1fr 90px; gap: 12px; padding: 8px 16px; border-top: 1px solid var(--r5-line-soft); align-items: baseline; font-size: 9.5pt; }
  .detail-row:first-child { border-top: none; }
  .detail-qty { font-family: 'JetBrains Mono', monospace; font-size: 9pt; color: var(--r5-ink-4); font-variant-numeric: tabular-nums; }
  .detail-name { color: var(--r5-ink); font-weight: 500; }
  .detail-desc { font-size: 8.5pt; color: var(--r5-ink-4); margin-top: 1px; line-height: 1.3; }
  .detail-price { text-align: right; color: var(--r5-ink); font-weight: 600; font-variant-numeric: tabular-nums; }
  .detail-note { background: var(--r5-primary-tint); border: 1px solid var(--r5-primary-soft); border-radius: 10px; padding: 14px 18px; margin-top: 18px; display: flex; gap: 12px; font-size: 9.5pt; color: var(--r5-ink-2); }
  /* Subventions */
  .subv-flow { background: radial-gradient(circle at top right, rgba(5,150,105,.08), transparent 55%), linear-gradient(180deg,#fff 0%,var(--r5-success-tint) 100%); border: 1px solid #A7F3D0; border-radius: 14px; padding: 24px 28px; margin-bottom: 24px; display: grid; grid-template-columns: 1fr 60px 1fr; gap: 16px; align-items: center; }
  .subv-col { text-align: center; }
  .subv-col-label { font-size: 9pt; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--r5-ink-4); margin-bottom: 6px; }
  .subv-col-value { font-size: 26pt; font-weight: 700; letter-spacing: -0.025em; line-height: 1; color: var(--r5-ink); font-variant-numeric: tabular-nums; }
  .subv-col-value.net { color: var(--r5-success); }
  .subv-col-sub { font-size: 9pt; color: var(--r5-ink-4); margin-top: 6px; }
  .subv-arrow { color: var(--r5-ink-5); text-align: center; }
  .subv-card { background: #fff; border: 1px solid var(--r5-line); border-radius: 10px; padding: 16px 20px; margin-bottom: 12px; }
  .subv-card-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; }
  .subv-card-name { font-size: 11pt; font-weight: 700; color: var(--r5-ink); }
  .subv-card-amount { font-size: 13pt; font-weight: 700; color: var(--r5-success); font-variant-numeric: tabular-nums; }
  .subv-card-desc { font-size: 9.5pt; color: var(--r5-ink-3); line-height: 1.4; }
  .subv-card-formula { margin-top: 8px; font-family: 'JetBrains Mono', monospace; font-size: 8.5pt; color: var(--r5-ink-4); padding: 6px 10px; background: var(--r5-bg-soft); border-radius: 4px; border: 1px solid var(--r5-line-soft); }
  /* Économies */
  .savings-chart { background: #fff; border: 1px solid var(--r5-line); border-radius: 12px; padding: 24px 28px; margin-bottom: 24px; }
  .savings-chart-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 24px; }
  .savings-chart-title { font-size: 13pt; font-weight: 700; color: var(--r5-ink); }
  .savings-chart-meta { font-size: 9pt; color: var(--r5-ink-4); }
  .savings-chart-svg-wrap { position: relative; height: 280px; }
  .savings-chart-legend { display: flex; gap: 24px; margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--r5-line-soft); font-size: 10pt; }
  .legend-dot { display: inline-flex; align-items: center; gap: 8px; color: var(--r5-ink-3); }
  .legend-dot::before { content: ''; width: 12px; height: 12px; border-radius: 3px; }
  .legend-dot.cost::before { background: var(--r5-line-strong); }
  .legend-dot.savings::before { background: var(--r5-orange); }
  .legend-dot.payback::before { background: var(--r5-primary); border-radius: 50%; height: 10px; width: 10px; }
  /* Financement */
  .pay-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 12px; margin-bottom: 24px; }
  .pay-card { background: #fff; border: 1.5px solid var(--r5-line); border-radius: 12px; padding: 18px 20px; position: relative; }
  .pay-card.recommended { border-color: var(--r5-primary); background: linear-gradient(180deg,var(--r5-primary-tint) 0%,#fff 100%); }
  .pay-card-badge { position: absolute; top: -10px; right: 16px; background: var(--r5-primary); color: #fff; font-size: 8pt; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; padding: 3px 10px; border-radius: 999px; }
  .pay-card-name { font-size: 12pt; font-weight: 700; color: var(--r5-ink); margin-bottom: 12px; }
  .pay-card-value { font-size: 20pt; font-weight: 800; letter-spacing: -0.025em; color: var(--r5-ink); font-variant-numeric: tabular-nums; line-height: 1; }
  .pay-card-value small { font-size: 10pt; font-weight: 500; color: var(--r5-ink-4); }
  .pay-card-meta { font-size: 9pt; color: var(--r5-ink-4); margin-top: 6px; }
  .pay-card-list { list-style: none; margin: 14px 0 0; padding: 12px 0 0; border-top: 1px solid var(--r5-line-soft); }
  .pay-card-list li { display: flex; align-items: flex-start; gap: 6px; font-size: 9pt; color: var(--r5-ink-3); padding: 3px 0; line-height: 1.4; }
  .pay-card-list svg { color: var(--r5-success); flex-shrink: 0; margin-top: 3px; }
  .trust-strip { margin-top: 24px; background: var(--r5-bg-soft); border: 1px solid var(--r5-line); border-radius: 12px; padding: 18px 22px; display: flex; justify-content: space-around; flex-wrap: wrap; gap: 18px; }
  .trust-item { display: flex; flex-direction: column; text-align: center; font-size: 8.5pt; color: var(--r5-ink-4); }
  .trust-item strong { color: var(--r5-ink); font-size: 9pt; font-weight: 700; margin-bottom: 2px; }
  /* CGV */
  .cgv-text { column-count: 2; column-gap: 28px; font-size: 8.5pt; line-height: 1.5; color: var(--r5-ink-3); }
  .cgv-text h4 { font-size: 9pt; font-weight: 700; color: var(--r5-ink); margin: 0 0 4px; }
  .cgv-text p { margin: 0 0 10px; }
  .sign-block { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; margin-top: 32px; padding-top: 24px; border-top: 2px solid var(--r5-ink); }
  .sign-col h4 { font-size: 10pt; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--r5-ink-4); margin: 0 0 16px; }
  .sign-line { border-bottom: 1px solid var(--r5-ink); height: 48px; margin-bottom: 8px; }
  .sign-meta { display: flex; justify-content: space-between; font-size: 9pt; color: var(--r5-ink-4); }
  /* Datasheet */
  .ds-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; }
  .ds-panel { background: linear-gradient(180deg,#1a1a1a 0%,#0a0a0a 100%); border-radius: 14px; padding: 32px; display: flex; align-items: center; justify-content: center; min-height: 280px; }
  .ds-spec-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 24px; font-size: 9.5pt; }
  .ds-spec-label { font-size: 7.5pt; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--r5-ink-4); margin-bottom: 2px; }
  .ds-spec-val { font-weight: 700; color: var(--r5-ink); font-variant-numeric: tabular-nums; }
  `;

  // SVG checkmark pour pay-card lists
  const CK = `<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>`;

  // ============================================================================
  // BUILD HTML — 8 pages
  // ============================================================================
  function buildPdfHtml(state, calc) {
    const C = window.RAYCAST_CALC_2026;
    const c = state.client || {};
    const today = new Date();
    const dateStr = today.toLocaleDateString("fr-CA", { day: "2-digit", month: "long", year: "numeric" });
    const validStr = new Date(today.getTime() + 30 * 86400000).toLocaleDateString("fr-CA", { day: "2-digit", month: "long", year: "numeric" });
    const subNum = state.number || `2026-${Date.now().toString().slice(-4)}`;
    const metaLine = `Soumission ${subNum} · ${dateStr}`;
    const hasSolar = (calc?.solarCalc?.kw || 0) > 0;

    const groups = buildGroups(state, calc);
    const grant = calc?.total_grant || 0;
    const netCost = calc?.net_cost > 0 ? calc.net_cost : (calc?.ttc || 0);
    const ges = (calc?.production || 0) * 0.000688;
    const payback = isFinite(calc?.payback) && calc.payback > 0 ? calc.payback.toFixed(1).replace(".", ",") : "—";
    const repName = state.rep_name || "Marc-André Rivest, représentant";

    const header = `<div class="pg-header"><div class="pg-header-brand"><span class="dot">R</span>RAYCOM</div><div class="pg-header-meta">${escapeHtml(metaLine)}</div></div>`;
    const footer = (n, tot) => `<div class="pg-footer"><div>Raycom Électrique inc. · Mascouche · (450) 474-8470 · info@raycomelectrique.com</div><div class="pg-footer-meta"><span>RBQ 5590-9402</span><span>${n} / ${tot}</span></div></div>`;
    const totalPages = hasSolar ? 8 : 6;
    // Bannière config explicite (M8) : ex. « Solaire 10,5 kW + Stockage Tesla Powerwall + Borne de recharge VÉ »
    const configLine = groups.filter(g => !/^(Installation|Options|Services|Déplacement)/.test(g.title)).map(g => g.title.split(" · ").slice(0, 2).join(" · ").replace(/^Solaire$/, "Solaire")).join("  +  ") || projectTitle(state, calc);

    // ---- PAGE 1 : Cover
    const coverHtml = `
    <div class="r5pdf-page"><div class="cover">
      <div class="cover-top">
        <div class="cover-brand"><div class="cover-brand-mark">R</div>RAYCOM</div>
        <div class="cover-rbq"><div class="cover-num">${escapeHtml(subNum)}</div><strong>RBQ 5590-9402</strong><br>sous-cat. 11.2 &amp; 13.5 · CMEQ</div>
      </div>
      <div class="cover-eyebrow">Soumission · Projet d'énergie</div>
      <h1 class="cover-title">${projectTitle(state, calc)}</h1>
      <p class="cover-subtitle">${escapeHtml(projectSubtitle(state, calc))}</p>
      <div style="display:inline-block;margin-top:4px;padding:7px 14px;background:#F1F5F9;border:1px solid #E2E8F0;border-radius:8px;font-size:11.5px;font-weight:600;color:#334155;letter-spacing:.2px;">Configuration : ${escapeHtml(configLine)}</div>
      <div class="cover-client">
        <div class="cover-client-eyebrow">Préparé pour</div>
        <div class="cover-client-name">${escapeHtml(c.name || "Client")}</div>
        <div class="cover-client-meta">${escapeHtml([(c.address_civic || "") + " " + (c.address_street || ""), c.address_city, c.address_postal].filter(x => x && x.trim()).join(" · "))}</div>
        <div class="cover-client-meta-row">
          <span><strong>Téléphone</strong><b>${escapeHtml(c.phone || "—")}</b></span>
          <span><strong>Courriel</strong><b>${escapeHtml(c.email || "—")}</b></span>
          <span><strong>Zone</strong><b>${escapeHtml((calc?.zone?.zone_key || "Z1") + " · " + (c.address_city || ""))}</b></span>
        </div>
      </div>
      <div class="cover-bottom">
        <div class="cover-tagline">Prenez le contrôle de votre énergie.</div>
        <div class="cover-date">Date d'émission<strong>${dateStr}</strong>Valide jusqu'au ${validStr}</div>
      </div>
    </div></div>`;

    // ---- PAGE 2 : Synthèse
    const summaryRows = groups.map(g => `
      <div class="syn-summary-row"><div class="syn-summary-row-label">${escapeHtml(g.title)}</div><div class="syn-summary-row-value">${g.sub}</div></div>`).join("");
    const synHtml = `
    <div class="r5pdf-page">${header}<div class="pg-body">
      <div class="pg-eyebrow">Synthèse · 1 minute de lecture</div>
      <h1 class="syn-h1">L'essentiel du projet en un coup d'œil.</h1>
      <p class="syn-lead">Voici les chiffres clés. Le détail technique, les subventions, les économies projetées et le financement suivent.</p>
      <div class="syn-hero-kpi">
        <div class="syn-hero-eyebrow">Économies estimées sur 25 ans · net</div>
        <div class="syn-hero-value">${num0(calc?.savings_25 || 0)}${NB}<small>$</small></div>
        <div class="syn-hero-meta">Après subventions${grant > 0 ? " <strong>" + money(grant) + "</strong>" : ""} et coût total. Hypothèse tarif HQ${NB}${escapeHtml(c.hq_tariff || "D")} avec +3${NB}%/an.</div>
      </div>
      <div class="syn-kpi-grid">
        <div class="syn-kpi success"><div class="syn-kpi-eyebrow">Retour sur invest.</div><div class="syn-kpi-value">${payback}<small>ans</small></div><div class="syn-kpi-meta">${(state.client?.financing_plan_id || "comptant") === "comptant" ? "Comptant" : "Financé"}</div></div>
        <div class="syn-kpi primary"><div class="syn-kpi-eyebrow">Subventions totales</div><div class="syn-kpi-value">${num0(grant)}<small>$</small></div><div class="syn-kpi-meta">LogisVert + CII + RV</div></div>
        <div class="syn-kpi"><div class="syn-kpi-eyebrow">GES évités</div><div class="syn-kpi-value">${ges > 0 ? ges.toFixed(1).replace(".", ",") : "—"}<small>t/an</small></div><div class="syn-kpi-meta">≈ ${Math.round(ges / 4.6)} voiture(s)</div></div>
      </div>
      <div class="syn-summary-table">
        ${summaryRows}
        ${grant > 0 ? `<div class="syn-summary-row"><div class="syn-summary-row-label">Subventions (LogisVert + CII + Roulez Vert)</div><div class="syn-summary-row-value" style="color:var(--r5-success)">−${money(grant)}</div></div>` : ""}
        <div class="syn-summary-row"><div class="syn-summary-row-label">TPS 5,000 % + TVQ 9,975 %</div><div class="syn-summary-row-value">${money((calc?.tps || 0) + (calc?.tvq || 0))}</div></div>
        <div class="syn-summary-total"><div class="syn-summary-total-label">Total net TTC</div><div class="syn-summary-total-value">${money(netCost)}</div></div>
      </div>
    </div>${footer(2, totalPages)}</div>`;

    // ---- PAGE 3 : Détail
    const detailGroupsHtml = groups.map(g => `
      <div class="detail-group">
        <div class="detail-group-head"><span class="detail-group-title">${escapeHtml(g.title)}</span><span class="detail-group-sub">${g.sub}</span></div>
        ${g.rows.map(r => {
          const codeBadge = r.code ? `<span style="font-family:'JetBrains Mono','Courier New',monospace;font-size:8.5px;font-weight:600;color:#475569;background:#F1F5F9;border:1px solid #E2E8F0;padding:1px 5px;border-radius:4px;margin-right:7px;white-space:nowrap;">${escapeHtml(r.code)}</span>` : "";
          const puBit = r.pu ? `<span style="color:#94A3B8;"> · ${escapeHtml(r.pu)}/u</span>` : "";
          const descLine = (r.desc || r.pu) ? `<div class="detail-desc">${escapeHtml(r.desc || "")}${puBit}</div>` : "";
          return `<div class="detail-row"><div class="detail-qty">${escapeHtml(r.qty)}</div><div><div class="detail-name">${codeBadge}${escapeHtml(r.name)}</div>${descLine}</div><div class="detail-price">${r.price}</div></div>`;
        }).join("")}
      </div>`).join("");
    const detailHtml = `
    <div class="r5pdf-page">${header}<div class="pg-body">
      <div class="pg-eyebrow">Détail technique · matériel &amp; main-d'œuvre</div>
      <h1 class="pg-title">Tout ce qui est inclus.</h1>
      ${detailGroupsHtml}
      <div class="detail-note">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#2563EB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;margin-top:1px;"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>
        <div><strong>Non inclus</strong> · Permis municipal (à la charge du client) · Frais de raccordement Hydro-Québec · Toute modification structurelle de toiture.</div>
      </div>
    </div>${footer(3, totalPages)}</div>`;

    // ---- PAGE 4 : Subventions
    const subvCards = [];
    if ((calc?.logisvert || 0) > 0) subvCards.push(`<div class="subv-card"><div class="subv-card-head"><div class="subv-card-name">LogisVert · Hydro-Québec</div><div class="subv-card-amount">+ ${money(calc.logisvert)}</div></div><div class="subv-card-desc">Subvention résidentielle pour panneaux solaires raccordés au réseau HQ. Versement direct après mise en service.</div><div class="subv-card-formula">${calc.solarCalc?.kw || 0}${NB}kW × 1${NB}000${NB}$/kW · plafond 40${NB}% du HT solaire admissible</div></div>`);
    if ((calc?.commercial_subv?.cii_federal || 0) > 0) subvCards.push(`<div class="subv-card"><div class="subv-card-head"><div class="subv-card-name">Crédit d'impôt fédéral CII · ARC</div><div class="subv-card-amount">+ ${money(calc.commercial_subv.cii_federal)}</div></div><div class="subv-card-desc">Crédit d'impôt remboursable 30 % sur équipement de production d'énergie propre admissible.</div><div class="subv-card-formula">30 % × matériel solaire admissible</div></div>`);
    if ((calc?.commercial_subv?.solutions_efficaces || 0) > 0) subvCards.push(`<div class="subv-card"><div class="subv-card-head"><div class="subv-card-name">Solutions efficaces · Hydro-Québec</div><div class="subv-card-amount">+ ${money(calc.commercial_subv.solutions_efficaces)}</div></div><div class="subv-card-desc">Programme commercial Hydro-Québec pour l'installation solaire de grande puissance.</div><div class="subv-card-formula">1${NB}000${NB}$/kW · plafond 40${NB}% du projet</div></div>`);
    if ((calc?.rve || 0) > 0) subvCards.push(`<div class="subv-card"><div class="subv-card-head"><div class="subv-card-name">Roulez Vert · Transition énergétique QC</div><div class="subv-card-amount">+ ${money(calc.rve)}</div></div><div class="subv-card-desc">Subvention bornes de recharge résidentielles. <strong>Programme final en 2026</strong> — dernière année.</div><div class="subv-card-formula">50 % des coûts admissibles · max 600 $</div></div>`);
    const beforeGrant = (calc?.ttc || 0);
    const subvHtml = `
    <div class="r5pdf-page">${header}<div class="pg-body">
      <div class="pg-eyebrow">Subventions admissibles</div>
      <h1 class="pg-title">${grant > 0 ? money(grant) + " d'aide gouvernementale." : "Programmes de subvention."}</h1>
      <div class="subv-flow">
        <div class="subv-col"><div class="subv-col-label">Coût avant subv.</div><div class="subv-col-value">${money(beforeGrant)}</div><div class="subv-col-sub">TTC</div></div>
        <div class="subv-arrow"><svg width="40" height="20" viewBox="0 0 40 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 10 L34 10"/><path d="M28 4 L34 10 L28 16"/></svg></div>
        <div class="subv-col"><div class="subv-col-label">Net après subv.</div><div class="subv-col-value net">${money(netCost)}</div><div class="subv-col-sub">−${money(grant)}</div></div>
      </div>
      ${subvCards.join("") || `<div class="subv-card"><div class="subv-card-desc">Aucune subvention applicable à cette configuration. Contactez Raycom pour valider votre admissibilité.</div></div>`}
    </div>${footer(4, totalPages)}</div>`;

    // ---- PAGE 5 : Économies 25 ans (solaire seulement)
    const prod = calc?.production || 0;
    const annualKwh = calc?.annual_kwh || 0;
    const cov = annualKwh > 0 ? Math.round((prod / annualKwh) * 100) : 0;
    const savingsHtml = !hasSolar ? "" : `
    <div class="r5pdf-page">${header}<div class="pg-body">
      <div class="pg-eyebrow">Économies projetées · 25 ans</div>
      <h1 class="pg-title">${payback !== "—" ? "Le système se rembourse en " + payback + " ans." : "Vos économies sur 25 ans."}</h1>
      <div class="savings-chart">
        <div class="savings-chart-head"><div class="savings-chart-title">Économies cumulatives nettes</div><div class="savings-chart-meta">Tarif HQ${NB}${escapeHtml(c.hq_tariff || "D")} · inflation +3${NB}% / an</div></div>
        <div class="savings-chart-svg-wrap">${buildSavingsChart(calc)}</div>
        <div class="savings-chart-legend">
          <span class="legend-dot cost">Coût net du projet (${money(netCost)})</span>
          <span class="legend-dot savings">Économies cumulatives nettes</span>
          ${payback !== "—" ? `<span class="legend-dot payback">Point de payback (${payback} ans)</span>` : ""}
        </div>
      </div>
      <div class="syn-kpi-grid">
        <div class="syn-kpi"><div class="syn-kpi-eyebrow">Production an 1</div><div class="syn-kpi-value">${num0(prod)}<small>kWh</small></div><div class="syn-kpi-meta">${annualKwh > 0 ? cov + " % de votre conso" : "Estimée NRCan"}</div></div>
        <div class="syn-kpi"><div class="syn-kpi-eyebrow">Économies an 1</div><div class="syn-kpi-value">${num0(calc?.savings?.total || 0)}<small>$</small></div><div class="syn-kpi-meta">Facture HQ réduite + injection</div></div>
        <div class="syn-kpi"><div class="syn-kpi-eyebrow">Valeur revente +</div><div class="syn-kpi-value">+${num0((calc?.home_value || 0))}<small>$</small></div><div class="syn-kpi-meta">Étude SCHL 2024</div></div>
      </div>
    </div>${footer(5, totalPages)}</div>`;

    // ---- PAGE Financement
    const cashTtc = calc?.ttc || 0;
    const monthlyEst = (() => { const r = 0.1299 / 12, n = 240; const m = cashTtc > 0 ? cashTtc * (r / (1 - Math.pow(1 + r, -n))) : 0; return isFinite(m) ? m : 0; })();
    const finPageNum = hasSolar ? 6 : 5;
    const finHtml = `
    <div class="r5pdf-page">${header}<div class="pg-body">
      <div class="pg-eyebrow">Financement · 3 scénarios</div>
      <h1 class="pg-title">Comment souhaitez-vous payer&nbsp;?</h1>
      <div class="pay-grid">
        <div class="pay-card recommended">
          <div class="pay-card-badge">Recommandé</div>
          <div class="pay-card-name">Comptant</div>
          <div class="pay-card-value">${num0(cashTtc)}${NB}<small>$ TTC</small></div>
          <div class="pay-card-meta">Payback ${payback} ans · économies max</div>
          <ul class="pay-card-list"><li>${CK} Aucuns frais d'intérêt</li><li>${CK} Économies dès la 1<sup>re</sup> année</li><li>${CK} Chèque ou virement</li></ul>
        </div>
        <div class="pay-card">
          <div class="pay-card-name">FinanceIt — 0 $ · 3 mois</div>
          <div class="pay-card-value">${monthlyEst > 0 ? num0(monthlyEst) : "—"}${NB}<small>$ / mois</small></div>
          <div class="pay-card-meta">0 $ et 0 % les 3 premiers mois</div>
          <ul class="pay-card-list"><li>${CK} Aucun paiement ni intérêt pendant 3 mois</li><li>${CK} Puis 240 mois · 12,99 %</li><li>${CK} Préapprobation en 5 min</li></ul>
        </div>
        <div class="pay-card">
          <div class="pay-card-name">FinanceIt 20 ans</div>
          <div class="pay-card-value">${monthlyEst > 0 ? num0(monthlyEst) : "—"}${NB}<small>$ / mois</small></div>
          <div class="pay-card-meta">240 mois · 12,99 % APR</div>
          <ul class="pay-card-list"><li>${CK} Préapprobation 5 min</li><li>${CK} Remboursement anticipé sans pénalité</li><li>${CK} Économies HQ couvrent une partie</li></ul>
        </div>
      </div>
      <div class="trust-strip">
        <div class="trust-item"><strong>RBQ 5590-9402</strong>11.2 &amp; 13.5 · CMEQ</div>
        <div class="trust-item"><strong>Tesla Certified</strong>Installateur Powerwall</div>
        <div class="trust-item"><strong>FinanceIt</strong>Marchand certifié</div>
        <div class="trust-item"><strong>2 ans MO</strong>Garantie main-d'œuvre</div>
        <div class="trust-item"><strong>10–25 ans</strong>Garanties produits</div>
      </div>
    </div>${footer(finPageNum, totalPages)}</div>`;

    // ---- PAGE CGV + signature
    const cgvPageNum = hasSolar ? 7 : 6;
    const cgvHtml = `
    <div class="r5pdf-page">${header}<div class="pg-body">
      <div class="pg-eyebrow">Conditions générales</div>
      <h1 class="pg-title">Les petites lignes.</h1>
      <div class="cgv-text">
        <h4>1. Validité</h4><p>Soumission valide 30 jours à compter de la date d'émission. Au-delà, les prix peuvent être révisés selon le marché.</p>
        <h4>2. Délais</h4><p>Installation prévue 6 à 10 semaines suivant signature et dépôt. Variable selon disponibilité matériaux et météo.</p>
        <h4>3. Garantie main-d'œuvre</h4><p>Raycom garantit la main-d'œuvre 2 ans suivant la mise en service. Couvre défauts d'installation sans frais.</p>
        <h4>4. Garanties produits</h4><p>Tesla Powerwall : 10 ans. Panneaux Thornova : 25 ans linéaire (≥84,8 % an 25). Onduleurs Fox/Sig : 10 ans.</p>
        <h4>5. Subventions</h4><p>Montants LogisVert, CII et Roulez Vert estimés selon programmes en vigueur. Le client dépose les demandes ; Raycom fournit la documentation.</p>
        <h4>6. Permis municipaux</h4><p>À la charge du client. Raycom peut accompagner moyennant un frais de service additionnel.</p>
        <h4>7. Paiement</h4><p>Comptant : 30 % signature, 60 % matériaux, 10 % mise en service. Carte : 100 % signature (frais 3 %). FinanceIt : selon contrat.</p>
        <h4>8. Annulation</h4><p>Selon la LPC, 10 jours pour annuler sans pénalité après signature. Au-delà, frais jusqu'à 15 % peuvent s'appliquer.</p>
      </div>
      <div class="sign-block">
        <div class="sign-col"><h4>Pour le client</h4><div class="sign-line"></div><div class="sign-meta"><span>${escapeHtml(c.name || "Client")}</span><span>Date</span></div></div>
        <div class="sign-col"><h4>Pour Raycom Électrique</h4><div class="sign-line"></div><div class="sign-meta"><span>${escapeHtml(repName)}</span><span>${dateStr}</span></div></div>
      </div>
    </div>${footer(cgvPageNum, totalPages)}</div>`;

    // ---- PAGE 8 : Datasheet Thornova (solaire seulement)
    let cells = "";
    for (let r = 0; r < 12; r++) { let row = `<g transform="translate(0,${r * 20})">`; for (let col = 0; col < 9; col++) row += `<rect x="${col * 18}" y="0" width="18" height="18"/>`; cells += row + "</g>"; }
    const specs = [
      ["Puissance crête", "500 Wp"], ["Rendement", "22,5 %"], ["Vmpp / Impp", "37,8 V / 13,2 A"], ["Voc / Isc", "45,4 V / 14,1 A"],
      ["Dimensions", "1762 × 1134 mm"], ["Poids", "22,5 kg"], ["Charge vent", "2 400 Pa"], ["Charge neige", "5 400 Pa"],
      ["Bifacialité", "75 % ± 5 %"], ["Coeff. température", "−0,28 %/°C"]
    ];
    const dsHtml = !hasSolar ? "" : `
    <div class="r5pdf-page">${header}<div class="pg-body">
      <div class="pg-eyebrow">Annexe · Datasheet panneau solaire</div>
      <h1 class="pg-title">Thornova BBT54 500W.</h1>
      <div class="ds-grid">
        <div class="ds-panel">
          <svg viewBox="0 0 200 280" style="width:100%;max-width:200px;height:auto;">
            <rect x="10" y="10" width="180" height="260" rx="4" fill="#0a0a0a" stroke="#3a3a3a" stroke-width="1.5"/>
            <g stroke="#1e3a8a" stroke-width=".8" fill="#0a0a0a"><g transform="translate(20,20)">${cells}</g></g>
            <text x="100" y="265" text-anchor="middle" fill="#a3a3a3" font-size="8" font-family="Inter" font-weight="600" letter-spacing=".2em">THORNOVA</text>
          </svg>
        </div>
        <div>
          <div style="font-size:12pt;font-weight:700;color:var(--r5-ink);margin-bottom:4px;">BBT54 500W</div>
          <div style="font-size:9pt;color:var(--r5-ink-4);margin-bottom:18px;">All Black Bifacial · TopCon n-type</div>
          <div class="ds-spec-grid">
            ${specs.map(s => `<div><div class="ds-spec-label">${s[0]}</div><div class="ds-spec-val">${s[1]}</div></div>`).join("")}
          </div>
          <div style="margin-top:22px;padding:14px 16px;background:var(--r5-success-tint);border:1px solid #A7F3D0;border-radius:8px;font-size:9pt;color:var(--r5-ink-2);">
            <strong style="color:var(--r5-success);display:block;margin-bottom:4px;letter-spacing:.04em;text-transform:uppercase;font-size:8pt;">Garantie performance</strong>
            12 ans produit · 25 ans linéaire · ≥ 84,8 % à 25 ans · IP68 · classe A
          </div>
        </div>
      </div>
      <div style="margin-top:32px;padding-top:20px;border-top:1px solid var(--r5-line);font-size:8.5pt;color:var(--r5-ink-4);line-height:1.5;">
        <strong style="color:var(--r5-ink);">Conditions STC.</strong> Irradiance 1${NB}000 W/m² · cellule 25 °C · AM 1,5. Tolérance +5/−0 %. Certifications IEC 61215, IEC 61730, IEC 61701, UL 61730. Fabrication ISO 9001 / 14001.
      </div>
    </div>${footer(8, totalPages)}</div>`;

    const pages = hasSolar
      ? [coverHtml, synHtml, detailHtml, subvHtml, savingsHtml, finHtml, cgvHtml, dsHtml]
      : [coverHtml, synHtml, detailHtml, subvHtml, finHtml, cgvHtml];

    return `<div id="r5pdf-root"><style>${PDF_CSS}</style>${pages.join("")}</div>`;
  }

  // ============================================================================
  // RENDER → PDF (page par page, format Letter)
  // ============================================================================
  // Dimensions Letter @96dpi : 816 × 1056 px (ratio identique au format Letter mm)
  const PAGE_W_PX = 816, PAGE_H_PX = 1056;

  async function generatePdf(state, calc) {
    if (!window.html2canvas || !window.jspdf) {
      throw new Error("jsPDF ou html2canvas non chargé — vérifier index.html");
    }

    // 1) Attendre que les polices web (Inter + JetBrains Mono) soient chargées —
    //    sinon html2canvas rend avec une police de repli → texte décalé / "croche".
    try {
      if (document.fonts && document.fonts.ready) {
        await document.fonts.ready;
        // Force le chargement explicite des familles utilisées dans le PDF
        await Promise.all([
          document.fonts.load("700 56pt Inter"),
          document.fonts.load("400 12pt Inter"),
          document.fonts.load("600 9pt 'JetBrains Mono'")
        ].map(p => p.catch(() => {})));
      }
    } catch (_) { /* polices : best-effort */ }

    // 2) Container hors-écran, largeur exacte 816px
    const container = document.createElement("div");
    container.style.position = "fixed";
    container.style.left = "-99999px";
    container.style.top = "0";
    container.style.width = PAGE_W_PX + "px";
    container.style.background = "#fff";
    container.innerHTML = buildPdfHtml(state, calc);
    document.body.appendChild(container);

    try {
      const pageEls = Array.from(container.querySelectorAll(".r5pdf-page"));
      const { jsPDF } = window.jspdf;
      const pdf = new jsPDF({ orientation: "portrait", unit: "mm", format: "letter" }); // 215.9 × 279.4 mm
      const pageW = 215.9, pageH = 279.4;

      for (let i = 0; i < pageEls.length; i++) {
        const el = pageEls[i];
        // 3) Hauteur FIXE exacte → chaque canvas a le ratio Letter parfait (pas de distorsion)
        el.style.height = PAGE_H_PX + "px";
        el.style.minHeight = PAGE_H_PX + "px";
        el.style.maxHeight = PAGE_H_PX + "px";
        el.style.overflow = "hidden";

        // 4) Scale-to-fit : si le contenu déborde 1056px, on réduit le corps pour tout faire entrer
        const body = el.querySelector(".pg-body") || el.querySelector(".cover");
        if (body) {
          const overflow = body.scrollHeight - body.clientHeight;
          if (overflow > 2 && body.clientHeight > 0) {
            const factor = Math.max(0.78, body.clientHeight / body.scrollHeight);
            body.style.transformOrigin = "top left";
            body.style.transform = `scale(${factor})`;
            body.style.width = (100 / factor) + "%";
          }
        }

        const canvas = await window.html2canvas(el, {
          scale: 2, useCORS: true, backgroundColor: "#ffffff", logging: false,
          width: PAGE_W_PX, height: PAGE_H_PX, windowWidth: PAGE_W_PX, windowHeight: PAGE_H_PX,
          scrollX: 0, scrollY: 0, imageTimeout: 0
        });
        // 5) PNG (texte net, pas d'artefact JPEG) · ratio canvas == ratio Letter → addImage plein cadre
        const imgData = canvas.toDataURL("image/png");
        if (i > 0) pdf.addPage();
        pdf.addImage(imgData, "PNG", 0, 0, pageW, pageH, undefined, "FAST");
      }
      return pdf.output("blob");
    } finally {
      document.body.removeChild(container);
    }
  }

  function downloadPdf(blob, filename) {
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url; a.download = filename || `Raycom_Soumission_${Date.now()}.pdf`;
    document.body.appendChild(a); a.click();
    setTimeout(() => { URL.revokeObjectURL(url); document.body.removeChild(a); }, 100);
  }

  async function uploadToSupabase(blob, filename, submissionId) {
    if (!window.RAYCAST_SUPABASE) return null;
    const sb = window.RAYCAST_SUPABASE;
    const path = `submissions/${submissionId || "draft"}/${filename}`;
    const { error } = await sb.storage.from("pdfs").upload(path, blob, { contentType: "application/pdf", upsert: true });
    if (error) { console.warn("[pdf-2026] Upload Storage échoué:", error.message); return null; }
    const { data: urlData } = sb.storage.from("pdfs").getPublicUrl(path);
    return urlData?.publicUrl || null;
  }

  async function generate(state, calc, options = {}) {
    const blob = await generatePdf(state, calc);
    const filename = options.filename || `Raycom_${(state.client?.name || "Client").replace(/\s+/g, "-")}_${state.number || Date.now()}.pdf`;
    let url = null;
    if (options.upload && options.submissionId) url = await uploadToSupabase(blob, filename, options.submissionId);
    if (options.download !== false) downloadPdf(blob, filename);
    return { blob, url, filename };
  }

  window.PDF_2026 = { generate, buildPdfHtml, buildAutoScope, downloadPdf, uploadToSupabase };
})();
