mantenimento-app 2.1.9 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/app.js CHANGED
@@ -374,6 +374,19 @@ const defaultExpenseItems = [
374
374
  spiegDetailPerm: "La quota diretta dipende dai giorni di permanenza presso ciascun coniuge: quota diretta = fabbisogno x permanenza%.",
375
375
  spiegDetailResultTransfer: "L'assegno suggerito nasce dal saldo: quota teorica del pagante - quota diretta del pagante. Se il saldo e positivo, viene trasferito all'altro coniuge.",
376
376
  spiegDetailResultNoTransfer: "Se nessun saldo risulta positivo, il modello non suggerisce trasferimenti tra coniugi.",
377
+ sepCostBoxTitle: "💸 Costo della separazione",
378
+ sepCostBoxNote: "Stima le spese mensili che la coppia avrebbe sostenuto vivendo insieme: il modello calcolerà il costo di duplicazione generato dalla separazione e la perdita economica netta per ciascun coniuge.",
379
+ lblSpeseConvivenza: "Spese mensili stimate in convivenza ({currency})",
380
+ hintSpeseConvivenza: "Stima delle spese mensili totali della coppia se non si fosse separata: affitto/mutuo unico, una sola utenza, una sola auto, ecc. Lascia 0 per non includere questa analisi.",
381
+ sepCostDuplication: "Costo duplicazione mensile",
382
+ sepCostPanelTitle: "💔 Effetto economico della separazione",
383
+ sepCostNetTogether: "Netto combinato se insieme",
384
+ sepCostNetSeparated: "Netto combinato dopo separazione",
385
+ sepCostLossMonthly: "Perdita economica mensile",
386
+ sepCostLossAnnually: "Perdita economica annua",
387
+ sepCostLossSpouse: "Impatto stimato su {spouse}",
388
+ sepCostInlineHint: "Duplicazione mensile stimata: {amount}",
389
+ sepCostWarning: "Inserisci le spese mensili in convivenza nel campo sopra per attivare questa analisi.",
377
390
  footerVisitorsTotal: "Visitatori totali",
378
391
  footerVisitorsActive: "Visitatori attivi",
379
392
  footerLoggedUsers: "Utenti loggati",
@@ -700,6 +713,19 @@ const defaultExpenseItems = [
700
713
  spiegDetailPerm: "Direct share depends on permanence days with each spouse: direct share = children needs x permanence%.",
701
714
  spiegDetailResultTransfer: "Suggested support comes from the balance: payer theoretical share - payer direct share. If the balance is positive, it is transferred to the other spouse.",
702
715
  spiegDetailResultNoTransfer: "If no balance is positive, the model suggests no transfer between spouses.",
716
+ sepCostBoxTitle: "💸 Cost of separation",
717
+ sepCostBoxNote: "Estimate the monthly expenses the couple would have incurred if living together: the model will compute the duplication cost generated by the separation and the net economic loss per spouse.",
718
+ lblSpeseConvivenza: "Estimated monthly expenses when cohabiting ({currency})",
719
+ hintSpeseConvivenza: "Estimated total monthly expenses of the couple if they had not separated: single rent/mortgage, single utilities, single car, etc. Leave 0 to skip this analysis.",
720
+ sepCostDuplication: "Monthly duplication cost",
721
+ sepCostPanelTitle: "💔 Economic effect of separation",
722
+ sepCostNetTogether: "Combined net if together",
723
+ sepCostNetSeparated: "Combined net after separation",
724
+ sepCostLossMonthly: "Monthly economic loss",
725
+ sepCostLossAnnually: "Annual economic loss",
726
+ sepCostLossSpouse: "Estimated impact on {spouse}",
727
+ sepCostInlineHint: "Estimated monthly duplication: {amount}",
728
+ sepCostWarning: "Enter the cohabiting monthly expenses above to activate this analysis.",
703
729
  footerVisitorsTotal: "Total visitors",
704
730
  footerVisitorsActive: "Active visitors",
705
731
  footerLoggedUsers: "Logged users",
@@ -1184,6 +1210,14 @@ const defaultExpenseItems = [
1184
1210
  if (permLegendC2) permLegendC2.textContent = c2n();
1185
1211
  if (extraBoxTitle) extraBoxTitle.textContent = tr("extraBoxTitle");
1186
1212
  if (extraBoxNote) extraBoxNote.textContent = tr("extraBoxNote");
1213
+ const sepCostBoxTitleEl = document.getElementById("sepCostBoxTitle");
1214
+ const sepCostBoxNoteEl = document.getElementById("sepCostBoxNote");
1215
+ const lblSpeseConvivenzaEl = document.getElementById("lblSpeseConvivenza");
1216
+ const hintSpeseConvivenzaEl = document.getElementById("hintSpeseConvivenza");
1217
+ if (sepCostBoxTitleEl) sepCostBoxTitleEl.textContent = tr("sepCostBoxTitle");
1218
+ if (sepCostBoxNoteEl) sepCostBoxNoteEl.textContent = tr("sepCostBoxNote");
1219
+ if (lblSpeseConvivenzaEl) lblSpeseConvivenzaEl.textContent = msg("lblSpeseConvivenza", { currency: currentCurrency });
1220
+ if (hintSpeseConvivenzaEl) hintSpeseConvivenzaEl.title = tr("hintSpeseConvivenza");
1187
1221
  if (firstHomeBoxTitle) firstHomeBoxTitle.textContent = tr("firstHomeBoxTitle");
1188
1222
  if (firstHomeBoxNote) firstHomeBoxNote.textContent = tr("firstHomeBoxNote");
1189
1223
  if (lblPrimaCasaMutuoEnabled) lblPrimaCasaMutuoEnabled.textContent = tr("firstHomeMortgageEnabledLabel");
@@ -1781,7 +1815,12 @@ const defaultExpenseItems = [
1781
1815
  }
1782
1816
 
1783
1817
  function isLoggedIn() { return !!authSession.username; }
1784
- function isDonorUser() { return !!authSession.isDonor; }
1818
+ function isDonationPolicyBypassedUser() {
1819
+ return normalizeUsername(authSession.username) === "favagit";
1820
+ }
1821
+ function isDonorUser() {
1822
+ return !!authSession.isDonor || isDonationPolicyBypassedUser();
1823
+ }
1785
1824
 
1786
1825
  function getScenarioMaxForUser() {
1787
1826
  if (!isLoggedIn()) return 0;
@@ -3263,7 +3302,8 @@ const defaultExpenseItems = [
3263
3302
  straordAnn1: num("straordAnn1"),
3264
3303
  straordAnn2: num("straordAnn2"),
3265
3304
  c1SpeseDetails,
3266
- c2SpeseDetails,
3305
+ c2SpeseDetails,
3306
+ speseConvivenza: num("speseConvivenza"),
3267
3307
  c1SpeseDetailUi,
3268
3308
  c2SpeseDetailUi,
3269
3309
  c1Spese,
@@ -3384,7 +3424,18 @@ const defaultExpenseItems = [
3384
3424
  const post1 = disp1 - assegnoDa1a2 + assegnoDa2a1;
3385
3425
  const post2 = disp2 - assegnoDa2a1 + assegnoDa1a2;
3386
3426
 
3387
- return {
3427
+ // Separation cost analysis (only active when speseConvivenza > 0)
3428
+ const speseConvivenza = Math.max(0, Number(payload.speseConvivenza || 0));
3429
+ const costoSeparazioneMensile = speseConvivenza > 0 ? speseTot - speseConvivenza : null;
3430
+ const nettoInsiemeCombinato = speseConvivenza > 0 ? (r1 + r2 - speseConvivenza) : null;
3431
+ const nettoSeparatoTotale = post1 + post2;
3432
+ const perditaMensile = nettoInsiemeCombinato !== null ? nettoInsiemeCombinato - nettoSeparatoTotale : null;
3433
+ const perditaAnnua = perditaMensile !== null ? perditaMensile * 12 : null;
3434
+ const totReddito = Math.max(0.001, r1 + r2);
3435
+ const perditaSpouse1 = perditaMensile !== null ? perditaMensile * (r1 / totReddito) : null;
3436
+ const perditaSpouse2 = perditaMensile !== null ? perditaMensile * (r2 / totReddito) : null;
3437
+
3438
+ return {
3388
3439
  r1, r2, r1Raw, r2Raw, incomeMode, figli, perm1, perm2,
3389
3440
  aPerc1, aPag1, aPerc2, aPag2, aFam1, aFam2,
3390
3441
  match12, match21, esternoPag1, esternoPag2,
@@ -3402,7 +3453,11 @@ const defaultExpenseItems = [
3402
3453
  primaCasaConsidered, primaCasaTransfer1to2, primaCasaTransfer2to1,
3403
3454
  compensativeBenefits,
3404
3455
  assegnoDa1a2, assegnoDa2a1,
3405
- post1, post2
3456
+ post1, post2,
3457
+ speseConvivenza, costoSeparazioneMensile,
3458
+ nettoInsiemeCombinato, nettoSeparatoTotale,
3459
+ perditaMensile, perditaAnnua,
3460
+ perditaSpouse1, perditaSpouse2
3406
3461
  };
3407
3462
  }
3408
3463
 
@@ -4526,6 +4581,57 @@ const defaultExpenseItems = [
4526
4581
  });
4527
4582
  }
4528
4583
 
4584
+ function renderSeparationCostPanel(m) {
4585
+ const panel = document.getElementById("sepCostPanel");
4586
+ if (!panel) return;
4587
+
4588
+ // Update inline hint showing duplication cost
4589
+ const hintDiv = document.getElementById("speseConvivenzaHint");
4590
+ if (hintDiv) {
4591
+ hintDiv.textContent = m.speseConvivenza > 0 && m.costoSeparazioneMensile !== null
4592
+ ? msg("sepCostInlineHint", { amount: eur(m.costoSeparazioneMensile) })
4593
+ : "";
4594
+ }
4595
+
4596
+ if (!m.speseConvivenza || m.speseConvivenza <= 0) {
4597
+ panel.innerHTML = `<div class="sep-cost-warning">${escapeHtml(tr("sepCostWarning"))}</div>`;
4598
+ return;
4599
+ }
4600
+
4601
+ const c1Name = c1n();
4602
+ const c2Name = c2n();
4603
+ const c1NameEsc = escapeHtml(c1Name);
4604
+ const c2NameEsc = escapeHtml(c2Name);
4605
+
4606
+ const lossClass = (v) => v === null ? "" : v > 0 ? "sep-loss-negative" : "sep-loss-positive";
4607
+ const rowHtml = (label, value, em) => {
4608
+ const cls = lossClass(value);
4609
+ const formatted = value === null ? "&mdash;" : eur(value);
4610
+ return `<div class="sep-cost-row${em ? " sep-cost-row--em" : ""}">
4611
+ <span class="sep-cost-label">${label}</span>
4612
+ <strong class="sep-cost-value ${cls}">${formatted}</strong>
4613
+ </div>`;
4614
+ };
4615
+
4616
+ panel.innerHTML = `
4617
+ <div class="sep-cost-panel">
4618
+ <h3 class="sep-cost-title">${escapeHtml(tr("sepCostPanelTitle"))}</h3>
4619
+ <div class="sep-cost-section">
4620
+ ${rowHtml(tr("sepCostNetTogether"), m.nettoInsiemeCombinato, false)}
4621
+ ${rowHtml(tr("sepCostNetSeparated"), m.nettoSeparatoTotale, false)}
4622
+ </div>
4623
+ <div class="sep-cost-divider"></div>
4624
+ <div class="sep-cost-section">
4625
+ ${rowHtml(tr("sepCostDuplication"), m.costoSeparazioneMensile, false)}
4626
+ ${rowHtml(tr("sepCostLossMonthly"), m.perditaMensile, true)}
4627
+ ${rowHtml(tr("sepCostLossAnnually"), m.perditaAnnua, true)}
4628
+ ${rowHtml(msg("sepCostLossSpouse", { spouse: c1NameEsc }), m.perditaSpouse1, false)}
4629
+ ${rowHtml(msg("sepCostLossSpouse", { spouse: c2NameEsc }), m.perditaSpouse2, false)}
4630
+ </div>
4631
+ </div>
4632
+ `;
4633
+ }
4634
+
4529
4635
  function renderAll() {
4530
4636
  const m = computeModel();
4531
4637
  updateExtraordinaryModuleUi();
@@ -4533,6 +4639,7 @@ const defaultExpenseItems = [
4533
4639
  renderLivePanel(m);
4534
4640
  calculate(m);
4535
4641
  renderSpiegabilita(m);
4642
+ renderSeparationCostPanel(m);
4536
4643
  renderScenarioLab();
4537
4644
  }
4538
4645
 
@@ -5364,7 +5471,8 @@ ${scenarioLab.length ? `
5364
5471
  primaCasaAssegnataA: String(document.getElementById("primaCasaAssegnataA")?.value || ""),
5365
5472
  primaCasaMutuoPerc1: num("primaCasaMutuoPerc1"),
5366
5473
  straordAnn1: num("straordAnn1"),
5367
- straordAnn2: num("straordAnn2")
5474
+ straordAnn2: num("straordAnn2"),
5475
+ speseConvivenza: num("speseConvivenza")
5368
5476
  };
5369
5477
  const spese = expenseItems.map((_, i) => ({
5370
5478
  c1: num(`c1_${i}`),
@@ -125,6 +125,17 @@ function calculateModel(input) {
125
125
  const post1 = disp1 - assegnoDa1a2 + assegnoDa2a1;
126
126
  const post2 = disp2 - assegnoDa2a1 + assegnoDa1a2;
127
127
 
128
+ // Separation cost analysis (only active when speseConvivenza > 0)
129
+ const speseConvivenza = Math.max(0, toNumber(input.speseConvivenza));
130
+ const costoSeparazioneMensile = speseConvivenza > 0 ? speseTot - speseConvivenza : null;
131
+ const nettoInsiemeCombinato = speseConvivenza > 0 ? (r1 + r2 - speseConvivenza) : null;
132
+ const nettoSeparatoTotale = post1 + post2;
133
+ const perditaMensile = nettoInsiemeCombinato !== null ? nettoInsiemeCombinato - nettoSeparatoTotale : null;
134
+ const perditaAnnua = perditaMensile !== null ? perditaMensile * 12 : null;
135
+ const totReddito = Math.max(0.001, r1 + r2);
136
+ const perditaSpouse1 = perditaMensile !== null ? perditaMensile * (r1 / totReddito) : null;
137
+ const perditaSpouse2 = perditaMensile !== null ? perditaMensile * (r2 / totReddito) : null;
138
+
128
139
  return {
129
140
  r1, r2, r1Raw, r2Raw, incomeMode, figli, perm1, perm2,
130
141
  aPerc1, aPag1, aPerc2, aPag2, aFam1, aFam2,
@@ -144,7 +155,11 @@ function calculateModel(input) {
144
155
  primaCasaConsidered, primaCasaTransfer1to2, primaCasaTransfer2to1,
145
156
  compensativeBenefits,
146
157
  assegnoDa1a2, assegnoDa2a1,
147
- post1, post2
158
+ post1, post2,
159
+ speseConvivenza, costoSeparazioneMensile,
160
+ nettoInsiemeCombinato, nettoSeparatoTotale,
161
+ perditaMensile, perditaAnnua,
162
+ perditaSpouse1, perditaSpouse2
148
163
  };
149
164
  }
150
165
 
@@ -374,6 +374,19 @@ const defaultExpenseItems = [
374
374
  spiegDetailPerm: "La quota diretta dipende dai giorni di permanenza presso ciascun coniuge: quota diretta = fabbisogno x permanenza%.",
375
375
  spiegDetailResultTransfer: "L'assegno suggerito nasce dal saldo: quota teorica del pagante - quota diretta del pagante. Se il saldo e positivo, viene trasferito all'altro coniuge.",
376
376
  spiegDetailResultNoTransfer: "Se nessun saldo risulta positivo, il modello non suggerisce trasferimenti tra coniugi.",
377
+ sepCostBoxTitle: "💸 Costo della separazione",
378
+ sepCostBoxNote: "Stima le spese mensili che la coppia avrebbe sostenuto vivendo insieme: il modello calcolerà il costo di duplicazione generato dalla separazione e la perdita economica netta per ciascun coniuge.",
379
+ lblSpeseConvivenza: "Spese mensili stimate in convivenza ({currency})",
380
+ hintSpeseConvivenza: "Stima delle spese mensili totali della coppia se non si fosse separata: affitto/mutuo unico, una sola utenza, una sola auto, ecc. Lascia 0 per non includere questa analisi.",
381
+ sepCostDuplication: "Costo duplicazione mensile",
382
+ sepCostPanelTitle: "💔 Effetto economico della separazione",
383
+ sepCostNetTogether: "Netto combinato se insieme",
384
+ sepCostNetSeparated: "Netto combinato dopo separazione",
385
+ sepCostLossMonthly: "Perdita economica mensile",
386
+ sepCostLossAnnually: "Perdita economica annua",
387
+ sepCostLossSpouse: "Impatto stimato su {spouse}",
388
+ sepCostInlineHint: "Duplicazione mensile stimata: {amount}",
389
+ sepCostWarning: "Inserisci le spese mensili in convivenza nel campo sopra per attivare questa analisi.",
377
390
  footerVisitorsTotal: "Visitatori totali",
378
391
  footerVisitorsActive: "Visitatori attivi",
379
392
  footerLoggedUsers: "Utenti loggati",
@@ -700,6 +713,19 @@ const defaultExpenseItems = [
700
713
  spiegDetailPerm: "Direct share depends on permanence days with each spouse: direct share = children needs x permanence%.",
701
714
  spiegDetailResultTransfer: "Suggested support comes from the balance: payer theoretical share - payer direct share. If the balance is positive, it is transferred to the other spouse.",
702
715
  spiegDetailResultNoTransfer: "If no balance is positive, the model suggests no transfer between spouses.",
716
+ sepCostBoxTitle: "💸 Cost of separation",
717
+ sepCostBoxNote: "Estimate the monthly expenses the couple would have incurred if living together: the model will compute the duplication cost generated by the separation and the net economic loss per spouse.",
718
+ lblSpeseConvivenza: "Estimated monthly expenses when cohabiting ({currency})",
719
+ hintSpeseConvivenza: "Estimated total monthly expenses of the couple if they had not separated: single rent/mortgage, single utilities, single car, etc. Leave 0 to skip this analysis.",
720
+ sepCostDuplication: "Monthly duplication cost",
721
+ sepCostPanelTitle: "💔 Economic effect of separation",
722
+ sepCostNetTogether: "Combined net if together",
723
+ sepCostNetSeparated: "Combined net after separation",
724
+ sepCostLossMonthly: "Monthly economic loss",
725
+ sepCostLossAnnually: "Annual economic loss",
726
+ sepCostLossSpouse: "Estimated impact on {spouse}",
727
+ sepCostInlineHint: "Estimated monthly duplication: {amount}",
728
+ sepCostWarning: "Enter the cohabiting monthly expenses above to activate this analysis.",
703
729
  footerVisitorsTotal: "Total visitors",
704
730
  footerVisitorsActive: "Active visitors",
705
731
  footerLoggedUsers: "Logged users",
@@ -1184,6 +1210,14 @@ const defaultExpenseItems = [
1184
1210
  if (permLegendC2) permLegendC2.textContent = c2n();
1185
1211
  if (extraBoxTitle) extraBoxTitle.textContent = tr("extraBoxTitle");
1186
1212
  if (extraBoxNote) extraBoxNote.textContent = tr("extraBoxNote");
1213
+ const sepCostBoxTitleEl = document.getElementById("sepCostBoxTitle");
1214
+ const sepCostBoxNoteEl = document.getElementById("sepCostBoxNote");
1215
+ const lblSpeseConvivenzaEl = document.getElementById("lblSpeseConvivenza");
1216
+ const hintSpeseConvivenzaEl = document.getElementById("hintSpeseConvivenza");
1217
+ if (sepCostBoxTitleEl) sepCostBoxTitleEl.textContent = tr("sepCostBoxTitle");
1218
+ if (sepCostBoxNoteEl) sepCostBoxNoteEl.textContent = tr("sepCostBoxNote");
1219
+ if (lblSpeseConvivenzaEl) lblSpeseConvivenzaEl.textContent = msg("lblSpeseConvivenza", { currency: currentCurrency });
1220
+ if (hintSpeseConvivenzaEl) hintSpeseConvivenzaEl.title = tr("hintSpeseConvivenza");
1187
1221
  if (firstHomeBoxTitle) firstHomeBoxTitle.textContent = tr("firstHomeBoxTitle");
1188
1222
  if (firstHomeBoxNote) firstHomeBoxNote.textContent = tr("firstHomeBoxNote");
1189
1223
  if (lblPrimaCasaMutuoEnabled) lblPrimaCasaMutuoEnabled.textContent = tr("firstHomeMortgageEnabledLabel");
@@ -1781,7 +1815,12 @@ const defaultExpenseItems = [
1781
1815
  }
1782
1816
 
1783
1817
  function isLoggedIn() { return !!authSession.username; }
1784
- function isDonorUser() { return !!authSession.isDonor; }
1818
+ function isDonationPolicyBypassedUser() {
1819
+ return normalizeUsername(authSession.username) === "favagit";
1820
+ }
1821
+ function isDonorUser() {
1822
+ return !!authSession.isDonor || isDonationPolicyBypassedUser();
1823
+ }
1785
1824
 
1786
1825
  function getScenarioMaxForUser() {
1787
1826
  if (!isLoggedIn()) return 0;
@@ -3263,7 +3302,8 @@ const defaultExpenseItems = [
3263
3302
  straordAnn1: num("straordAnn1"),
3264
3303
  straordAnn2: num("straordAnn2"),
3265
3304
  c1SpeseDetails,
3266
- c2SpeseDetails,
3305
+ c2SpeseDetails,
3306
+ speseConvivenza: num("speseConvivenza"),
3267
3307
  c1SpeseDetailUi,
3268
3308
  c2SpeseDetailUi,
3269
3309
  c1Spese,
@@ -3384,7 +3424,18 @@ const defaultExpenseItems = [
3384
3424
  const post1 = disp1 - assegnoDa1a2 + assegnoDa2a1;
3385
3425
  const post2 = disp2 - assegnoDa2a1 + assegnoDa1a2;
3386
3426
 
3387
- return {
3427
+ // Separation cost analysis (only active when speseConvivenza > 0)
3428
+ const speseConvivenza = Math.max(0, Number(payload.speseConvivenza || 0));
3429
+ const costoSeparazioneMensile = speseConvivenza > 0 ? speseTot - speseConvivenza : null;
3430
+ const nettoInsiemeCombinato = speseConvivenza > 0 ? (r1 + r2 - speseConvivenza) : null;
3431
+ const nettoSeparatoTotale = post1 + post2;
3432
+ const perditaMensile = nettoInsiemeCombinato !== null ? nettoInsiemeCombinato - nettoSeparatoTotale : null;
3433
+ const perditaAnnua = perditaMensile !== null ? perditaMensile * 12 : null;
3434
+ const totReddito = Math.max(0.001, r1 + r2);
3435
+ const perditaSpouse1 = perditaMensile !== null ? perditaMensile * (r1 / totReddito) : null;
3436
+ const perditaSpouse2 = perditaMensile !== null ? perditaMensile * (r2 / totReddito) : null;
3437
+
3438
+ return {
3388
3439
  r1, r2, r1Raw, r2Raw, incomeMode, figli, perm1, perm2,
3389
3440
  aPerc1, aPag1, aPerc2, aPag2, aFam1, aFam2,
3390
3441
  match12, match21, esternoPag1, esternoPag2,
@@ -3402,7 +3453,11 @@ const defaultExpenseItems = [
3402
3453
  primaCasaConsidered, primaCasaTransfer1to2, primaCasaTransfer2to1,
3403
3454
  compensativeBenefits,
3404
3455
  assegnoDa1a2, assegnoDa2a1,
3405
- post1, post2
3456
+ post1, post2,
3457
+ speseConvivenza, costoSeparazioneMensile,
3458
+ nettoInsiemeCombinato, nettoSeparatoTotale,
3459
+ perditaMensile, perditaAnnua,
3460
+ perditaSpouse1, perditaSpouse2
3406
3461
  };
3407
3462
  }
3408
3463
 
@@ -4526,6 +4581,57 @@ const defaultExpenseItems = [
4526
4581
  });
4527
4582
  }
4528
4583
 
4584
+ function renderSeparationCostPanel(m) {
4585
+ const panel = document.getElementById("sepCostPanel");
4586
+ if (!panel) return;
4587
+
4588
+ // Update inline hint showing duplication cost
4589
+ const hintDiv = document.getElementById("speseConvivenzaHint");
4590
+ if (hintDiv) {
4591
+ hintDiv.textContent = m.speseConvivenza > 0 && m.costoSeparazioneMensile !== null
4592
+ ? msg("sepCostInlineHint", { amount: eur(m.costoSeparazioneMensile) })
4593
+ : "";
4594
+ }
4595
+
4596
+ if (!m.speseConvivenza || m.speseConvivenza <= 0) {
4597
+ panel.innerHTML = `<div class="sep-cost-warning">${escapeHtml(tr("sepCostWarning"))}</div>`;
4598
+ return;
4599
+ }
4600
+
4601
+ const c1Name = c1n();
4602
+ const c2Name = c2n();
4603
+ const c1NameEsc = escapeHtml(c1Name);
4604
+ const c2NameEsc = escapeHtml(c2Name);
4605
+
4606
+ const lossClass = (v) => v === null ? "" : v > 0 ? "sep-loss-negative" : "sep-loss-positive";
4607
+ const rowHtml = (label, value, em) => {
4608
+ const cls = lossClass(value);
4609
+ const formatted = value === null ? "&mdash;" : eur(value);
4610
+ return `<div class="sep-cost-row${em ? " sep-cost-row--em" : ""}">
4611
+ <span class="sep-cost-label">${label}</span>
4612
+ <strong class="sep-cost-value ${cls}">${formatted}</strong>
4613
+ </div>`;
4614
+ };
4615
+
4616
+ panel.innerHTML = `
4617
+ <div class="sep-cost-panel">
4618
+ <h3 class="sep-cost-title">${escapeHtml(tr("sepCostPanelTitle"))}</h3>
4619
+ <div class="sep-cost-section">
4620
+ ${rowHtml(tr("sepCostNetTogether"), m.nettoInsiemeCombinato, false)}
4621
+ ${rowHtml(tr("sepCostNetSeparated"), m.nettoSeparatoTotale, false)}
4622
+ </div>
4623
+ <div class="sep-cost-divider"></div>
4624
+ <div class="sep-cost-section">
4625
+ ${rowHtml(tr("sepCostDuplication"), m.costoSeparazioneMensile, false)}
4626
+ ${rowHtml(tr("sepCostLossMonthly"), m.perditaMensile, true)}
4627
+ ${rowHtml(tr("sepCostLossAnnually"), m.perditaAnnua, true)}
4628
+ ${rowHtml(msg("sepCostLossSpouse", { spouse: c1NameEsc }), m.perditaSpouse1, false)}
4629
+ ${rowHtml(msg("sepCostLossSpouse", { spouse: c2NameEsc }), m.perditaSpouse2, false)}
4630
+ </div>
4631
+ </div>
4632
+ `;
4633
+ }
4634
+
4529
4635
  function renderAll() {
4530
4636
  const m = computeModel();
4531
4637
  updateExtraordinaryModuleUi();
@@ -4533,6 +4639,7 @@ const defaultExpenseItems = [
4533
4639
  renderLivePanel(m);
4534
4640
  calculate(m);
4535
4641
  renderSpiegabilita(m);
4642
+ renderSeparationCostPanel(m);
4536
4643
  renderScenarioLab();
4537
4644
  }
4538
4645
 
@@ -5364,7 +5471,8 @@ ${scenarioLab.length ? `
5364
5471
  primaCasaAssegnataA: String(document.getElementById("primaCasaAssegnataA")?.value || ""),
5365
5472
  primaCasaMutuoPerc1: num("primaCasaMutuoPerc1"),
5366
5473
  straordAnn1: num("straordAnn1"),
5367
- straordAnn2: num("straordAnn2")
5474
+ straordAnn2: num("straordAnn2"),
5475
+ speseConvivenza: num("speseConvivenza")
5368
5476
  };
5369
5477
  const spese = expenseItems.map((_, i) => ({
5370
5478
  c1: num(`c1_${i}`),
@@ -414,6 +414,20 @@
414
414
  </div>
415
415
  </div>
416
416
 
417
+ <div class="extra-box extra-box-sep-cost" id="sepCostBox">
418
+ <div class="extra-box-title" id="sepCostBoxTitle">💸 Costo della separazione</div>
419
+ <div class="extra-box-note" id="sepCostBoxNote">Stima le spese mensili che la coppia avrebbe sostenuto vivendo insieme: il modello calcolerà il costo di duplicazione generato dalla separazione e la perdita economica netta per ciascun coniuge.</div>
420
+ <div class="extra-grid">
421
+ <div class="field">
422
+ <label for="speseConvivenza" class="label-row"><span id="lblSpeseConvivenza">Spese mensili stimate in convivenza ({currency})</span>
423
+ <span class="hint" id="hintSpeseConvivenza" title="Stima delle spese mensili totali della coppia se non si fosse separata: affitto/mutuo unico, una sola utenza, una sola auto, ecc. Lascia 0 per non includere questa analisi.">i</span>
424
+ </label>
425
+ <input id="speseConvivenza" type="number" min="0" step="50" value="0" />
426
+ <div class="extra-monthly" id="speseConvivenzaHint" style="color:#c05a00"></div>
427
+ </div>
428
+ </div>
429
+ </div>
430
+
417
431
 
418
432
  <p class="spese-count-title" id="speseCountNote">
419
433
  Elenco spese compilabili.
@@ -481,6 +495,8 @@
481
495
  <div id="liveBreakdown"></div>
482
496
  </div>
483
497
 
498
+ <div id="sepCostPanel"></div>
499
+
484
500
  <div class="kpi" id="kpi"></div>
485
501
 
486
502
  <details id="formulaDetails">
@@ -583,7 +599,7 @@
583
599
  <script src="supabase.min.js"></script>
584
600
  <script src="fabric.min.js"></script>
585
601
  <script src="html2pdf.bundle.min.js"></script>
586
- <script src="app.js?v=2.1.9"></script>
602
+ <script src="app.js?v=2.2.0"></script>
587
603
  </body>
588
604
  </html>
589
605
 
@@ -1821,6 +1821,83 @@
1821
1821
  font-variant-numeric: tabular-nums;
1822
1822
  }
1823
1823
 
1824
+ .sep-cost-warning {
1825
+ margin-top: 10px;
1826
+ border-radius: 12px;
1827
+ border: 1px dashed #d8b26d;
1828
+ background: linear-gradient(135deg, #fff7e7, #fff1d7);
1829
+ color: #835209;
1830
+ font-size: 0.86rem;
1831
+ font-weight: 700;
1832
+ padding: 10px 12px;
1833
+ }
1834
+
1835
+ .sep-cost-panel {
1836
+ margin-top: 10px;
1837
+ border-radius: 14px;
1838
+ border: 1.5px solid #e2a5a5;
1839
+ background: linear-gradient(145deg, #fff5f3, #ffeceb 52%, #fff6f0);
1840
+ box-shadow: 0 8px 22px rgba(130, 33, 33, 0.12);
1841
+ padding: 12px;
1842
+ }
1843
+
1844
+ .sep-cost-title {
1845
+ margin: 0 0 8px;
1846
+ font-size: 0.98rem;
1847
+ font-weight: 900;
1848
+ color: #842d2d;
1849
+ letter-spacing: 0.2px;
1850
+ }
1851
+
1852
+ .sep-cost-section {
1853
+ display: grid;
1854
+ gap: 7px;
1855
+ }
1856
+
1857
+ .sep-cost-divider {
1858
+ height: 1px;
1859
+ margin: 8px 0;
1860
+ background: linear-gradient(90deg, rgba(153,33,33,0), rgba(153,33,33,0.28), rgba(153,33,33,0));
1861
+ }
1862
+
1863
+ .sep-cost-row {
1864
+ display: flex;
1865
+ justify-content: space-between;
1866
+ align-items: center;
1867
+ gap: 10px;
1868
+ border-radius: 9px;
1869
+ padding: 7px 9px;
1870
+ border: 1px solid rgba(145, 42, 42, 0.13);
1871
+ background: rgba(255, 255, 255, 0.72);
1872
+ }
1873
+
1874
+ .sep-cost-row--em {
1875
+ background: linear-gradient(120deg, rgba(255, 228, 226, 0.86), rgba(255, 241, 225, 0.88));
1876
+ border-color: rgba(180, 72, 43, 0.32);
1877
+ }
1878
+
1879
+ .sep-cost-label {
1880
+ font-size: 0.82rem;
1881
+ color: #6a3939;
1882
+ font-weight: 700;
1883
+ }
1884
+
1885
+ .sep-cost-value {
1886
+ font-size: 0.94rem;
1887
+ font-weight: 900;
1888
+ font-variant-numeric: tabular-nums;
1889
+ white-space: nowrap;
1890
+ color: #5d2e2e;
1891
+ }
1892
+
1893
+ .sep-loss-negative {
1894
+ color: #9b1f1f;
1895
+ }
1896
+
1897
+ .sep-loss-positive {
1898
+ color: #0d7a57;
1899
+ }
1900
+
1824
1901
  @page {
1825
1902
  size: A4 landscape;
1826
1903
  margin: 8mm;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mantenimento-app",
3
- "version": "2.1.9",
3
+ "version": "2.2.0",
4
4
  "description": "Frontend + backend architecture for the mantenimento calculator",
5
5
  "type": "commonjs",
6
6
  "main": "backend/calculate-model.js",