<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="theme-color" content="#1a2a4a">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="PropImmo">
<meta name="description" content="Créez et envoyez des propositions de gestion locative en PDF">
<title>PropImmo — Proposition Locative</title>
<link rel="manifest" href="pwa_manifest.json">
<link rel="apple-touch-icon" href="icon-192.png">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('./pwa_sw.js')
.then(() => console.log('✅ Service Worker enregistré'))
.catch(e => console.warn('SW erreur:', e));
});
}
</script>
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;overflow-x:hidden}
body{font-family:Arial,sans-serif;background:#f0f4f8;font-size:14px;
overscroll-behavior:none;
padding-top:env(safe-area-inset-top);
padding-bottom:env(safe-area-inset-bottom);
}
/* ── TABS ── */
.tabs{display:flex;background:#1a2a4a;position:sticky;top:0;z-index:100;
padding-top:env(safe-area-inset-top)}
.tab{flex:1;padding:14px 8px;color:#aac;cursor:pointer;font-size:13px;
text-align:center;border-bottom:3px solid transparent;transition:.2s;
-webkit-tap-highlight-color:transparent}
.tab.active{color:#fff;border-bottom-color:#2196F3;font-weight:bold}
.tab-content{display:none}.tab-content.active{display:block}
/* ── EDITOR ── */
.editor{padding:16px;max-width:700px;margin:0 auto;
padding-bottom:calc(80px + env(safe-area-inset-bottom))}
.editor h2{color:#1a2a4a;margin-bottom:14px;font-size:16px}
.esec{background:#fff;border-radius:8px;padding:14px;margin-bottom:12px;
box-shadow:0 1px 4px rgba(0,0,0,.08)}
.esec h3{color:#2196F3;font-size:11px;text-transform:uppercase;letter-spacing:1px;
margin-bottom:12px;border-bottom:2px solid #e8f4fd;padding-bottom:6px}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.grid.one{grid-template-columns:1fr}
.field{display:flex;flex-direction:column;gap:4px}
.field label{font-size:11px;color:#666;font-weight:bold}
.field input,.field textarea{border:1px solid #ddd;border-radius:6px;
padding:10px 12px;font-size:14px;color:#333;width:100%;
font-family:Arial,sans-serif;
-webkit-appearance:none;appearance:none;
background:#fff}
.field input:focus,.field textarea:focus{outline:none;border-color:#2196F3;
box-shadow:0 0 0 3px rgba(33,150,243,.15)}
.field input[readonly] {
background: #f1f5f9 !important;
color: #64748b !important;
border-color: #e2e8f0 !important;
cursor: not-allowed !important;
}
.note{font-size:11px;color:#888;font-style:italic;margin-top:5px}
/* ── BOUTONS FIXES EN BAS ── */
.acts-fixed{position:fixed;bottom:0;left:0;right:0;
background:#fff;border-top:1px solid #e0e0e0;
padding:10px 16px calc(10px + env(safe-area-inset-bottom));
display:flex;gap:8px;z-index:99;box-shadow:0 -2px 12px rgba(0,0,0,.1)}
.btn{flex:1;border:none;padding:13px 8px;border-radius:8px;cursor:pointer;
font-size:13px;font-weight:bold;text-align:center;transition:.15s;
-webkit-tap-highlight-color:transparent;touch-action:manipulation}
.btn:active{transform:scale(.97);opacity:.9}
.btn.blue{background:#2196F3;color:#fff}
.btn.green{background:#27ae60;color:#fff}
.btn.share{background:#2980b9;color:#fff}
.btn.gray{background:#607d8b;color:#fff}
/* ── PREVIEW RESPONSIVE ── */
.preview-wrap{background:#cdd4db;padding:12px;
padding-bottom:calc(80px + env(safe-area-inset-bottom));
display:flex;flex-direction:column;align-items:center}
.preview-acts{width:100%;max-width:794px;display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap}
.doc-scroll{width:100%;overflow:visible;display:flex;justify-content:center;align-items:flex-start}
.scale-container{transform-origin:top center;transition:transform 0.1s ease}
/* ── TOAST NOTIFICATION ── */
.toast{position:fixed;bottom:calc(80px + env(safe-area-inset-bottom) + 12px);
left:50%;transform:translateX(-50%) translateY(20px);
background:#323232;color:#fff;padding:10px 20px;border-radius:24px;
font-size:13px;opacity:0;transition:.3s;z-index:200;white-space:nowrap;
pointer-events:none}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
/* =========================================
DOCUMENT A4 STRUCTURÉ SUR 2 PAGES ENTIÈRES (794×1123px)
========================================= */
.doc{background:#fff;width:794px;margin:0 auto;
box-shadow:0 2px 20px rgba(0,0,0,.18);
font-family:Arial,sans-serif;font-size:10.5px;line-height:1.45;color:#222}
.page{width:794px;height:1123px;max-height:1123px;overflow:hidden;display:flex;
flex-direction:column;position:relative;background:#fff;box-sizing:border-box;}
.pb{page-break-after:always;break-after:page}
.dh{background:#1a2a4a;color:#fff;padding:20px 24px;display:flex;
justify-content:space-between;align-items:center;gap:10px;flex-shrink:0}
.dh h1{font-size:18px;line-height:1.3;font-weight:900;text-transform:uppercase;flex:1}
.dh h1 .c{color:#2196F3}
.dh-logo{text-align:right;flex-shrink:0}
.dh-logo .lt{font-size:20px;font-weight:900;line-height:1}
.dh-logo .lb{font-size:20px;font-weight:900;color:#2196F3;line-height:1}
.dh-logo .lc{font-size:9px;color:#aac;margin-top:3px}
.abar{background:#eef5ff;border:1.5px solid #c5dff8;text-align:center;
padding:8px 18px;color:#1a2a4a;font-weight:bold;font-size:11px;
margin:0 24px 14px;border-radius:0 0 5px 5px;flex-shrink:0}
.st{display:flex;align-items:center;gap:7px;background:#2196F3;color:#fff;
font-size:10.5px;font-weight:bold;padding:6px 12px;border-radius:4px;
margin-bottom:10px;text-transform:uppercase;letter-spacing:.6px;flex-shrink:0}
.st.orange{background:#e67e22}
.ds{margin:0 24px 14px;flex-shrink:0}
.c-footer-signature {
margin: 0 24px 14px;
padding: 12px 16px;
background: #f8fafc;
border-top: 2px solid #2196F3;
border-radius: 4px;
font-size: 11px;
color: #334155;
text-align: right;
flex-shrink: 0;
}
.c-footer-signature strong {
color: #1a2a4a;
font-size: 11.5px;
}
table{width:100%;border-collapse:collapse;font-size:10px}
th{background:#1a2a4a;color:#fff;padding:6px 8px;text-align:left;
font-size:9.5px;text-transform:uppercase;letter-spacing:.4px;font-weight:bold}
td{padding:6px 8px;border-bottom:1px solid #eee;vertical-align:middle}
tr:last-child td{border-bottom:none}
.zb tr:nth-child(even) td{background:#f5f9ff}
.amt{color:#2196F3;font-weight:bold}
.hlr td{background:#1a2a4a;color:#fff;font-weight:bold}
.hlr .amt{color:#7ecfff}
.offered{color:#27ae60;font-weight:bold;font-size:9.5px}
.two{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin:0 24px 14px;flex-shrink:0}
.two>.col{min-width:0}
.ib{background:#f0f6ff;border-left:3px solid #2196F3;padding:6px 10px;
border-radius:0 4px 4px 0;margin-bottom:6px;font-size:10px;color:#333;line-height:1.5}
.promo{background:linear-gradient(130deg,#1a2a4a 55%,#1e3560);color:#fff;
padding:12px 20px;margin:0 24px 14px;border-radius:6px;
display:flex;align-items:center;gap:14px;flex-shrink:0}
.promo-ico{font-size:26px;flex-shrink:0}
.promo .pm{font-size:13.5px;font-weight:bold}
.promo .ps{font-size:9.5px;color:#aac}
.gg{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:8px}
.gc{background:#eef5ff;border:1.5px solid #c5dff8;border-radius:4px;padding:8px 4px;text-align:center}
.gc .gi{font-size:16px;margin-bottom:2px}
.gc .gl{font-size:8.5px;color:#666;text-transform:uppercase;letter-spacing:.3px}
.gc .ga{font-size:12.5px;font-weight:bold;color:#1a2a4a;margin:2px 0}
.gc .gs{font-size:8px;color:#888}
.svc2{display:grid;grid-template-columns:1fr 1fr;gap:0 12px}
.svcs{list-style:none}
.svcs li{padding:4px 0;border-bottom:1px solid #f0f4ff;font-size:9.5px;
color:#333;display:flex;gap:5px;line-height:1.35}
.svcs li .ico{width:14px;text-align:center;flex-shrink:0;font-size:10px}
.p2h{background:#1a2a4a;color:#fff;padding:10px 24px;
display:flex;justify-content:space-between;align-items:center;
font-size:10.5px;flex-shrink:0;margin-bottom:14px}
.p2h strong{color:#2196F3}
.chip{display:inline-block;background:#e8f4fd;color:#1565c0;font-size:8.5px;
font-weight:bold;padding:1px 5px;border-radius:5px;border:1px solid #b3d9f7}
.df{background:#1a2a4a;color:#7a8faa;font-size:8px;text-align:center;
padding:8px 16px;line-height:1.5;margin-top:auto;flex-shrink:0}
.spacer{flex:1 1 auto}
/* PRINT */
@media print{
.tabs,.editor,.preview-acts,.acts-fixed,.toast{display:none!important}
body{background:#fff;padding:0}
.preview-wrap{padding:0;background:#fff}
.tab-content{display:block!important}
.doc-scroll{overflow:visible;background:#fff}
.doc{overflow:visible;box-shadow:none;width:210mm;margin:0 auto}
.page{width:210mm;height:297mm;max-height:297mm;overflow:hidden;margin:0}
.pb{page-break-after:always;break-after:page}
@page{size:A4 portrait;margin:0}
}
</style>
</head>
<body>
<div class="tabs">
<div class="tab active" onclick="go('editor')">📝 Personnaliser</div>
<div class="tab" onclick="go('preview')">👁️ Aperçu</div>
</div>
<div class="toast" id="toast"></div>
<div id="editor" class="tab-content active">
<div class="editor">
<h2>📝 Personnalisation</h2>
<div class="esec"><h3>🏢 Agence (Verrouillé)</h3>
<div class="grid">
<div class="field"><label>Nom (ligne 1)</label><input id="a1" value="GUY" readonly></div>
<div class="field"><label>Nom (ligne 2)</label><input id="a2" value="HOQUET" readonly></div>
<div class="field"><label>Ville</label><input id="ac" value="SAINT-OMER" readonly></div>
<div class="field"><label>Adresse agence</label><input id="aa" value="8 rue de l'Ecusserie 62500 Saint-Omer" readonly></div>
<div class="field"><label>N° RCS</label><input id="ar" value="905000402" readonly></div>
<div class="field"><label>Capital (€)</label><input id="ap" value="12 000" readonly></div>
</div>
</div>
<div class="esec"><h3>👤 Votre profil Conseiller (Sauvegarde automatique)</h3>
<div class="grid">
<div class="field"><label>Nom</label><input id="cNom" value="" placeholder="Ex: Planchon" oninput="saveRep()"></div>
<div class="field"><label>Prénom</label><input id="cPre" value="" placeholder="Ex: Rémi" oninput="saveRep()"></div>
<div class="field"><label>Numéro de tel</label><input id="cT" value="" type="tel" placeholder="Ex: 06 00 00 00 00" oninput="saveRep()"></div>
<div class="field"><label>Adresse mail</label><input id="cE" value="" type="email" placeholder="Ex: r.planchon@guyhoquet.com" oninput="saveRep()"></div>
</div>
</div>
<div class="esec"><h3>🏡 Bien</h3>
<div class="grid one"><div class="field"><label>Adresse complète</label><input id="pa" value="Appartement D2, 5 rue de l'Egalité 62510 ARQUES"></div></div>
<div class="grid" style="margin-top:10px">
<div class="field"><label>Type de bien</label><input id="pt" value="Appartement 1 chambre"></div>
<div class="field"><label>Superficie (m²)</label><input id="ps" value="47" type="number" inputmode="numeric" oninput="autoFee()"></div>
<div class="field"><label>Référence / Lot</label><input id="pr" value="D2"></div>
</div>
</div>
<div class="esec"><h3>💰 Loyers</h3>
<div class="grid">
<div class="field"><label>Loyer nu HC (€)</label><input id="rN" value="630" type="number" inputmode="decimal"></div>
<div class="field"><label>Loyer meublé HC (€)</label><input id="rM" value="660" type="number" inputmode="decimal"></div>
<div class="field"><label>Charges locatives (€)</label><input id="rC" value="0" type="number" inputmode="decimal"></div>
</div>
</div>
<div class="esec"><h3>📋 Honoraires de location</h3>
<div class="grid">
<div class="field"><label>Mission complète (€/m²)</label><input id="fM" value="8" type="number" inputmode="decimal" oninput="autoFee()"></div>
<div class="field"><label>État des lieux (€/m²)</label><input id="fE" value="3" type="number" inputmode="decimal" oninput="autoFee()"></div>
<div class="field"><label>Total honoraires (€)</label><input id="fT" value="510" type="number" inputmode="decimal"></div>
</div>
<p class="note">Recalculé auto : (mission + EDL) × superficie</p>
</div>
<div class="esec"><h3>⚙️ Gestion & Assurances</h3>
<div class="grid">
<div class="field"><label>Taux gestion (%)</label><input id="gR" value="6" type="number" inputmode="decimal"></div>
<div class="field"><label>Taux multi-lots (%)</label><input id="gM" value="5" type="number" inputmode="decimal"></div>
<div class="field"><label>Seuil multi-lots (lots)</label><input id="gT" value="10" type="number" inputmode="numeric"></div>
<div class="field"><label>Taux GLI (%)</label><input id="gi" value="2.75" type="number" inputmode="decimal" step="0.01"></div>
<div class="field"><label>Plafond loyers impayés (€)</label><input id="glL" value="90 000"></div>
<div class="field"><label>Plafond détériorations (€)</label><input id="glD" value="10 000"></div>
<div class="field"><label>Plafond protection juridique (€)</label><input id="glJ" value="6 000"></div>
<div class="field"><label>Seuil intervention (€)</label><input id="glS" value="250"></div>
<div class="field"><label>PNO standard (€/an)</label><input id="pno" value="93" type="number" inputmode="decimal"></div>
</div>
</div>
<div class="esec"><h3>🎁 Offre promo</h3>
<div class="grid">
<div class="field"><label>Libellé</label><input id="pL" value="3 mois de gestion offerts"></div>
<div class="field"><label>Période</label><input id="pP" value="Du 1er avril au 30 juin 2026"></div>
</div>
</div>
</div>
<div class="acts-fixed">
<button class="btn blue" onclick="render();go('preview')">👁️ Aperçu</button>
<button class="btn green" onclick="render();go('preview');setTimeout(doPDF,400)">💾 PDF</button>
<button class="btn share" onclick="render();go('preview');setTimeout(shareDocument,400)">📤 Partager</button>
</div>
</div>
<div id="preview" class="tab-content">
<div class="preview-wrap">
<div class="preview-acts">
<button class="btn blue" style="flex:none;padding:11px 14px" onclick="go('editor')">📝 Modifier</button>
<button class="btn green" style="flex:none;padding:11px 14px" onclick="doPDF()">💾 PDF</button>
<button class="btn share" style="flex:none;padding:11px 14px" onclick="shareDocument()">📤 Partager</button>
<button class="btn gray" style="flex:none;padding:11px 14px" onclick="window.print()">🖨️</button>
</div>
<div class="doc-scroll"><div class="doc" id="docOut"></div></div>
</div>
</div>
<script>
const v=id=>document.getElementById(id)?.value||'';
const n=id=>parseFloat(v(id))||0;
const f=x=>parseFloat(x).toLocaleString('fr-FR',{minimumFractionDigits:2,maximumFractionDigits:2})+'\u00a0€';
function loadRep(){
if(localStorage.getItem('propimmo_cNom')) document.getElementById('cNom').value = localStorage.getItem('propimmo_cNom');
if(localStorage.getItem('propimmo_cPre')) document.getElementById('cPre').value = localStorage.getItem('propimmo_cPre');
if(localStorage.getItem('propimmo_cT')) document.getElementById('cT').value = localStorage.getItem('propimmo_cT');
if(localStorage.getItem('propimmo_cE')) document.getElementById('cE').value = localStorage.getItem('propimmo_cE');
}
function saveRep(){
localStorage.setItem('propimmo_cNom', document.getElementById('cNom').value);
localStorage.setItem('propimmo_cPre', document.getElementById('cPre').value);
localStorage.setItem('propimmo_cT', document.getElementById('cT').value);
localStorage.setItem('propimmo_cE', document.getElementById('cE').value);
}
function toast(msg,ms=2800){
const el=document.getElementById('toast');
if(el){el.textContent=msg;el.classList.add('show');clearTimeout(el._t);el._t=setTimeout(()=>el.classList.remove('show'),ms);}
}
function autoFee(){
const s=n('ps'),m=n('fM'),e=n('fE');
if(s&&m&&e) document.getElementById('fT').value=(s*(m+e)).toFixed(2);
}
function calc(){
const rN=n('rN'),rM=n('rM'),gr=n('gR')/100,gi=n('gi')/100;
return{
mN:f(rN*gr),mM:f(rM*gr),
gN:f(rN*gi),gM:f(rM*gi),
nN:f(rN*(1-gr)),nM:f(rM*(1-gr)),
ngN:f(rN*(1-gr-gi)),ngM:f(rM*(1-gr-gi))
};
}
function getHTMLPage1(){
const c=calc(),rN=n('rN'),rM=n('rM'),ch=n('rC'),surf=n('ps'),fM=n('fM'),fE=n('fE');
return `
<div class="page">
<div class="dh">
<h1><span class="c">PROPRIÉTAIRES</span> ON S'OCCUPE<br>DE LA <span class="c">GESTION</span> DE VOTRE BIEN !</h1>
<div class="dh-logo"><div class="lt">${v('a1')}</div><div class="lb">${v('a2')}</div><div class="lc">${v('ac')}</div></div>
</div>
<div class="abar">📍 ${v('pa')}</div>
<div class="ds">
<div class="st"><span>💰</span> Valeur Locative</div>
<p style="font-size:10px;color:#555;margin-bottom:7px">Estimation — <strong>${v('pt')}</strong> · ${surf} m²</p>
<table class="zb">
<thead><tr><th>Type de bail</th><th>Loyer HC</th><th>Charges</th><th>Loyer CC</th></tr></thead>
<tbody>
<tr><td>📄 Location nue</td><td>${f(rN)}</td><td>${ch?f(ch):'—'}</td><td class="amt">${f(rN+ch)}</td></tr>
<tr><td>🛋️ Location meublée</td><td>${f(rM)}</td><td>${ch?f(ch):'—'}</td><td class="amt">${f(rM+ch)}</td></tr>
</tbody>
</table>
</div>
<div class="ds">
<div class="st"><span>📋</span> Honoraires Location & Mise en place</div>
<table class="zb" style="margin-bottom:8px">
<thead><tr><th>Prestation de recherche & constitution du dossier</th><th style="text-align:center">Locataire</th><th style="text-align:center">Bailleur</th></tr></thead>
<tbody>
<tr><td>📢 Commercialisation complète, publicité & communication</td><td></td><td style="text-align:center">✅</td></tr>
<tr><td>🔍 Sélection rigoureuse & étude de solvabilité des dossiers</td><td></td><td style="text-align:center">✅</td></tr>
<tr><td>🚪 Organisation des visites du bien</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td></tr>
<tr><td>✍️ Rédaction du bail sécurisé & constitution des annexes</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td></tr>
<tr><td>🏡 Réalisation de l'état des lieux d'entrée (EDL)</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td></tr>
<tr><td>🤝 Frais d'entremise et de négociation commerciale</td><td></td><td class="offered" style="text-align:center">🎁 Offert</td></tr>
</tbody>
</table>
<table style="width:100%">
<thead><tr><th>Surface habitable</th><th>Calcul Mission (${fM}€/m²)</th><th>Calcul EDL (${fE}€/m²)</th><th>Total Unique Facturé</th></tr></thead>
<tbody><tr class="hlr"><td>${surf} m²</td><td class="amt">${f(surf*fM)}</td><td class="amt">${f(surf*fE)}</td><td class="amt">${f(n('fT'))}</td></tr></tbody>
</table>
</div>
<div class="ds">
<div class="st"><span>⚙️</span> Honoraires de Gestion Courante</div>
<div class="ib" style="margin-bottom:8px">⚙️ <strong>Formule Standard :</strong> Un taux fixe de <strong>${v('gR')}% TTC</strong> appliqué sur l'ensemble des montants encaissés.</div>
<div class="ib" style="margin-bottom:8px">📦 <strong>Avantage Multi-lots :</strong> Un taux réduit à <strong>${v('gM')}% TTC</strong> accessible dès un portefeuille de <strong>${v('gT')} lots</strong>.</div>
<p style="font-size:9.5px;color:#555;margin:8px 0 5px;font-weight:bold">Simulations de vos revenus nets mensuels (hors options) :</p>
<table class="zb">
<thead><tr><th>Type de Bail préconisé</th><th>Loyer Brut HC</th><th>Honoraires de gestion mensuels</th><th>Revenu Net reversé</th></tr></thead>
<tbody>
<tr><td>📄 Gestion Location Nue</td><td>${f(rN)}</td><td>${c.mN}</td><td class="amt">${c.nN}</td></tr>
<tr><td>🛋️ Gestion Location Meublée</td><td>${f(rM)}</td><td>${c.mM}</td><td class="amt">${c.nM}</td></tr>
</tbody>
</table>
</div>
<div class="promo">
<div class="promo-ico">🎁</div>
<div><div class="ps">${v('pP')}</div><div class="pm">${v('pL')}</div></div>
</div>
<div class="spacer"></div>
<div class="df">${v('a1')} ${v('a2')} ${v('ac')} — agence juridiquement et financièrement indépendante. SAS au capital de ${v('ap')} € — RCS n° ${v('ar')} — ${v('aa')}.</div>
</div>`;
}
function getHTMLPage2(){
const c=calc(),rN=n('rN'),rM=n('rM');
const hasRep = v('cNom') || v('cPre') || v('cT') || v('cE');
const fullName = [v('cPre'), v('cNom')].filter(Boolean).join(' ');
return `
<div class="page">
<div class="dh">
<h1><span class="c">PROPRIÉTAIRES</span> ON S'OCCUPE<br>DE LA <span class="c">GESTION</span> DE VOTRE BIEN !</h1>
<div class="dh-logo"><div class="lt">${v('a1')}</div><div class="lb">${v('a2')}</div><div class="lc">${v('ac')}</div></div>
</div>
<div class="p2h"><strong>${v('a1')} ${v('a2')} ${v('ac')}</strong><span>📍 ${v('pa')}</span></div>
<div class="ds" style="margin-top:6px">
<div class="st"><span>🛡️</span> Assurances & Protections</div>
<p style="font-size:10px;color:#555;margin-bottom:9px"><strong>Option GLI :</strong> <span class="chip">${v('gi')}% TTC</span> des loyers perçus</p>
<div class="gg">
<div class="gc"><div class="gi">🏦</div><div class="gl">Loyers impayés</div><div class="ga">${v('glL')} €</div><div class="gs">Sans franchise</div></div>
<div class="gc"><div class="gi">🔧</div><div class="gl">Détériorations</div><div class="ga">${v('glD')} €</div><div class="gs">Sans franchise</div></div>
<div class="gc"><div class="gi">⚖️</div><div class="gl">Protection juridique</div><div class="ga">${v('glJ')} €</div><div class="gs">Seuil : ${v('glS')} €</div></div>
</div>
<table class="zb" style="margin-bottom:12px">
<thead><tr><th>Bien</th><th>Bail</th><th>Brut</th><th>Gestion</th><th>GLI</th><th>Net final sécurisé</th></tr></thead>
<tbody>
<tr><td rowspan="2" style="font-weight:bold">${v('pt')}</td><td>📄 Nue</td><td>${f(rN)}</td><td>${c.mN}</td><td>${c.gN}</td><td class="amt">${c.ngN}</td></tr>
<tr><td>🛋️ Meublée</td><td>${f(rM)}</td><td>${c.mM}</td><td>${c.gM}</td><td class="amt">${c.ngM}</td></tr>
</tbody>
</table>
<p style="font-size:10px;color:#555">🏡 <strong>Assurance PNO :</strong> à partir de <strong>${v('pno')} €/an TTC</strong></p>
</div>
<div class="ds">
<div class="st"><span>✅</span> Services de Gestion Inclus</div>
<div class="svc2">
<ul class="svcs">
<li><span class="ico">👤</span>Interlocuteur unique location & gestion</li>
<li><span class="ico">💻</span>Espace client : baux, EDL, comptabilité</li>
<li><span class="ico">📨</span>Quittances mensuelles aux locataires</li>
<li><span class="ico">💳</span>Loyers reversés le 20 de chaque mois</li>
</ul>
<ul class="svcs">
<li><span class="ico">⛺</span>Saisie CAF pour locataires APL</li>
<li><span class="ico">📋</span>Attestations assurances & chaudière / an</li>
<li><span class="ico">📈</span>Révision annuelle des loyers (IRL)</li>
<li><span class="ico">🔨</span>Travaux urgents < 175 € gérés directement</li>
</ul>
</div>
</div>
<div class="ds">
<div class="st"><span>🛠️</span> Services de Gestion Optionnels</div>
<div class="svc2">
<ul class="svcs">
<li><span class="ico">📊</span>Aide complète à la déclaration fiscale (Formulaire 2044)</li>
<li><span class="ico">🏢</span>Gestion complète des charges de copropriété & syndic</li>
</ul>
<ul class="svcs">
<li><span class="ico">🚨</span>Recouvrement des créances avec suivi et action d'huissier</li>
<li><span class="ico">🔄</span>Régularisation annuelle approfondie des charges locatives</li>
</ul>
</div>
</div>
<div class="spacer"></div>
${hasRep ? `
<div class="c-footer-signature">
Votre conseiller commercial dédié : <strong>${fullName || '—'}</strong> ${v('cT') ? ' · 📞 ' + v('cT') : ''} ${v('cE') ? ' · ✉️ ' + v('cE') : ''}
</div>` : ''}
<div class="df">${v('a1')} ${v('a2')} ${v('ac')} — agence juridiquement et financièrement indépendante. SAS au capital de ${v('ap')} € — RCS n° ${v('ar')} — ${v('aa')}.</div>
</div>`;
}
function getHTMLContent(){
return getHTMLPage1() + getHTMLPage2();
}
function render(){
const out = document.getElementById('docOut');
if(out) out.innerHTML = getHTMLContent();
}
/* GENERATION PAR UNIQUE PAGE SANS AUCUN COMPORTEMENT FLOTTANT */
function pdfName(){return`Proposition_${v('pr')||'bien'}_${v('ac')}.pdf`.replace(/\s+/g,'_');}
function generatePDFPageObject(htmlMarkup) {
return new Promise((resolve, reject) => {
const iframe = document.createElement('iframe');
iframe.style.position = 'fixed';
iframe.style.top = '-20000px';
iframe.style.left = '-20000px';
iframe.style.width = '794px';
iframe.style.height = '1123px';
iframe.style.border = 'none';
document.body.appendChild(iframe);
const styleLinks = Array.from(document.querySelectorAll('style')).map(s => s.outerHTML).join('\n');
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
iframeDoc.open();
iframeDoc.write(`
<!DOCTYPE html>
<html style="width:794px !important; min-width:794px !important; height:1123px !important; max-height:1123px !important;">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=794, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
${styleLinks}
<style>
html, body {
background: #ffffff !important;
width: 794px !important;
min-width: 794px !important;
max-width: 794px !important;
height: 1123px !important;
max-height: 1123px !important;
overflow: hidden !important;
-webkit-text-size-adjust: none !important;
text-size-adjust: none !important;
}
.page {
box-shadow: none !important;
margin: 0 !important;
width: 794px !important;
min-width: 794px !important;
max-width: 794px !important;
height: 1123px !important;
max-height: 1123px !important;
display: flex !important;
flex-direction: column !important;
overflow: hidden !important;
}
.svc2 {
display: table !important;
width: 100% !important;
table-layout: fixed !important;
}
.svc2 .svcs {
display: table-cell !important;
width: 50% !important;
vertical-align: top !important;
}
</style>
</head>
<body style="width:794px !important; height:1123px !important; max-height:1123px !important;">
<div id="iframeDocOut" style="width:794px !important; height:1123px !important; max-height:1123px !important;">${htmlMarkup}</div>
</body>
</html>
`);
iframeDoc.close();
setTimeout(async () => {
try {
const pageNode = iframeDoc.querySelector('.page');
const canvas = await window.html2canvas(pageNode, {
scale: 2,
useCORS: true,
logging: false,
backgroundColor: '#ffffff',
width: 794,
height: 1123,
windowWidth: 794,
windowHeight: 1123
});
document.body.removeChild(iframe);
resolve(canvas);
} catch (err) {
if(iframe.parentNode) document.body.removeChild(iframe);
reject(err);
}
}, 280);
});
}
async function generatePDFObject() {
if(document.activeElement && typeof document.activeElement.blur === 'function') {
document.activeElement.blur();
}
const { jsPDF } = window.jspdf;
const pdf = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4', compress: true });
const canvas1 = await generatePDFPageObject(getHTMLPage1());
const imgData1 = canvas1.toDataURL('image/jpeg', 0.94);
pdf.addImage(imgData1, 'JPEG', 0, 0, 210, 297, undefined, 'FAST');
pdf.addPage('a4', 'p');
const canvas2 = await generatePDFPageObject(getHTMLPage2());
const imgData2 = canvas2.toDataURL('image/jpeg', 0.94);
pdf.addImage(imgData2, 'JPEG', 0, 0, 210, 297, undefined, 'FAST');
return pdf;
}
async function doPDF(){
toast('⏳ Génération du PDF…');
try {
const pdf = await generatePDFObject();
pdf.save(pdfName());
toast('✅ PDF 2 pages téléchargé !');
} catch(e) {
console.error(e);
toast('❌ Erreur lors de la création du PDF.');
}
}
/* LOGIQUE DE PARTAGE WEB SHARE NATIVE */
async function shareDocument() {
toast('⏳ Préparation du partage…');
try {
const pdfObject = await generatePDFObject();
const pdfBlob = pdfObject.output('blob');
const fileName = pdfName();
const file = new File([pdfBlob], fileName, { type: 'application/pdf' });
const shareText = `Bonjour, veuillez trouver ci-joint notre proposition commerciale de gestion locative concernant le bien situé à : ${v('pa')}.`;
if (navigator.canShare && navigator.canShare({ files: [file] })) {
await navigator.share({
files: [file],
text: shareText,
title: 'Proposition Gestion Locative'
});
toast('✅ Document partagé !');
} else {
const waUrl = `https://api.whatsapp.com/send?text=${encodeURIComponent(shareText + " (Le document PDF a été enregistré préliminairement dans vos téléchargements)")}`;
pdfObject.save(fileName);
window.open(waUrl, '_blank');
toast('💾 PDF enregistré & Partage ouvert !');
}
} catch (e) {
console.error(e);
toast('❌ Échec du partage.');
}
}
/* TABS AVEC MISE À L'ÉCHELLE MOBILE VISUELLE */
function go(t){
document.querySelectorAll('.tab').forEach((x,i)=>
x.classList.toggle('active',(i===0&&t==='editor')||(i===1&&t==='preview')));
document.getElementById('editor').classList.toggle('active',t==='editor');
document.getElementById('preview').classList.toggle('active',t==='preview');
if(t==='preview') {
render();
const doc = document.getElementById('docOut');
const scrollWin = document.querySelector('.doc-scroll');
if(doc && scrollWin) {
if (doc.parentNode.classList.contains('scale-container')) {
const parent = doc.parentNode;
parent.parentNode.insertBefore(doc, parent);
parent.parentNode.removeChild(parent);
}
const availableWidth = scrollWin.clientWidth;
if(availableWidth < 794) {
const scaleFactor = availableWidth / 794;
const wrapper = document.createElement('div');
wrapper.className = 'scale-container';
wrapper.style.width = '794px';
wrapper.style.height = (1123 * 2 * scaleFactor) + 'px';
wrapper.style.transform = `scale(${scaleFactor})`;
doc.parentNode.insertBefore(wrapper, doc);
wrapper.appendChild(doc);
}
}
}
}
loadRep();
autoFee();
render();
</script>
</body>
</html>