// ============================================================
// LandingPage — Vestora · refonte 2026-06
// Exposé sur window.LandingPage. React global (pas d'imports ES).
// Réutilise les tokens (main.css/landing.css) + helpers globaux
// (window.computeInvestment, window.fmt). Aucune couleur en dur.
// i18n FR-CA par défaut, EN structuré à l'identique.
// ============================================================

(function () {
const { useState: useLS, useEffect: useLE, useRef: useLR, useMemo: useLM } = React;

/* ---------- Logo « Pillar » sauge ---------- */
function Logo({ size = 28, word = true }) {
  const s = size;
  return (
    <span className="lp-logo-inner" style={{ display: "inline-flex", alignItems: "center", gap: 10 }}>
      <svg width={s} height={s} viewBox="0 0 64 64" fill="none" role="img" aria-label="Vestora">
        <rect x="13" y="35" width="10" height="15" rx="3" fill="var(--accent)" />
        <rect x="27" y="25" width="10" height="25" rx="3" fill="var(--accent)" />
        <rect x="41" y="13" width="10" height="37" rx="3" fill="var(--accent)" />
      </svg>
      {word && <span className="lp-logo-word">Vestora</span>}
    </span>
  );
}

/* ---------- i18n ---------- */
const T = {
  fr: {
    nav: { produit: "Produit", courtiers: "Courtiers", calc: "Calculateurs", tarifs: "Tarifs", blog: "Blog", signin: "Se connecter", trial: "Essai gratuit" },
    hero: {
      eyebrow: "Données MAMH + SCHL + Centris · Calibré sur 8 023 listings QC 2026",
      h1a: "Le bon immeuble locatif, ",
      h1em: "sans deviner les chiffres.",
      sub: "8 023 plex, condos et multilogements au Québec — analysés en temps réel selon vos hypothèses, scorés contre le marché QC réel et les standards banque canadiens.",
      cta1: "Explorer la carte gratuitement",
      cta2: "Voir un exemple d'analyse",
      micro: "Sans carte · Essai Pro 7 j · Annule en 1 clic",
      maptag: "Carte vivante — 8 023 biens scorés",
      sources: ["MAMH", "SCHL/CMHC", "Centris", "Statistique Canada", "Walk Score®"],
    },
    stats: [
      { n: 8023, fmt: "int", lbl: "listings actifs" },
      { n: 18, fmt: "int", lbl: "villes au Québec" },
      { n: 71.5, fmt: "pct", lbl: "couverture MAMH" },
      { n: 58, fmt: "int", lbl: "score marché QC (médiane)" },
      { n: 97.6, fmt: "pct", lbl: "divergence Marché vs Banque" },
    ],
    bento: {
      eyebrow: "Ce que fait Vestora",
      h2a: "Tout l'immeuble dans ", h2em: "un seul écran.",
      a: { h: "À la fois carte et radar", p: "Chaque pin porte son score d'investissement. Vous lisez un quartier d'un coup d'œil, pas une annonce à la fois." },
      b: { h: "Scoré contre le marché QC réel", p: "Le même bien, deux référentiels. Marché québécois observé contre standards de financement bancaire." , note: "97,6 % des biens divergent." },
      c: { h: "Stress test 3 taux", p: "DSCR recalculé à 5 %, 6 % et 7 %. Vous voyez où le bien décroche avant la banque." },
      d: { h: "Fiscalité QC complète", p: "CCA, gain en capital et droits de mutation par paliers — calculés, pas approximés.", chips: ["CCA", "Gain en capital", "Droits mutation QC", "Paliers"] },
      e: { h: "8 profils investisseur", p: "Le score s'adapte à votre stratégie. Un même immeuble n'a pas la même note pour tous.", chips: ["Rendement", "Long terme", "1er achat", "Revalorisation", "BRRRR", "Étudiant", "Multilog", "Mixte"] },
      f: { h: "PDF banquier 2 pages", p: "Un dossier propre, chiffres sourcés, prêt à déposer au financement." },
      mvsb: { market: "Marché QC", bank: "Banque" },
      stress: ["5 %", "6 %", "7 %"],
    },
    step: {
      eyebrow: "Une analyse en 30 secondes",
      h2a: "De la carte au verdict, ", h2em: "sans Excel.",
      s1: { n: "01", h: "Chercher", p: "Filtrez par ville, type, prix et score. La carte se met à jour en direct." },
      s2: { n: "02", h: "Cliquer", p: "Ouvrez un bien : score, verdict et signaux de risque, immédiatement." },
      s3: { n: "03", h: "Décider", p: "Bougez vos hypothèses. Le DSCR se recalcule sous vos doigts." },
      calc: { price: "Prix d'achat", down: "Mise de fonds", rate: "Taux", dscr: "DSCR" },
    },
    cmp: {
      eyebrow: "Comparatif",
      h2a: "Le vrai concurrent, c'est ", h2em: "l'Excel du chum courtier.",
      cols: ["", "Vestora", "Feuille Excel", "Centris"],
      rows: [
        { l: "8 KPIs financiers calculés", v: "yes", e: "partial", c: "no", en: "manuel" },
        { l: "Stress test 3 taux", v: "yes", e: "no", c: "no" },
        { l: "Fiscalité QC (CCA, gain capital)", v: "yes", e: "partial", c: "no", en: "partiel" },
        { l: "Évaluations MAMH branchées", v: "yes", e: "no", c: "no" },
        { l: "Mise à jour quotidienne", v: "yes", e: "no", c: "yes" },
        { l: "Export PDF banquier", v: "yes", e: "partial", c: "no", en: "bricolé" },
        { l: "Coût mensuel", v: "24–129 $", e: "votre temps", c: "gratuit", txt: true },
      ],
      hint: "Faites défiler le tableau →",
    },
    crt: {
      eyebrow: "Pour courtiers OACIQ",
      h2a: "CRM, fiche brandée, Dossier IA Commercial — ", h2em: "un cockpit pour vos clients.",
      feats: ["Kanban OACIQ", "Portail client privé", "PDF brandé courtier"],
      cta: "Découvrir Pro Courtier",
      banner: "Vous êtes une équipe ? Pack Agence 89 $ pour 2 sièges.",
      bannerCta: "Voir le Pack Agence",
      kanban: ["Prospects", "Visites", "Offres"],
    },
    price: {
      eyebrow: "Tarifs",
      h2a: "Un prix par usage, ", h2em: "pas par bien.",
      monthly: "Mensuel", annual: "Annuel", save: "−20 %",
      plans: [
        { name: "Découverte", price: 0, per: "/ toujours", desc: "1 analyse complète gratuite par semaine. Carte et scores en accès libre.", feats: ["Carte + scores 8 023 biens", "1 analyse complète / semaine", "Filtres et favoris"], cta: "Commencer", href: "index.html" },
        { name: "Investisseur Pro", price: 49, perM: "/ mois", desc: "Projection 10 ans, fiscalité QC complète, scénarios et PDF banquier.", feats: ["Analyses illimitées", "Projection 10 ans + scénarios", "Fiscalité QC (CCA, gain capital)", "Export PDF banquier"], cta: "Essayer 7 jours", href: "#/tarifs", featured: true, badge: "Le plus choisi" },
        { name: "Pro Courtier", price: 59, perM: "/ mois", desc: "CRM kanban OACIQ, portail client privé et fiche brandée à vos couleurs.", feats: ["Tout Investisseur Pro", "CRM kanban OACIQ", "Portail client + fiche brandée"], cta: "Voir Pro Courtier", href: "#/courtiers" },
      ],
      banner: "+3 plans : Investisseur 24 $ · Pro Commercial 129 $ · Pack Agence 89 $/2",
      bannerCta: "Tous les tarifs",
      oneshot: "Un dossier ponctuel sur un bien ? Dossier IA Vestora — 49 $ one-shot.",
      oneshotCta: "Commander un Dossier IA",
    },
    faq: {
      eyebrow: "Questions",
      h2: "Ce qu'on nous demande.",
      items: [
        { q: "C'est quoi un DSCR ?", a: ["Le ", { term: "DSCR", href: "#/calculateurs/dscr" }, " (Debt Service Coverage Ratio) mesure si les revenus nets de l'immeuble couvrent le service de la dette. Au-dessus de 1,20, la banque est confortable. En dessous de 1,0, l'immeuble ne se paie pas tout seul. ", { term: "Calculez-le ici", href: "#/calculateurs/dscr" }, "."] },
        { q: "Combien d'analyses dans Découverte ?", a: ["Une analyse complète gratuite par semaine, plus la carte et les scores en accès libre, sans limite. Au-delà, l'abonnement Investisseur Pro débloque les analyses illimitées."] },
        { q: "C'est légal de scraper Centris ?", a: ["Nous indexons des données d'annonces publiques, pas un flux MLS. Aucune donnée privée, aucun accès courtier réservé. Conforme à la Loi 25 et hébergé au Canada."] },
        { q: "Vous remplacez Analysimmo ?", a: ["Différemment. Eux vous font payer l'analyse bien par bien. Nous, c'est 8 023 immeubles déjà pré-calculés et scorés — vous filtrez d'abord, vous analysez ensuite."] },
        { q: "Vos calculs sont vérifiés comment ?", a: ["50 cas sur 50 sont validés à ±0,50 $ près contre un modèle Excel de référence. La parité est testée à chaque déploiement — un écart bloque la mise en production."] },
        { q: "Les données sont à jour ?", a: ["Centris est ré-indexé quotidiennement. Les évaluations municipales viennent des rôles MAMH, les paramètres SCHL des barèmes publics en vigueur."] },
        { q: "Mes données sont protégées ?", a: ["Conformité Loi 25, hébergement au Canada. Vos hypothèses et favoris restent à vous ; les sources MAMH et SCHL sont publiques."] },
      ],
    },
    final: { h2a: "8 023 immeubles. Une seule manière de savoir ", h2em: "lesquels valent la peine.", cta: "Commencer gratuitement" },
    footer: {
      links: ["Produit", "Courtiers", "Calculateurs", "Tarifs", "Blog", "Confidentialité"],
      legal: "Sources publiques : MAMH (rôles d'évaluation), SCHL/CMHC (barèmes), Centris (annonces). Vestora n'est ni courtier ni conseiller financier. Hébergement au Canada · Conforme Loi 25.",
      copy: "© 2026 Vestora",
    },
  },
  en: {
    nav: { produit: "Product", courtiers: "Brokers", calc: "Calculators", tarifs: "Pricing", blog: "Blog", signin: "Sign in", trial: "Free trial" },
    hero: {
      eyebrow: "MAMH + CMHC + Centris data · Calibrated on 8,023 QC listings 2026",
      h1a: "The right rental building, ",
      h1em: "without guessing the numbers.",
      sub: "8,023 plexes, condos and multi-units across Quebec — analyzed in real time on your assumptions, scored against the real QC market and Canadian bank standards.",
      cta1: "Explore the map free",
      cta2: "See a sample analysis",
      micro: "No card · 7-day Pro trial · Cancel in one click",
      maptag: "Live map — 8,023 scored buildings",
      sources: ["MAMH", "CMHC", "Centris", "Statistics Canada", "Walk Score®"],
    },
    stats: [
      { n: 8023, fmt: "int", lbl: "active listings" },
      { n: 18, fmt: "int", lbl: "Quebec cities" },
      { n: 71.5, fmt: "pct", lbl: "MAMH coverage" },
      { n: 58, fmt: "int", lbl: "QC market score (median)" },
      { n: 97.6, fmt: "pct", lbl: "Market vs Bank divergence" },
    ],
    bento: {
      eyebrow: "What Vestora does",
      h2a: "The whole building on ", h2em: "one screen.",
      a: { h: "A map and a radar at once", p: "Every pin carries its investment score. Read a neighbourhood at a glance, not one listing at a time." },
      b: { h: "Scored against the real QC market", p: "Same building, two yardsticks. Observed Quebec market versus bank financing standards.", note: "97.6% of buildings diverge." },
      c: { h: "3-rate stress test", p: "DSCR recomputed at 5%, 6% and 7%. See where it breaks before the bank does." },
      d: { h: "Full QC taxation", p: "CCA, capital gains and tiered transfer duties — computed, not approximated.", chips: ["CCA", "Capital gains", "QC transfer duties", "Tiers"] },
      e: { h: "8 investor profiles", p: "The score adapts to your strategy. The same building isn't scored the same for everyone.", chips: ["Yield", "Long term", "First buy", "Value-add", "BRRRR", "Student", "Multi-unit", "Mixed"] },
      f: { h: "2-page banker PDF", p: "A clean file, sourced numbers, ready to submit for financing." },
      mvsb: { market: "QC Market", bank: "Bank" },
      stress: ["5%", "6%", "7%"],
    },
    step: {
      eyebrow: "An analysis in 30 seconds",
      h2a: "From map to verdict, ", h2em: "no spreadsheet.",
      s1: { n: "01", h: "Search", p: "Filter by city, type, price and score. The map updates live." },
      s2: { n: "02", h: "Click", p: "Open a building: score, verdict and risk signals, instantly." },
      s3: { n: "03", h: "Decide", p: "Move your assumptions. The DSCR recomputes under your fingers." },
      calc: { price: "Purchase price", down: "Down payment", rate: "Rate", dscr: "DSCR" },
    },
    cmp: {
      eyebrow: "Comparison",
      h2a: "The real rival is ", h2em: "your friend's Excel sheet.",
      cols: ["", "Vestora", "Excel sheet", "Centris"],
      rows: [
        { l: "8 financial KPIs computed", v: "yes", e: "partial", c: "no", en: "manual" },
        { l: "3-rate stress test", v: "yes", e: "no", c: "no" },
        { l: "QC taxation (CCA, cap. gains)", v: "yes", e: "partial", c: "no", en: "partial" },
        { l: "MAMH valuations wired in", v: "yes", e: "no", c: "no" },
        { l: "Daily updates", v: "yes", e: "no", c: "yes" },
        { l: "Banker PDF export", v: "yes", e: "partial", c: "no", en: "hand-made" },
        { l: "Monthly cost", v: "$24–129", e: "your time", c: "free", txt: true },
      ],
      hint: "Scroll the table →",
    },
    crt: {
      eyebrow: "For OACIQ brokers",
      h2a: "CRM, branded sheet, Commercial AI File — ", h2em: "a cockpit for your clients.",
      feats: ["OACIQ kanban", "Private client portal", "Broker-branded PDF"],
      cta: "Discover Pro Broker",
      banner: "Running a team? Agency Pack $89 for 2 seats.",
      bannerCta: "See the Agency Pack",
      kanban: ["Leads", "Showings", "Offers"],
    },
    price: {
      eyebrow: "Pricing",
      h2a: "Priced by use, ", h2em: "not by building.",
      monthly: "Monthly", annual: "Annual", save: "−20%",
      plans: [
        { name: "Discover", price: 0, per: "/ forever", desc: "1 free full analysis per week. Map and scores open to all.", feats: ["Map + scores, 8,023 buildings", "1 full analysis / week", "Filters and favourites"], cta: "Start", href: "index.html" },
        { name: "Investor Pro", price: 49, perM: "/ month", desc: "10-year projection, full QC taxation, scenarios and banker PDF.", feats: ["Unlimited analyses", "10-year projection + scenarios", "QC taxation (CCA, cap. gains)", "Banker PDF export"], cta: "Try 7 days", href: "#/tarifs", featured: true, badge: "Most chosen" },
        { name: "Pro Broker", price: 59, perM: "/ month", desc: "OACIQ kanban CRM, private client portal and a sheet in your brand.", feats: ["Everything in Investor Pro", "OACIQ kanban CRM", "Client portal + branded sheet"], cta: "See Pro Broker", href: "#/courtiers" },
      ],
      banner: "+3 plans: Investor $24 · Pro Commercial $129 · Agency Pack $89/2",
      bannerCta: "All pricing",
      oneshot: "A one-off file on a single building? Vestora AI File — $49 one-shot.",
      oneshotCta: "Order an AI File",
    },
    faq: {
      eyebrow: "Questions",
      h2: "What people ask us.",
      items: [
        { q: "What is a DSCR?", a: ["The ", { term: "DSCR", href: "#/calculateurs/dscr" }, " (Debt Service Coverage Ratio) tells you whether a building's net income covers its debt service. Above 1.20 the bank is comfortable. Below 1.0 it doesn't pay for itself. ", { term: "Compute it here", href: "#/calculateurs/dscr" }, "."] },
        { q: "How many analyses in Discover?", a: ["One free full analysis per week, plus the map and scores with no limit. Beyond that, Investor Pro unlocks unlimited analyses."] },
        { q: "Is scraping Centris legal?", a: ["We index public listing data, not an MLS feed. No private data, no broker-only access. Compliant with Quebec's Law 25 and hosted in Canada."] },
        { q: "Do you replace Analysimmo?", a: ["Differently. They charge you per building. We pre-compute and score 8,023 buildings — you filter first, analyze second."] },
        { q: "How are your calculations verified?", a: ["50 of 50 cases are validated within ±$0.50 of a reference Excel model. Parity is tested on every deploy — a mismatch blocks the release."] },
        { q: "Is the data up to date?", a: ["Centris is re-indexed daily. Municipal valuations come from MAMH rolls, CMHC parameters from current public schedules."] },
        { q: "Is my data protected?", a: ["Law 25 compliant, hosted in Canada. Your assumptions and favourites stay yours; MAMH and CMHC sources are public."] },
      ],
    },
    final: { h2a: "8,023 buildings. One way to know ", h2em: "which ones are worth it.", cta: "Start free" },
    footer: {
      links: ["Product", "Brokers", "Calculators", "Pricing", "Blog", "Privacy"],
      legal: "Public sources: MAMH (assessment rolls), CMHC (schedules), Centris (listings). Vestora is neither a broker nor a financial advisor. Hosted in Canada · Law 25 compliant.",
      copy: "© 2026 Vestora",
    },
  },
};

/* ---------- Hero pins ---------- */
const HERO_PINS = [
  { id: "p1", score: 78, color: "var(--score-excellent)", lat: 46.8221, lng: -71.2244, addr: "458, Rue de la Salle · Limoilou", v: { fr: "Bon · Triplex", en: "Good · Triplex" }, kpis: [{ k: "Cap", v: "6,1 %", ve: "6.1%" }, { k: "DSCR", v: "1,07" }, { k: "Flux", v: "+1 922 $", ve: "+$1,922", cls: "pos" }] },
  { id: "p2", score: 62, color: "var(--score-fair)", lat: 46.8030, lng: -71.2270, addr: "1180, Avenue Cartier · Montcalm", v: { fr: "Moyen · Duplex", en: "Fair · Duplex" }, kpis: [{ k: "Cap", v: "4,8 %", ve: "4.8%" }, { k: "DSCR", v: "0,96" }, { k: "Flux", v: "−2 400 $", ve: "−$2,400", cls: "neg" }] },
  { id: "p3", score: 45, color: "oklch(0.64 0.16 48)", lat: 46.8165, lng: -71.2375, addr: "789, St-Vallier Est · Saint-Roch", v: { fr: "À éviter · 6-plex", en: "Avoid · 6-plex" }, kpis: [{ k: "Cap", v: "3,9 %", ve: "3.9%" }, { k: "DSCR", v: "0,81" }, { k: "Flux", v: "−7 100 $", ve: "−$7,100", cls: "neg" }] },
  { id: "p4", score: 31, color: "var(--score-avoid)", lat: 46.8398, lng: -71.2208, addr: "1780, Av. Champfleury · Limoilou", v: { fr: "À éviter · Duplex", en: "Avoid · Duplex" }, kpis: [{ k: "Cap", v: "0,5 %", ve: "0.5%" }, { k: "DSCR", v: "0,09" }, { k: "Flux", v: "−33 271 $", ve: "−$33,271", cls: "neg" }] },
];

/* ---------- Count-up (scroll-triggered; IO is unreliable in sandboxed iframes) ---------- */
function CountUp({ value, fmt, lang }) {
  const ref = useLR(null);
  const [cur, setCur] = useLS(0);
  const done = useLR(false);
  useLE(() => {
    const el = ref.current;
    if (!el) return;
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduce) { setCur(value); return; }
    const run = () => {
      done.current = true;
      const dur = 1100, t0 = performance.now();
      const id = setInterval(() => {
        const p = Math.min(1, (performance.now() - t0) / dur);
        const eased = 1 - Math.pow(1 - p, 3);
        setCur(value * eased);
        if (p >= 1) { setCur(value); clearInterval(id); }
      }, 16);
    };
    const check = () => {
      if (done.current) return;
      const r = el.getBoundingClientRect();
      if (r.top < window.innerHeight * 0.9 && r.bottom > 0) { run(); cleanup(); }
    };
    const cleanup = () => window.removeEventListener("scroll", check);
    window.addEventListener("scroll", check, { passive: true });
    check();
    return cleanup;
  }, [value]);
  const loc = lang === "fr" ? "fr-CA" : "en-CA";
  let txt;
  if (fmt === "pct") txt = cur.toLocaleString(loc, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + (lang === "fr" ? " %" : "%");
  else txt = Math.round(cur).toLocaleString(loc);
  return <span ref={ref}>{txt}</span>;
}

/* ---------- DSCR helper (réutilise window.computeInvestment) ---------- */
function liveDSCR(price, downPct, rate) {
  const listing = { price, metrics: { noi: price * 0.052 } };
  const r = window.computeInvestment(listing, { downPaymentPct: downPct, mortgageRate: rate, amortizationYears: 25 });
  return r.dscr;
}

/* ---------- Leaflet mini-map ---------- */
function useLeaflet(containerRef, mapRef, opts) {
  useLE(() => {
    if (!containerRef.current || mapRef.current || !window.L) return;
    const map = window.L.map(containerRef.current, {
      center: opts.center, zoom: opts.zoom,
      zoomControl: !!opts.zoomControl, attributionControl: !!opts.attribution,
      dragging: opts.dragging !== false, scrollWheelZoom: false, doubleClickZoom: false,
      boxZoom: false, keyboard: false, tap: false,
    });
    if (opts.zoomControl) window.L.control.zoom({ position: "topright" }).addTo(map);
    window.L.tileLayer("https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", {
      attribution: "© OpenStreetMap, © CARTO", subdomains: "abcd", maxZoom: 19,
    }).addTo(map);
    mapRef.current = map;
    setTimeout(() => map.invalidateSize(), 60);
    return () => { map.remove(); mapRef.current = null; };
  }, []);
}

function HeroMap({ lang }) {
  const containerRef = useLR(null);
  const mapRef = useLR(null);
  const [sel, setSel] = useLS(null);
  const markers = useLR(new Map());
  useLeaflet(containerRef, mapRef, { center: [46.823, -71.228], zoom: 13, zoomControl: false, attribution: true, dragging: true });

  useLE(() => {
    const map = mapRef.current;
    if (!map || !window.L) return;
    markers.current.forEach((m) => map.removeLayer(m));
    markers.current = new Map();
    HERO_PINS.forEach((p) => {
      const active = sel && sel.id === p.id;
      const html = `<div class="leaflet-pin ${active ? "active" : ""}" style="--pin-color:${p.color}"><span class="leaflet-pin-bubble">${p.score}</span></div>`;
      const icon = window.L.divIcon({ html, className: "leaflet-pin-wrap", iconSize: [40, 30], iconAnchor: [20, 30] });
      const mk = window.L.marker([p.lat, p.lng], { icon, riseOnHover: true });
      mk.on("click", () => setSel(p));
      mk.addTo(map);
      markers.current.set(p.id, mk);
    });
  }, [sel]);

  return (
    <div className="lp-mapcard">
      <div ref={containerRef} className="lp-map" />
      <div className="lp-map-tag"><span className="pulse" />{T[lang].hero.maptag}</div>
      <MiniDrawer pin={sel} lang={lang} onClose={() => setSel(null)} />
    </div>
  );
}

function MiniDrawer({ pin, lang, onClose }) {
  if (!pin) return <div className="lp-minidrawer" aria-hidden="true" />;
  const verdict = lang === "fr" ? pin.v.fr : pin.v.en;
  return (
    <div className={`lp-minidrawer open`}>
      <div className="lp-minidrawer-head">
        <span className="md-score" style={{ background: pin.color }}>{pin.score}</span>
        <span>
          <div className="md-addr">{pin.addr}</div>
          <div className="md-verdict">{verdict}</div>
        </span>
        <button className="md-close" onClick={onClose} aria-label="Fermer">
          <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M3 3l8 8M11 3l-8 8" /></svg>
        </button>
      </div>
      <div className="md-kpis">
        {pin.kpis.map((k, i) => (
          <div className="md-kpi" key={i}>
            <div className="k">{k.k}</div>
            <div className={`v ${k.cls || ""}`}>{lang === "fr" ? k.v : (k.ve || k.v)}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

function BentoMap() {
  const containerRef = useLR(null);
  const mapRef = useLR(null);
  const markers = useLR(new Map());
  useLeaflet(containerRef, mapRef, { center: [46.81, -71.235], zoom: 12, zoomControl: false, attribution: false, dragging: false });
  const pins = [
    ...HERO_PINS,
    { id: "x1", score: 84, color: "var(--score-excellent)", lat: 46.8014, lng: -71.2246 },
    { id: "x2", score: 72, color: "var(--score-good)", lat: 46.8351, lng: -71.2215 },
    { id: "x3", score: 53, color: "var(--score-fair)", lat: 46.7905, lng: -71.2700 },
    { id: "x4", score: 67, color: "var(--score-good)", lat: 46.8090, lng: -71.2120 },
  ];
  useLE(() => {
    const map = mapRef.current;
    if (!map || !window.L) return;
    markers.current.forEach((m) => map.removeLayer(m));
    markers.current = new Map();
    pins.forEach((p) => {
      const html = `<div class="leaflet-pin" style="--pin-color:${p.color}"><span class="leaflet-pin-bubble">${p.score}</span></div>`;
      const icon = window.L.divIcon({ html, className: "leaflet-pin-wrap", iconSize: [36, 28], iconAnchor: [18, 28] });
      const mk = window.L.marker([p.lat, p.lng], { icon });
      mk.addTo(map);
      markers.current.set(p.id, mk);
    });
  }, []);
  return <div ref={containerRef} className="lp-map" />;
}

/* ---------- Icons ---------- */
const IcYes = () => <svg className="ic yes" viewBox="0 0 18 18" fill="none"><circle cx="9" cy="9" r="8.2" stroke="currentColor" strokeWidth="1.3" opacity="0.35" /><path d="M5.4 9.2l2.3 2.3 4.9-5" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" /></svg>;
const IcNo = () => <svg className="ic no" viewBox="0 0 18 18" fill="none"><circle cx="9" cy="9" r="8.2" stroke="currentColor" strokeWidth="1.3" opacity="0.4" /><path d="M6.2 6.2l5.6 5.6M11.8 6.2l-5.6 5.6" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" /></svg>;
const IcPartial = () => <svg className="ic partial" viewBox="0 0 18 18" fill="none"><circle cx="9" cy="9" r="8.2" stroke="currentColor" strokeWidth="1.3" opacity="0.4" /><path d="M5 9h8" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" /></svg>;
const Tick = () => <svg width="15" height="15" viewBox="0 0 15 15" fill="none"><path d="M3.5 8l2.6 2.6L11.5 4.5" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" /></svg>;
const Arrow = () => <svg className="arr" width="15" height="15" viewBox="0 0 15 15" fill="none"><path d="M3 7.5h8M7.5 4l3.5 3.5L7.5 11" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" /></svg>;

window.Logo = Logo;
window.__LP = { T, HERO_PINS, CountUp, liveDSCR, HeroMap, BentoMap, IcYes, IcNo, IcPartial, Tick, Arrow };
})();
