Chargement des données officielles du 1er tour…

Les données officielles du 1er tour sont chargées depuis le serveur (quelques secondes).

Initialisation…
'; dropdown.classList.add('open'); return; } dropdown.innerHTML = matches.map(c => `
${c.nom}Dép. ${c.dep}
` ).join(''); dropdown.querySelectorAll('.d-item').forEach(el=>{ el.addEventListener('click',()=>{ const c = communesIndex[el.dataset.key]; searchInput.value = c.nom; dropdown.classList.remove('open'); afficherCommune(c); }); }); dropdown.classList.add('open'); }, 200); }); document.addEventListener('click', e=>{ if(!e.target.closest('.search-wrap')) dropdown.classList.remove('open'); }); /* ===== Affichage commune ===== */ let listes=[], siegesTotal=0, tauxPrime=0.5, hasOfficialT2=false; function afficherCommune(commune) { [communeSection,t1Section,t2Section,resultsSection].forEach(el=>el.style.display='none'); listes=[]; // Données déjà agrégées depuis le JSON const inscrits = commune.ins || 0; const exprimes = commune.exp || 0; const participation = inscrits>0 ? Math.round(exprimes/inscrits*100) : 0; // Sièges // Utiliser le nombre de sièges officiel du JSON (colonne 'Sièges au CM' du Ministère) // Fallback sur le tableau L2121-2 si absent (Paris/Lyon/Marseille fichier séparé) let nSieges = commune.sieges && commune.sieges > 0 ? commune.sieges : siegesLegaux(Math.round(inscrits * 1.55)); // Cas spéciaux PLM non présents dans le fichier BV if (!commune.sieges || commune.sieges === 0) { if (commune.dep === '75') nSieges = 163; else if (commune.dep === '69' && commune.code === '69123') nSieges = 73; else if (commune.dep === '13' && commune.code === '13055') nSieges = 101; } siegesTotal = nSieges; // Cas PLM : Paris, Lyon, Marseille ont un scrutin distinct // leurs listes sont dans un fichier séparé et peuvent être absentes du JSON principal const isPLM = ['75056','69123','13055'].includes(commune.dep + commune.code) || (commune.dep === '75') || (commune.dep === '69' && commune.code === '69123') || (commune.dep === '13' && commune.code === '13055'); const hasListes = commune.listes && commune.listes.length > 0; // Commune card communeSection.innerHTML=`
${commune.nom}
Dép. ${commune.dep}${inscrits?' · '+inscrits.toLocaleString('fr-FR')+' inscrits':''}${exprimes?' · Participation T1 : '+participation+' %':''}
${nSieges}
sièges
`; communeSection.style.display='block'; tauxPrime = 0.5; // Règle par défaut // Paris (dep 75) : prime de 25% pour le Conseil de Paris (loi PLM 11 août 2025) if (commune.dep === '75') tauxPrime = 0.25; // Paris if (commune.dep === '69' && commune.code === '69123') tauxPrime = 0.25; // Lyon if (commune.dep === '13' && commune.code === '13055') tauxPrime = 0.25; // Marseille // Cas PLM sans données dans le JSON → saisie manuelle pré-remplie if (!commune.listes || commune.listes.length === 0) { const t1s = document.getElementById('t1Section'); const isParis = commune.dep === '75'; const isLyon = commune.dep === '69' && commune.code === '69123'; const isMarseille = commune.dep === '13' && commune.code === '13055'; if (isParis || isLyon || isMarseille) { const msgPLM = isParis ? 'Paris — Conseil de Paris : règle spécifique loi PLM. La prime majoritaire est de 25 % des sièges (et non 50 %). Les listes du 1er tour sont pré-remplies.' : 'Lyon / Marseille — Scrutin spécifique loi PLM. Listes à saisir manuellement.'; t1s.innerHTML = `
${msgPLM}
`; t1s.style.display = 'block'; } if (isParis) { listes = assignerCouleurs([ {id:0, nom:'Paris est à vous! — Grégoire', nuance:'LUG', pct2:'', color:'', pctT1:37.98}, {id:1, nom:'Changer Paris — Dati', nuance:'LUD', pct2:'', color:'', pctT1:25.46}, {id:2, nom:'Nouveau Paris Populaire — Chikirou', nuance:'LFI', pct2:'', color:'', pctT1:11.72}, {id:3, nom:'Paris Apaisé — Bournazel', nuance:'LUC', pct2:'', color:'', pctT1:11.34}, {id:4, nom:'Knafo pour Paris', nuance:'LEXD', pct2:'', color:'', pctT1:10.40}, ]); tauxPrime = 0.25; // Conseil de Paris : prime de 25% } else { listes = [ {id:0, nom:'Liste 1', nuance:'', pct2:'', color:'#1B4FD8', pctT1:undefined}, {id:1, nom:'Liste 2', nuance:'', pct2:'', color:'#DC2626', pctT1:undefined}, ]; tauxPrime = 0.5; } afficherSaisieT2(nSieges); return; } // Listes T1 — depuis le JSON pré-agrégé const listesT1 = (commune.listes || []).map((l,i)=>{ const voix = l.voix || 0; const pct = exprimes>0 ? voix/exprimes*100 : 0; const nomL = l.nom || `Liste ${i+1}`; const nuance = l.nua || ''; const eluRaw = (l.elu || '').toLowerCase(); const elu = eluRaw.includes('élu')||eluRaw.includes('elu')||eluRaw==='1'||eluRaw==='true'; return {id:i, nom:nomL, nuance, pct, voix, elu, color:nuanceBaseColor(nuance) || PALETTE_FALLBACK[i % PALETTE_FALLBACK.length], pctT1:pct}; }).sort((a,b)=>b.pct-a.pct); listesT1.splice(0, listesT1.length, ...assignerCouleurs(listesT1)); // Élu au 1er tour ? // Élu si flag elu présent, OU si une liste dépasse 50% des suffrages exprimés const elusT1 = listesT1.filter(l => (l.elu || l.pct > 50) && l.pct >= 50); if(elusT1.length){ // Afficher tableau T1 + calcul direct des sièges const listesPourCalc = listesT1.map(l => ({...l, pct2: l.pct})); const resT1 = calculerSieges(listesPourCalc.map(l => ({...l, pct: l.pct})), nSieges, tauxPrime); const winT1 = resT1.reduce((a,b) => a.total>b.total?a:b); const nMT1 = Math.ceil(nSieges/2), nPT1 = nSieges-nMT1; const nonQT1 = resT1.filter(r=>!r.q&&r.pct>0); t1Section.innerHTML = `
✓ Liste élue dès le 1er tour — Majorité absolue obtenue, pas de 2e tour.
Résultats officiels — 1er tour (15 mars 2026)
${listesT1.map(l=>` `).join('')}
ListeVoix% exprimés
${l.nom}${l.nuance?`${l.nuance}`:''} ${l.voix?l.voix.toLocaleString('fr-FR'):'—'} ${l.pct.toFixed(2)} %
Répartition des ${nSieges} sièges
${nonQT1.length?`
Liste(s) sous 5 % : ${nonQT1.map(l=>`${l.nom}`).join(', ')} — non participante(s).
`:''}
${nMT1}
Prime majorit.
${nPT1}
Sièges prop.
${resT1.filter(r=>r.q).length}
Listes qualifiées
${[...resT1].sort((a,b)=>b.pct-a.pct).map(r=>` `).join('')}
ListeSièges
${r.nom} ${r.id===winT1.id?'▲ Élu':''} ${r.total} ${r.maj>0?`${r.maj} maj.`:''}${r.maj>0&&r.prop>0?' + ':''}${r.prop>0?`${r.prop} prop.`:''}
${[...resT1].filter(r=>r.total>0).sort((a,b)=>b.total-a.total).map(r=>`
${r.nom} ${r.total} siège${r.total>1?'s':''}
`).join('')}
% des voix
Répartition des sièges
`; t1Section.style.display='block'; setTimeout(() => dessinerGraphiques(resT1, nSieges), 100); return; } // Qualifiées T2 // Seuil légal : 10% des suffrages EXPRIMÉS const quali = listesT1.filter(l => l.pct >= 10); const fusion = listesT1.filter(l => l.pct < 10 && l.pct >= 5); t1Section.innerHTML=`
Résultats officiels — 1er tour (15 mars 2026)
${listesT1.map(l=>{ const q=quali.find(x=>x.id===l.id), f=fusion.find(x=>x.id===l.id); return ``; }).join('')}
ListeVoix% exprimésT2
${l.nom}${l.nuance?`${l.nuance}`:''} ${l.voix?l.voix.toLocaleString('fr-FR'):'—'} ${l.pct.toFixed(2)} % ${q?'✓ Qualifiée':f?'Fusion possible':'—'}
Qualifiées pour le 2e tour : listes avec ≥ 10 % des suffrages exprimés. Peuvent fusionner : listes avec ≥ 5 % des suffrages exprimés (art. L. 262 et L. 264 du Code électoral).
`; t1Section.style.display='block'; // Si les listes officielles T2 sont connues, les utiliser directement hasOfficialT2 = commune.t2 && commune.t2.length > 0; if (hasOfficialT2) { // Croiser avec listesT1 pour récupérer les voix du T1 listes = commune.t2.map((t, i) => { // Trouver la liste correspondante dans listesT1 via le numéro de panneau const match = listesT1.find(l => l.nuance === t.nua) || listesT1.find(l => l.nom.toLowerCase().includes(t.nom.toLowerCase().slice(0,10))) || listesT1[i] || null; return { id: i, nom: t.nom, nuance: t.nua, pct2: '', color: nuanceBaseColor(t.nua) || PALETTE_FALLBACK[i % PALETTE_FALLBACK.length], pctT1: match ? match.pct : undefined, voix: match ? match.voix : 0 }; }); listes = assignerCouleurs(listes); } else { listes = quali.map((l,i)=>({...l,pct2:''})); } renderT2(nSieges); } /* ===== Saisie T2 ===== */ function renderT2(nSieges) { t2Section.innerHTML=`
Scores du 2e tour — 22 mars 2026

Entrez vos estimations de scores. En cas de fusion ou désistement, supprimez la liste concernée avec ×. Le total doit être proche de 100 %.