Simulateur fiscal crypto 2026 — CrypCool
`;
}
/* ─── EXPORT PDF (window.print) ─────────────────────────────────────── */
function exportPDF(d, r, recipientName) {
const html = buildPDFHtml(d, r, recipientName);
const w = window.open("", "_blank");
if (w) {
w.document.write(html);
w.document.close();
w.onload = () => w.print();
}
}
/* ─── SEND EMAIL VIA ANTHROPIC API ──────────────────────────────────── */
/* ─── EMAIL TEMPLATE (100 % client-side — aucune donnée ne quitte le navigateur)
Génération d'un email HTML statique à partir des variables de simulation.
Pour l'envoi réel : transmettez emailSubject + emailHtml à votre backend
(SendGrid / Brevo / Mailjet) via un appel API sécurisé côté serveur.
──────────────────────────────────────────────────────────────────────── */
function buildEmailHtml(d, r, recipientEmail, recipientName) {
const f = (n) => new Intl.NumberFormat("fr-FR", { style:"currency", currency:"EUR", maximumFractionDigits:0 }).format(n||0);
const p = (n) => `${(n*100).toFixed(1).replace(".",",")} %`;
const dateStr = new Date().toLocaleDateString("fr-FR", { day:"2-digit", month:"long", year:"numeric" });
const optLabel = r.best === "flat" ? "Flat Tax 30 %" : "Barème progressif";
const optColor = r.best === "flat" ? "#FF9900" : "#18A999";
const optImpot = f(r.opt);
const econom = f(r.eco);
const pvImp = f(r.pvImp);
const txEff = p(r.txEff);
const prenom = recipientName ? recipientName.split(" ")[0] : "";
const salutation = prenom ? `Bonjour ${prenom},` : "Bonjour,";
const row = (label, val, highlight=false) => `
${label}
${val}
`;
const subject = `Votre simulation fiscale crypto 2026 — Récapitulatif CrypCool`;
const html = `
${subject}
CrypCool
Plateforme PSAN enregistrée AMF
Flat Tax 30 % — Revenus 2025
${salutation}
Voici le récapitulatif de votre simulation d'imposition sur vos plus-values crypto pour l'année 2025 (déclaration 2026), réalisée le ${dateStr} .
Ce document est généré localement dans votre navigateur — vos données fiscales ne sont jamais transmises à des tiers.
Option recommandée
${optLabel}
${optImpot}
Taux effectif : ${txEff}
· Économie vs. l'autre option : ${econom}
📐 Calcul de la plus-value (PAMP)
${row("Prix brut des cessions 2025", f(+d.cession))}
${+d.fraisCession > 0 ? row("− Frais d'exchanges (cession)", `− ${f(+d.fraisCession)}`) : ""}
${+d.fraisCession > 0 ? row("Prix net de cession", f(r.cessionNette), true) : ""}
${row("Coût d'acquisition cédé (PAMP)", f(r.coutCede))}
${row("Plus-value brute", f(r.pvBrute))}
${+d.moinsValues > 0 ? row("− Moins-values imputées", `− ${f(+d.moinsValues)}`) : ""}
${row("✅ Plus-value nette imposable", pvImp, true)}
⚖️ Comparatif des options
${row("Flat Tax 30 % (IR 12,8 % + PS 17,2 %)", f(r.flatTotal), r.best==="flat")}
${row(`Barème progressif (TMI ${p(r.tmi)} + PS 17,2 %)`, f(r.baremTotal), r.best==="bareme")}
📋 Prochaines étapes déclaratives
${[
["Formulaire 2086","Déclarez toutes vos cessions d'actifs numériques (une ligne par opération)."],
["Formulaire 3916-bis","Déclarez vos comptes d'actifs numériques détenus à l'étranger (exchanges)."],
r.best !== "flat" ? ["Option barème progressif","Cochez la case dédiée sur votre déclaration principale (case 2OP)."] : ["",""],
["Seuil d'exonération","Vérifiez que votre total de cessions dépasse bien 305 € avant de déclarer."],
].filter(([a])=>a).map(([titre, desc]) => `
${titre} — ${desc}
`).join("")}
Besoin d'un accompagnement personnalisé ?
Nos experts CrypCool peuvent vous aider à préparer votre déclaration, vérifier votre historique de cessions et optimiser votre situation fiscale.
Prendre rendez-vous →
Avertissement : Cette simulation est fournie à titre indicatif et ne constitue pas un conseil fiscal. Les résultats sont basés sur les données que vous avez saisies et la législation en vigueur au moment de la simulation (LFI 2026). Consultez un conseiller fiscal agréé pour votre situation personnelle.
Vous recevez cet email car vous avez utilisé le simulateur fiscal CrypCool.
Politique de confidentialité
`;
return { subject, html };
}
/* ── URL du backend d'envoi email ─────────────────────────────────────────
Renseigner l'URL de votre backend (Node.js + SendGrid/Brevo/Mailjet).
Format attendu : POST { to, subject, html, firstName }
Si vide (""), le simulateur ouvre un aperçu local dans un nouvel onglet.
────────────────────────────────────────────────────────────────────────── */
const BACKEND_EMAIL_URL = ""; // ex: "https://api.crypcool.com/send-simulateur"
async function sendPDFByEmail(d, r, recipientEmail, recipientName, setEmailStatus) {
setEmailStatus("sending");
try {
const { subject, html } = buildEmailHtml(d, r, recipientEmail, recipientName);
// ── Mode production : envoi via backend ──────────────────────────────
if (BACKEND_EMAIL_URL) {
const res = await fetch(BACKEND_EMAIL_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
to: recipientEmail,
subject,
html,
firstName: recipientName || "",
}),
});
if (!res.ok) throw new Error(`Backend HTTP ${res.status}`);
setEmailStatus("sent");
return;
}
// ── Mode développement : aperçu local (aucun serveur impliqué) ───────
const preview = window.open("", "_blank");
if (preview) {
preview.document.write(`
Aperçu email — CrypCool
⚙️ Mode aperçu — configurer BACKEND_EMAIL_URL pour l'envoi réel
${html}
⚙️ Intégration backend : Renseignez BACKEND_EMAIL_URL dans le simulateur
avec l'URL de votre backend (Node.js + SendGrid / Brevo / Mailjet).
Le backend reçoit { to, subject, html, firstName } et se charge de l'envoi.
Aucune donnée fiscale ne transite — seul le HTML de l'email formaté est transmis.
`);
preview.document.close();
}
setEmailStatus("sent");
} catch(e) {
console.error("[CrypCool] Erreur envoi email:", e);
setEmailStatus("error");
}
}
/* ─── PARTS OPTIONS ─────────────────────────────────────────────────── */
const PARTS = [
{ value: 1, label: "Célibataire / Divorcé(e) — 1 part" },
{ value: 1.5, label: "Célibataire + 1 enfant — 1,5 parts" },
{ value: 2, label: "Marié(e)/Pacsé(e) sans enfant — 2 parts" },
{ value: 2.5, label: "Marié(e)/Pacsé(e) + 1 enfant — 2,5 parts" },
{ value: 3, label: "Marié(e)/Pacsé(e) + 2 enfants — 3 parts" },
{ value: 3.5, label: "Marié(e)/Pacsé(e) + 3 enfants — 3,5 parts" },
{ value: 4, label: "Marié(e)/Pacsé(e) + 4 enfants — 4 parts" },
];
/* ─── SMALL COMPONENTS ──────────────────────────────────────────────── */
function InfoTip({ text }) {
const [show, setShow] = useState(false);
return (
setShow(true)} onMouseLeave={() => setShow(false)}
onClick={() => setShow(v=>!v)}
style={{
display:"inline-flex", alignItems:"center", justifyContent:"center",
width:16, height:16, borderRadius:"50%",
background:"rgba(255,153,0,0.15)", border:`1px solid ${B.orange}`,
fontSize:9, fontWeight:800, color:B.orange, cursor:"pointer", verticalAlign:"middle",
fontFamily:"Satoshi, sans-serif",
}}
>?
{show && (
{text}
)}
);
}
function PedaBlock({ icon, title, children }) {
const [open, setOpen] = useState(false);
return (
setOpen(v=>!v)} style={{
width:"100%", background:"transparent", border:"none",
display:"flex", alignItems:"center", justifyContent:"space-between",
padding:"11px 16px", cursor:"pointer",
color:B.teal, fontFamily:"Satoshi, sans-serif", fontSize:13, fontWeight:700,
}}>
{icon} {title}
▾
{open && (
{children}
)}
);
}
function NumInput({ label, value, onChange, hint, tip, suffix="€" }) {
const [foc, setFoc] = useState(false);
return (
{label}{tip && }
onChange(parseFloat(e.target.value)||0)}
onFocus={() => setFoc(true)} onBlur={() => setFoc(false)}
placeholder="0"
style={{
flex:1, background:"transparent", border:"none", outline:"none",
color:"#fff", fontSize:16, fontFamily:"Satoshi, sans-serif",
padding:"13px 16px", fontWeight:500,
}}
/>
{suffix}
{hint &&
{hint}
}
);
}
function SelInput({ label, value, onChange, options, tip }) {
return (
{label}{tip && }
onChange(parseFloat(e.target.value))} style={{
width:"100%", background:"rgba(255,255,255,0.08)",
border:"1.5px solid rgba(255,255,255,0.15)",
borderRadius:10, color:"#fff", fontSize:14, fontFamily:"Satoshi, sans-serif",
padding:"13px 16px", outline:"none", cursor:"pointer", appearance:"auto",
}}>
{options.map(o => {o.label} )}
);
}
function StepBar({ step }) {
const steps = ["Cessions", "Situation", "Contact", "Résultats"];
return (
{steps.map((label, i) => {
const n = i + 1;
const done = step > n, active = step === n;
return (
{i < steps.length - 1 && (
n ? B.orange : "rgba(255,255,255,0.1)",
transition:"background 0.3s",
}} />
)}
);
})}
);
}
/* ─── CUSTOM CHART TOOLTIP ──────────────────────────────────────────── */
function ChartTip({ active, payload }) {
if (!active || !payload?.length) return null;
return (
{payload.map((p,i) => (
{p.name} : {fmt(p.value)}
))}
);
}
/* ─── MAIN APP ──────────────────────────────────────────────────────── */
function App() {
useEffect(() => { injectFonts(); injectTrustpilot(); }, []);
const [step, setStep] = useState(1);
const [data, setData] = useState({
cession:"", acqTotal:"", vPortefeuille:"", fraisCession:0, moinsValues:0,
rni:"", parts:1,
modeDetail: false,
income: { d1Annuel:"", d1FraisReels:false, d1FraisReelsM:0, d2Annuel:"", d2FraisReels:false, d2FraisReelsM:0, autresRevenus:0, chargesDeductibles:0, abatSpeciaux:0 },
foyer: { statut:"celibataire", enfants:0, parentIsole:false, invalide:false, enfantsInvalides:0, ancienCombattant:false },
});
const [email, setEmail] = useState("");
const [emailName, setEmailName] = useState("");
const [emailFoc, setEmailFoc] = useState(false);
const [results, setResults] = useState(null);
const [anim, setAnim] = useState(true);
const [emailStatus, setEmailStatus] = useState("idle");
const [showEmailForm, setShowEmailForm] = useState(false);
// CSV import state
const [csvStatus, setCsvStatus] = useState("idle"); // idle | parsing | fetching | done | error
const [csvMsg, setCsvMsg] = useState("");
const [csvYear, setCsvYear] = useState(new Date().getFullYear() - 1);
const [csvYears, setCsvYears] = useState([]);
const [csvFilename, setCsvFilename] = useState("");
const [csvBalances, setCsvBalances] = useState(null);
const [csvDetails, setCsvDetails] = useState(null);
const fileInputRef = useRef(null);
const handleCSVFile = async (file) => {
if (!file || !file.name.endsWith(".csv")) {
setCsvStatus("error");
setCsvMsg("Veuillez sélectionner un fichier .csv");
return;
}
setCsvFilename(file.name);
setCsvStatus("parsing");
setCsvMsg("Analyse du fichier…");
try {
const text = await file.text();
const result = parseCSVData(text, csvYear);
if (result.error) {
setCsvStatus("error");
setCsvMsg(result.error);
return;
}
// If CSV contains years, update available years
if (result.years.length > 0) {
setCsvYears(result.years);
// If selected year not in CSV, use most recent year in CSV
if (!result.years.includes(csvYear)) {
const bestYear = result.years[result.years.length - 1];
// Re-parse with corrected year
const result2 = parseCSVData(text, bestYear);
setCsvYear(bestYear);
Object.assign(result, result2);
}
}
// Store raw text for re-parsing on year change
window.__csvRawText = text;
setCsvBalances(result.balances);
setCsvDetails({ nbTrades: result.nbTrades, nbCessions: result.nbCessions });
// Fill form fields
setData(d => ({
...d,
cession: result.cession || "",
acqTotal: result.acqTotal || "",
fraisCession: result.fraisCession || 0,
}));
// Fetch portfolio value from CoinGecko
if (Object.keys(result.balances).length > 0) {
setCsvStatus("fetching");
setCsvMsg("Récupération des cours actuels…");
const portfolioValue = await fetchCryptoPrices(result.balances);
if (portfolioValue !== null) {
setData(d => ({ ...d, vPortefeuille: portfolioValue }));
setCsvStatus("done");
setCsvMsg(`${result.nbCessions} cession(s) en ${csvYear} · Portefeuille valorisé à ${new Intl.NumberFormat("fr-FR", { style:"currency", currency:"EUR", maximumFractionDigits:0 }).format(portfolioValue)}`);
} else {
setCsvStatus("done");
setCsvMsg(`${result.nbCessions} cession(s) importée(s). Portefeuille non valorisé — renseignez la valeur manuellement.`);
}
} else {
setCsvStatus("done");
setCsvMsg(`${result.nbCessions} cession(s) importée(s) pour ${csvYear}.`);
}
} catch (e) {
setCsvStatus("error");
setCsvMsg("Erreur de lecture du fichier : " + e.message);
}
};
const handleCSVYearChange = async (newYear) => {
setCsvYear(newYear);
if (window.__csvRawText) {
const result = parseCSVData(window.__csvRawText, newYear);
if (!result.error) {
setCsvBalances(result.balances);
setCsvDetails({ nbTrades: result.nbTrades, nbCessions: result.nbCessions });
setData(d => ({
...d,
cession: result.cession || "",
acqTotal: result.acqTotal || "",
fraisCession: result.fraisCession || 0,
}));
if (Object.keys(result.balances).length > 0) {
setCsvStatus("fetching");
const portfolioValue = await fetchCryptoPrices(result.balances);
if (portfolioValue !== null) {
setData(d => ({ ...d, vPortefeuille: portfolioValue }));
setCsvStatus("done");
setCsvMsg(`${result.nbCessions} cession(s) en ${newYear} · Portefeuille valorisé à ${new Intl.NumberFormat("fr-FR", { style:"currency", currency:"EUR", maximumFractionDigits:0 }).format(portfolioValue)}`);
} else {
setCsvStatus("done");
setCsvMsg(`${result.nbCessions} cession(s) importée(s). Renseignez la valeur du portefeuille manuellement.`);
}
} else {
setCsvStatus("done");
setCsvMsg(`${result.nbCessions} cession(s) importée(s) pour ${newYear}.`);
}
}
}
};
const set = k => v => setData(d => ({ ...d, [k]: v }));
const setIncome = k => v => setData(d => ({ ...d, income: { ...d.income, [k]: v } }));
const setFoyer = k => v => setData(d => ({ ...d, foyer: { ...d.foyer, [k]: v } }));
// Compute live RNI for detail mode preview
const liveRni = data.modeDetail ? calcRNI(data.income).rni : (+data.rni || 0);
const liveParts = data.modeDetail ? calcParts(data.foyer).total : (+data.parts || 1);
const go = n => { setAnim(false); setTimeout(() => { setStep(n); setAnim(true); }, 160); };
const compute = () => {
const r = computeResults(data);
setResults(r);
setEmailStatus("idle");
setShowEmailForm(false);
go(r.exonere ? 4 : 3);
};
const pvPreview = data.cession && data.vPortefeuille
? calcPV(+data.cession, +data.acqTotal, +data.vPortefeuille) : null;
const isExoPreview = +data.cession > 0 && +data.cession <= TAX.seuilExo;
const chartData = results && !results.exonere ? [
{ name: "Flat Tax 30 %", "IR (12,8%)": Math.round(results.flatIR), "PS (17,2%)": Math.round(results.flatPS) },
{ name: "Barème progressif", "IR progressif": Math.round(results.irSurPV), "PS (17,2%)": Math.round(results.baremPS) },
] : [];
const cardStyle = {
background: "rgba(255,255,255,0.06)",
backdropFilter: "blur(12px)",
border: "1px solid rgba(255,255,255,0.1)",
borderRadius: 18, padding: "28px",
};
return (
{/* HEADER */}
Flat Tax 30 % — Revenus 2025
{/* HERO */}
{step === 1 && (
✦ Loi de finances 2026 — Données officielles
Calculez votre impôt
sur vos cryptos
Simulez votre imposition, comparez flat tax et barème progressif, et téléchargez votre récapitulatif PDF.
{/* ── DISCLAIMER BANNIÈRE ─────────────────────────────────── */}
🔒
Outil à titre informatif — Vos données restent sur votre appareil
Ce simulateur est fourni à titre indicatif et ne constitue pas un conseil fiscal ou juridique.
Les données saisies sont traitées exclusivement dans votre navigateur : elles ne sont ni collectées, ni conservées, ni transmises à des tiers.
Seul votre email (si renseigné) est enregistré dans notre CRM à des fins de contact.
Consultez un conseiller fiscal agréé pour votre situation personnelle.
)}
{step > 1 &&
}
{step > 1 &&
}
{/* ── STEP 1 : CESSIONS ─────────────────────────────────────── */}
{step === 1 && (
Étape 1 / 3
Vos cessions crypto
Méthode officielle DGFiP — Art. 150 VH bis du CGI
{/* ── CSV Import Zone ─────────────────────────────────────── */}
csvStatus === "idle" && fileInputRef.current?.click()}
onDragOver={e => { e.preventDefault(); e.stopPropagation(); }}
onDrop={e => { e.preventDefault(); e.stopPropagation(); const f = e.dataTransfer?.files?.[0]; if (f) handleCSVFile(f); }}
>
{ const f = e.target.files?.[0]; if (f) handleCSVFile(f); }} />
{csvStatus === "idle" && (<>
📄
Client CrypCool ? Importez votre export CSV
Glissez-déposez votre fichier ou parcourir
Les champs seront pré-remplis automatiquement.
>)}
{(csvStatus === "parsing" || csvStatus === "fetching") && (
)}
{csvStatus === "done" && (<>
✓
{csvFilename}
1 ? 12 : 0 }}>{csvMsg}
{csvYears.length > 1 && (
Année fiscale :
{csvYears.map(y => (
{ e.stopPropagation(); handleCSVYearChange(y); }}
style={{
padding: "4px 12px", borderRadius: 6, fontSize: 12, fontWeight: 700,
border: y === csvYear ? `1.5px solid ${B.orange}` : "1.5px solid rgba(255,255,255,0.15)",
background: y === csvYear ? "rgba(255,153,0,0.15)" : "rgba(255,255,255,0.05)",
color: y === csvYear ? B.orange : "rgba(255,255,255,0.5)",
cursor: "pointer", fontFamily: "Satoshi, sans-serif",
}}>{y}
))}
)}
{ e.stopPropagation(); setCsvStatus("idle"); setCsvMsg(""); setCsvFilename(""); setCsvBalances(null); setCsvDetails(null); window.__csvRawText = null; if(fileInputRef.current) fileInputRef.current.value = ""; }}
style={{ marginTop: 10, fontSize: 11, color: "rgba(255,255,255,0.35)", background: "none", border: "none", cursor: "pointer", textDecoration: "underline", fontFamily: "Satoshi, sans-serif" }}>
Réinitialiser l'import
>)}
{csvStatus === "error" && (<>
{csvMsg}
{ e.stopPropagation(); setCsvStatus("idle"); setCsvMsg(""); if(fileInputRef.current) fileInputRef.current.value = ""; }}
style={{ fontSize: 12, color: "rgba(255,255,255,0.5)", background: "none", border: "none", cursor: "pointer", textDecoration: "underline", fontFamily: "Satoshi, sans-serif" }}>
Réessayer
>)}
{/* ── Détail des soldes crypto (après import) ─────────────── */}
{csvStatus === "done" && csvBalances && Object.keys(csvBalances).filter(k => csvBalances[k] > 0).length > 0 && (
Soldes crypto détectés (portefeuille)
{Object.entries(csvBalances).filter(([,v]) => v > 0.00000001).map(([sym, amt]) => (
{sym} {amt < 0.01 ? amt.toFixed(8) : amt < 1 ? amt.toFixed(4) : amt.toFixed(2)}
))}
)}
{/* Live preview */}
{(pvPreview !== null || isExoPreview) && (
{isExoPreview ? (
🎉 Cessions ≤ 305 € — Exonération totale cette année !
) : (
Aperçu — Plus-value estimée
PV brute (PAMP)
{fmt(pvPreview)}
Flat Tax indicative
{fmt(pvPreview * 0.30)}
)}
)}
La méthode PAMP ne calcule pas "prix de vente − prix d'achat de ces cryptos" mais détermine la fraction du coût total correspondant à la portion du portefeuille vendue :
PV = Prix cession − (Montant investi × Prix cession ÷ Valeur portefeuille)
Ex : vous vendez 10 000 € (portefeuille = 50 000 €, investi = 30 000 €). Coût cédé = 30 000 × 10 000/50 000 = 6 000 €. PV = 4 000 € .
go(2)} disabled={!data.cession || !data.vPortefeuille} style={{
width:"100%", padding:"15px", marginTop:24, borderRadius:12, border:"none",
background: (!data.cession || !data.vPortefeuille) ? "rgba(255,255,255,0.08)" : B.gradOrange,
color: (!data.cession || !data.vPortefeuille) ? "rgba(255,255,255,0.3)" : B.navy,
fontSize:16, fontWeight:800, fontFamily:"Satoshi, sans-serif",
cursor: (!data.cession || !data.vPortefeuille) ? "not-allowed" : "pointer",
letterSpacing:"0.02em",
}}>
Continuer →
)}
{/* ── STEP 2 : SITUATION ────────────────────────────────────── */}
{step === 2 && (() => {
const rniLive = calcRNI(data.income);
const foyerLive = calcParts(data.foyer);
const canCompute = data.modeDetail
? (data.income.d1Annuel || data.income.autresRevenus)
: data.rni;
const toggleStyle = (active) => ({
flex:1, padding:"9px 14px", borderRadius:8, cursor:"pointer",
border:`1.5px solid ${active ? B.orange : "rgba(255,255,255,0.1)"}`,
background: active ? "rgba(255,153,0,0.12)" : "transparent",
color: active ? B.orange : "rgba(255,255,255,0.35)",
fontSize:12, fontWeight:700, fontFamily:"Satoshi, sans-serif",
transition:"all 0.2s",
});
const miniLabel = (txt) => (
{txt}
);
const checkBox = (label, checked, onChange, tip) => (
onChange(e.target.checked)}
style={{ width:16, height:16, accentColor:B.orange, cursor:"pointer" }} />
{label}{tip && }
);
return (
Étape 2 / 3
Votre situation fiscale
Nécessaire pour comparer flat tax et barème progressif
{/* Mode toggle */}
set("modeDetail")(false)}>
⚡ Mode simplifié
set("modeDetail")(true)}>
🔬 Mode détaillé
{/* ── SIMPLE MODE ─────────────────────────────────── */}
{!data.modeDetail && (<>
{data.rni > 0 && (
Quotient familial
{fmt(data.rni / data.parts)} / part
)}
>)}
{/* ── DETAILED MODE ───────────────────────────────── */}
{data.modeDetail && (<>
{/* Section salaires */}
💼 Traitements & Salaires
{miniLabel("Déclarant 1 — Salaire annuel brut")}
setIncome("d1Annuel")(parseFloat(e.target.value)||0)}
style={{ flex:1, background:"transparent", border:"none", outline:"none", color:"#fff", fontSize:14, fontFamily:"Satoshi,sans-serif", padding:"12px 14px", fontWeight:500 }} />
€
{checkBox("Frais réels (au lieu de l'abattement 10 %)", data.income.d1FraisReels, setIncome("d1FraisReels"),
"Si vos frais professionnels réels dépassent l'abattement forfaitaire de 10 % (plafonné à 14 556 €), vous pouvez opter pour la déduction des frais réels (déplacements, repas, formation…).")}
{data.income.d1FraisReels && (
setIncome("d1FraisReelsM")(parseFloat(e.target.value)||0)}
style={{ flex:1, background:"transparent", border:"none", outline:"none", color:"#fff", fontSize:13, fontFamily:"Satoshi,sans-serif", padding:"11px 14px" }} />
€
)}
{data.income.d1Annuel > 0 && (
Abt. appliqué : {fmt(data.income.d1FraisReels ? (+data.income.d1FraisReelsM||0) : Math.min(+data.income.d1Annuel * 0.1, TAX.plafondAbt))}
{" → "} Net D1 : {fmt(Math.max(0, +data.income.d1Annuel - (data.income.d1FraisReels ? (+data.income.d1FraisReelsM||0) : Math.min(+data.income.d1Annuel*0.1,TAX.plafondAbt))))}
)}
{miniLabel("Déclarant 2 — Salaire annuel brut (si applicable)")}
setIncome("d2Annuel")(parseFloat(e.target.value)||0)}
style={{ flex:1, background:"transparent", border:"none", outline:"none", color:"#fff", fontSize:14, fontFamily:"Satoshi,sans-serif", padding:"12px 14px", fontWeight:500 }} />
€
{checkBox("Frais réels déclarant 2", data.income.d2FraisReels, setIncome("d2FraisReels"),
"Si le déclarant 2 a des frais professionnels réels supérieurs à l'abattement 10 %.")}
{data.income.d2FraisReels && (
setIncome("d2FraisReelsM")(parseFloat(e.target.value)||0)}
style={{ flex:1, background:"transparent", border:"none", outline:"none", color:"#fff", fontSize:13, fontFamily:"Satoshi,sans-serif", padding:"11px 14px" }} />
€
)}
{/* Autres revenus + charges */}
📋 Autres revenus & Charges déductibles
{miniLabel("Autres revenus imposables (hors salaires & crypto)")}
setIncome("autresRevenus")(parseFloat(e.target.value)||0)}
style={{ flex:1, background:"transparent", border:"none", outline:"none", color:"#fff", fontSize:14, fontFamily:"Satoshi,sans-serif", padding:"12px 14px", fontWeight:500 }} />
€
Pensions, retraites, fonciers nets, BIC/BNC…
{miniLabel("Charges déductibles du RNG")}
setIncome("chargesDeductibles")(parseFloat(e.target.value)||0)}
style={{ flex:1, background:"transparent", border:"none", outline:"none", color:"#fff", fontSize:14, fontFamily:"Satoshi,sans-serif", padding:"12px 14px", fontWeight:500 }} />
€
PER/PERP, pension alimentaire versée, CSG déductible…
{miniLabel("Abattements spéciaux (retraité invalide, ancien combattant…)")}
setIncome("abatSpeciaux")(parseFloat(e.target.value)||0)}
style={{ flex:1, background:"transparent", border:"none", outline:"none", color:"#fff", fontSize:14, fontFamily:"Satoshi,sans-serif", padding:"12px 14px", fontWeight:500 }} />
€
{/* Foyer fiscal détaillé */}
👨👩👧 Foyer fiscal — Quotient familial
{miniLabel("Situation matrimoniale")}
{[["celibataire","Célibataire"],["divorce","Divorcé(e)"],["marie","Marié(e)"],["pacse","Pacsé(e)"],["veuf","Veuf/Veuve"]].map(([v,l]) => (
setFoyer("statut")(v)} style={{
padding:"7px 14px", borderRadius:8, cursor:"pointer",
border:`1.5px solid ${data.foyer.statut===v ? B.teal : "rgba(255,255,255,0.1)"}`,
background: data.foyer.statut===v ? "rgba(24,169,153,0.15)" : "transparent",
color: data.foyer.statut===v ? B.teal : "rgba(255,255,255,0.4)",
fontSize:12, fontWeight:700, fontFamily:"Satoshi,sans-serif",
}}>{l}
))}
{miniLabel("Nombre d'enfants à charge")}
setFoyer("enfants")(Math.max(0, (data.foyer.enfants||0)-1))} style={{ width:36, height:36, borderRadius:8, border:"1px solid rgba(255,255,255,0.15)", background:"transparent", color:"#fff", fontSize:18, cursor:"pointer" }}>−
{data.foyer.enfants||0}
setFoyer("enfants")((data.foyer.enfants||0)+1)} style={{ width:36, height:36, borderRadius:8, border:"1px solid rgba(255,255,255,0.15)", background:"transparent", color:"#fff", fontSize:18, cursor:"pointer" }}>+
{(data.foyer.enfants||0) > 0 && (
{miniLabel("Enfants invalides (carte mobilité incl.)")}
setFoyer("enfantsInvalides")(Math.max(0, (data.foyer.enfantsInvalides||0)-1))} style={{ width:36, height:36, borderRadius:8, border:"1px solid rgba(255,255,255,0.15)", background:"transparent", color:"#fff", fontSize:18, cursor:"pointer" }}>−
{Math.min(data.foyer.enfantsInvalides||0, data.foyer.enfants||0)}
setFoyer("enfantsInvalides")(Math.min((data.foyer.enfantsInvalides||0)+1, data.foyer.enfants||0))} style={{ width:36, height:36, borderRadius:8, border:"1px solid rgba(255,255,255,0.15)", background:"transparent", color:"#fff", fontSize:18, cursor:"pointer" }}>+
)}
{checkBox("Parent isolé (enfants à charge exclusive)", data.foyer.parentIsole, setFoyer("parentIsole"),
"Célibataire/divorcé élevant seul ses enfants : la 1ère demi-part enfant devient 1 part entière (+ 0,5 part).")}
{checkBox("Titulaire carte mobilité inclusion (invalide)", data.foyer.invalide, setFoyer("invalide"),
"+0,5 part supplémentaire pour le déclarant invalide ou son conjoint invalide.")}
{checkBox("Ancien combattant / Veuve de guerre (≥ 74 ans)", data.foyer.ancienCombattant, setFoyer("ancienCombattant"),
"+0,5 part pour les anciens combattants âgés d'au moins 74 ans ou les veuves de guerre.")}
{/* Live QF summary */}
Quotient familial calculé
{[["Parts adultes", foyerLive.partsAdultes], ["Parts enfants", foyerLive.partsEnfants], ["Parts bonus", foyerLive.partsBonus], ["Total N", foyerLive.total]].map(([k,v]) => (
))}
{/* Live RNI pipeline */}
{(data.income.d1Annuel || data.income.autresRevenus) ? (
📊 Calcul du RNI
{[
["TS net D1", fmt(rniLive.d1Net)],
...(rniLive.d2Sal > 0 ? [["TS net D2", fmt(rniLive.d2Net)]] : []),
...(rniLive.d2Sal > 0 ? [["TS NET total", fmt(rniLive.tsNet)]] : []),
...(rniLive.autresRevenus > 0 ? [["+ Autres revenus", fmt(rniLive.autresRevenus)]] : []),
["= RBG", fmt(rniLive.rbg)],
...(rniLive.charges > 0 ? [["− Charges", fmt(rniLive.charges)]] : []),
...(rniLive.charges > 0 ? [["= RNG", fmt(rniLive.rng)]] : []),
...(rniLive.abatSpe > 0 ? [["− Abattement spéciaux", fmt(rniLive.abatSpe)]] : []),
].map(([k,v]) => (
{k} {v}
))}
= RNI (hors crypto)
{fmt(rniLive.rni)}
Quotient / part : {fmt(rniLive.rni / foyerLive.total)} — TMI indicative : {pct((() => { let t=0; const q=(rniLive.rni)/foyerLive.total; for(const tr of TAX.tranches){if(q>tr.min)t=tr.taux;} return t; })())}
) : null}
>)}
Flat Tax 30 % (PFU) : Prélèvement Forfaitaire Unique = 12,8 % IR + 17,2 % prélèvements sociaux. Taux fixe sur la plus-value, indépendant de vos autres revenus. C'est le régime par défaut.
Barème progressif : La plus-value s'ajoute à vos revenus et est taxée par tranches (0 %, 11 %, 30 %, 41 %, 45 %) + 17,2 % PS. Avantageux si votre taux marginal est faible (TMI ≤ 11 %).
⚠️ L'option barème progressif s'applique à tous vos revenus de capitaux mobiliers de l'année (dividendes, intérêts…), pas uniquement aux crypto.
go(1)} style={{ flex:1, padding:"15px", borderRadius:12, border:"1px solid rgba(255,255,255,0.15)", background:"transparent", color:"rgba(255,255,255,0.5)", fontSize:15, fontWeight:600, fontFamily:"Satoshi, sans-serif", cursor:"pointer" }}>← Retour
Calculer →
);
})()}
{/* ── STEP 3 : EMAIL (HUBSPOT) ──────────────────────────────── */}
{step === 3 && (
🔒
Votre simulation est prête !
Renseignez vos coordonnées pour accéder à l'analyse complète, au comparatif graphique et à votre récapitulatif PDF détaillé.
{/* Blurred teaser */}
{/* HubSpot form */}
{
if (submittedEmail) setEmail(submittedEmail);
if (submittedName) setEmailName(submittedName);
go(4);
}}
/>
Données traitées conformément à notre{" "}
politique de confidentialité .{" "}
go(4)} style={{
background:"none", border:"none", color:"rgba(255,255,255,0.25)",
cursor:"pointer", textDecoration:"underline", fontSize:11, fontFamily:"Satoshi, sans-serif",
}}>Continuer sans formulaire →
)}
{/* ── STEP 4 : RESULTS ─────────────────────────────────────── */}
{step === 4 && results && (
{results.exonere ? (
🎉
Exonération totale !
Vos cessions ({fmt(data.cession)}) sont en dessous du seuil de 305 € . Aucun impôt à payer sur vos cryptos cette année.
{ setStep(1); setResults(null); }} style={{
marginTop:28, padding:"12px 32px", borderRadius:10, border:"1px solid rgba(255,255,255,0.15)",
background:"transparent", color:"rgba(255,255,255,0.5)",
fontSize:14, fontWeight:600, fontFamily:"Satoshi, sans-serif", cursor:"pointer",
}}>Nouvelle simulation
) : (
{/* BEST OPTION BANNER */}
✦ Option recommandée
{results.best==="flat" ? "Flat Tax 30 %" : "Barème progressif"}
Économie de{" "}
{fmt(results.eco)}
{" "}vs l'autre option
Impôt total dû
{fmt(results.opt)}
Taux effectif : {pct(results.txEff)}
{/* PV RECAP */}
📐 Calcul de la plus-value (PAMP)
{[
["Prix brut des cessions", fmt(data.cession)],
...(+data.fraisCession > 0 ? [["− Frais d'exchanges (cession)", `− ${fmt(data.fraisCession)}`]] : []),
...(+data.fraisCession > 0 ? [["Prix net de cession (base PAMP)", fmt(results.cessionNette)]] : []),
["Coût d'acquisition cédé (PAMP)", fmt(results.coutCede)],
["Plus-value brute", fmt(results.pvBrute)],
...(+data.moinsValues > 0 ? [["Moins-values imputées", `− ${fmt(data.moinsValues)}`]] : []),
].map(([k,v], i, arr) => (
{k}
{v}
))}
Plus-value nette imposable
{fmt(results.pvImp)}
{/* CHART */}
⚖️ Comparaison visuelle — Flat Tax vs Barème
`${Math.round(v/1000)}k€`} tick={{ fill:"rgba(255,255,255,0.25)", fontSize:11, fontFamily:"Satoshi" }} axisLine={false} tickLine={false} />
} cursor={{ fill:"rgba(255,255,255,0.03)" }} />
{/* Side cards */}
{[
{
key:"flat", label:"Flat Tax 30 %",
amount: fmt(results.flatTotal),
color: B.navyLight,
border: results.best==="flat" ? "rgba(97,164,210,0.5)" : "rgba(255,255,255,0.08)",
bg: results.best==="flat" ? "rgba(97,164,210,0.1)" : "rgba(255,255,255,0.03)",
detail: `IR 12,8 % : ${fmt(results.flatIR)}\nPS 17,2 % : ${fmt(results.flatPS)}`,
},
{
key:"bareme", label:"Barème progressif",
amount: fmt(results.baremTotal),
color: B.teal,
border: results.best==="bareme" ? "rgba(24,169,153,0.5)" : "rgba(255,255,255,0.08)",
bg: results.best==="bareme" ? "rgba(24,169,153,0.1)" : "rgba(255,255,255,0.03)",
detail: `TMI : ${pct(results.tmi)}\nIR sur PV : ${fmt(results.irSurPV)}\nPS 17,2 % : ${fmt(results.baremPS)}`,
},
].map(c => (
{results.best===c.key && (
Optimal
)}
{c.label}
{c.amount}
{c.detail}
))}
{/* PEDAGOGY */}
{results.best==="flat"
? <>La flat tax à 30 % est optimale car votre TMI est de {pct(results.tmi)} . Au barème progressif, votre plus-value serait taxée à ce taux + 17,2 % de PS, résultant en un impôt plus élevé. La flat tax plafonne l'IR à 12,8 % quelle que soit votre tranche.>
: <>Le barème progressif est avantageux : votre TMI de {pct(results.tmi)} est inférieur aux 12,8 % de l'IR forfaitaire. Votre plus-value est donc taxée à un taux IR plus faible. Ce choix s'applique à tous vos revenus du capital de l'année.>
}
Formulaire 2086 — Déclarez vos plus-values de cession d'actifs numériques lors de votre déclaration de revenus annuelle.
Formulaire 3916-bis — Obligatoire si vous détenez des comptes sur des plateformes étrangères (Binance, Coinbase, Kraken…), même si le solde est nul.
Option barème progressif — Si recommandée, cochez la case dédiée lors de votre déclaration en ligne sur impots.gouv.fr.
{/* DISCLAIMER */}
⚠️
Simulation indicative. Ne prend pas en compte la CEHR, les crédits d'impôt, le staking/minage professionnel, ni les régimes BIC/BNC.
{/* ACTIONS */}
exportPDF(data, results, emailName || email)} style={{
padding:"14px", borderRadius:12, border:"none",
background:B.gradOrange, color:B.navy,
fontSize:14, fontWeight:800, fontFamily:"Satoshi, sans-serif", cursor:"pointer",
display:"flex", alignItems:"center", justifyContent:"center", gap:8,
}}>📄 Télécharger le PDF détaillé
{ setStep(1); setResults(null); setEmail(""); setEmailName(""); setEmailStatus("idle"); setShowEmailForm(false); }} style={{
padding:"14px", borderRadius:12, border:"1px solid rgba(255,255,255,0.1)",
background:"transparent", color:"rgba(255,255,255,0.4)",
fontSize:14, fontWeight:600, fontFamily:"Satoshi, sans-serif", cursor:"pointer",
}}>Nouvelle simulation
{/* EMAIL PDF */}
{!showEmailForm ? (
📬 Recevoir le récapitulatif par email
Un récapitulatif complet avec tous les calculs, explications et prochaines étapes.
setShowEmailForm(true)} style={{
padding:"11px 22px", borderRadius:10, border:"1px solid rgba(24,169,153,0.5)",
background:"rgba(24,169,153,0.15)", color:B.teal,
fontWeight:700, fontSize:13, fontFamily:"Satoshi, sans-serif", cursor:"pointer", whiteSpace:"nowrap",
}}>Envoyer par email →
) : emailStatus === "sent" ? (
✅
Aperçu email généré !
L'email a été préparé et ouvert dans un nouvel onglet.
) : emailStatus === "error" ? (
Erreur lors de la génération
setEmailStatus("idle")} style={{
marginTop:8, background:"none", border:"none", color:B.teal,
cursor:"pointer", textDecoration:"underline", fontSize:12, fontFamily:"Satoshi, sans-serif",
}}>Réessayer
) : (
📬 Envoyer le récapitulatif détaillé
setEmailName(e.target.value)}
style={{
flex:1, minWidth:140, background:"rgba(255,255,255,0.08)",
border:"1.5px solid rgba(255,255,255,0.15)", borderRadius:10,
color:"#fff", fontSize:13, fontFamily:"Satoshi, sans-serif",
padding:"11px 14px", outline:"none",
}}
/>
setEmail(e.target.value)}
style={{
flex:2, minWidth:180, background:"rgba(255,255,255,0.08)",
border:"1.5px solid rgba(255,255,255,0.15)", borderRadius:10,
color:"#fff", fontSize:13, fontFamily:"Satoshi, sans-serif",
padding:"11px 14px", outline:"none",
}}
/>
sendPDFByEmail(data, results, email, emailName, setEmailStatus)}
disabled={!email || emailStatus === "sending"}
style={{
flex:1, padding:"12px 20px", borderRadius:10, border:"none",
background: email ? B.gradOrange : "rgba(255,255,255,0.08)",
color: email ? B.navy : "rgba(255,255,255,0.3)",
fontWeight:800, fontSize:13, fontFamily:"Satoshi, sans-serif",
cursor: email ? "pointer" : "not-allowed",
}}
>
{emailStatus === "sending" ? "⏳ Génération en cours…" : "Générer et envoyer →"}
setShowEmailForm(false)} style={{
background:"none", border:"none", color:"rgba(255,255,255,0.25)",
cursor:"pointer", fontSize:12, fontFamily:"Satoshi, sans-serif",
}}>Annuler
Un aperçu de l'email s'ouvrira dans un nouvel onglet. Aucun spam.
)}
{/* CTA CRYPCOOL */}
Besoin d'un accompagnement personnalisé ?
Nos experts CrypCool vous aident à remplir vos formulaires 2086 et 3916-bis, optimiser votre fiscalité et rester conforme avec la réglementation 2026.
Consulter un expert CrypCool →
)}
)}
{/* FOOTER */}
{/* Trustpilot widget */}
{[["Flat Tax (revenus 2025)","30 %"],["IR forfaitaire","12,8 %"],["Prél. sociaux","17,2 %"],["Seuil exo.","305 €"],["Barème","+ 0,9 %"]].map(([k,v]) => (
{k}
{v}
))}
{/* ── DISCLAIMER LÉGAL COMPLET ────────────────────────────────── */}
📋 Informations légales & Confidentialité
{[
{
icon:"⚠️",
titre:"Avertissement",
texte:"Ce simulateur est fourni à titre purement informatif. Les résultats obtenus ne constituent pas un conseil fiscal, juridique ou financier. CrypCool ne saurait être tenu responsable de toute décision prise sur la base de ces estimations. Consultez un expert-comptable ou un conseiller fiscal agréé pour votre situation personnelle.",
},
{
icon:"🔒",
titre:"Protection de vos données",
texte:"Toutes les données fiscales saisies (montants, revenus, situation familiale) sont traitées exclusivement dans votre navigateur. Elles ne sont ni collectées, ni enregistrées, ni transmises à aucun serveur. Seul votre adresse email, si vous choisissez de la renseigner, est transmise à notre CRM (HubSpot) conformément à notre politique de confidentialité.",
},
{
icon:"📄",
titre:"Données sources",
texte:"Les calculs sont basés sur la Loi de finances 2026 (n°2026-103 du 19/02/2026), l'article 150 VH bis du CGI (méthode PAMP) et la documentation DGFiP. Les taux et seuils sont ceux applicables aux revenus 2025 déclarés en 2026. Ce simulateur ne couvre pas les cas particuliers (staking professionnel, BNC, mineurs de cryptomonnaies, etc.).",
},
].map(({ icon, titre, texte }) => (
))}
© 2026 CrypCool — Loi de finances n°2026-103 du 19/02/2026 · Art. 150 VH bis du CGI · PSAN enregistré AMF
);
}
// Mount
const container = document.getElementById('root');
const reactRoot = ReactDOM.createRoot(container);
reactRoot.render(React.createElement(App));
// Auto-resize pour iframe parent
const sendHeight = () => {
window.parent.postMessage({ type: 'cc-resize', height: document.body.scrollHeight + 20 }, '*');
};
new ResizeObserver(sendHeight).observe(document.body);