mantenimento-app 2.1.1 → 2.1.3

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
@@ -28,6 +28,8 @@ const defaultExpenseItems = [
28
28
  let scenarioTransitionTimer = null;
29
29
  const SCENARIO_LAB_MAX = 3;
30
30
  const SCENARIO_LABELS = ["A", "B", "C"];
31
+ const EXPENSE_DETAIL_MAX_CHARS = 560;
32
+ const EXPENSE_DETAIL_MAX_LINES = 10;
31
33
 
32
34
  const QUOTA_MANTENIMENTO_PERC = 35;
33
35
 
@@ -228,6 +230,7 @@ const defaultExpenseItems = [
228
230
  expenseDetailBtn: "Dettaglio",
229
231
  expenseDetailTitle: "Apri dettaglio voce spesa",
230
232
  expenseDetailPlaceholder: "Scrivi qui il dettaglio di questa cifra (es. mesi, quota, riferimento).",
233
+ expenseDetailCharsRemaining: "Caratteri rimanenti: {count}",
231
234
  expenseRemoveTitle: "Rimuovi voce spesa",
232
235
  expenseRemoveBtn: "Rimuovi",
233
236
  expenseMinOneAlert: "Deve restare almeno una voce spesa.",
@@ -254,6 +257,33 @@ const defaultExpenseItems = [
254
257
  extraAnnHint1: "Quota annuale straordinaria stimata a carico di {spouse} (es. sanitarie non ricorrenti, scolastiche extra, attività non ordinarie).",
255
258
  extraAnnHint2: "Quota annuale straordinaria stimata a carico di {spouse} (es. sanitarie non ricorrenti, scolastiche extra, attività non ordinarie).",
256
259
  extraMonthlyEstimate: "Quota mensile stimata: {amount}",
260
+ firstHomeBoxTitle: "🏡 Mutuo prima casa ceduta",
261
+ firstHomeBoxNote: "Dichiara se esiste un mutuo sulla prima casa dei coniugi ceduta a uno dei due: il modello considera il trasferimento implicito quando la casa e assegnata al collocatario.",
262
+ firstHomeMortgageEnabledLabel: "Mutuo su prima casa dei coniugi",
263
+ firstHomeMortgageEnabledHint: "Attiva per includere il mutuo della prima casa ceduta nei benefici compensativi.",
264
+ firstHomeMortgageAmountLabel: "Rata mutuo mensile ({currency})",
265
+ firstHomeMortgageAmountHint: "Importo mensile complessivo della rata del mutuo prima casa.",
266
+ firstHomeAssignedToLabel: "Casa assegnata a",
267
+ firstHomeAssignedToHint: "Seleziona il coniuge a cui e ceduta la prima casa.",
268
+ firstHomeAssignedToNone: "Nessuna cessione",
269
+ firstHomeAssignedToSpouse: "Casa ceduta a {spouse}",
270
+ firstHomeSplitLabel: "Quota mutuo a carico {spouse} (%)",
271
+ firstHomeSplitHint: "Percentuale della rata mutuo pagata da {spouse}. La quota dell'altro coniuge e complementare a 100%.",
272
+ firstHomeSplitInfo: "Ripartizione mutuo: {spouse1} {p1}% · {spouse2} {p2}%",
273
+ calcCompBenefitsLabel: "Benefici compensativi gia allocati",
274
+ calcNoTransferWithBenefits: "Nessun trasferimento monetario suggerito. Benefici gia allocati: {benefits}.",
275
+ calcBenefitFamilyAllowance: "Assegno familiare INPS percepito da {spouse}",
276
+ calcBenefitPrimaryHomeMortgage: "Quota mutuo prima casa ceduta al collocatario ({payer} -> {receiver})",
277
+ pdfCompBenefitsSection: "Benefici compensativi gia allocati",
278
+ pdfCompBenefitsItem: "Beneficio",
279
+ pdfCompBenefitsAmount: "Valore {currency}/mese",
280
+ pdfCompBenefitsNone: "Nessun beneficio compensativo aggiuntivo dichiarato.",
281
+ pdfPrimaryHomeMortgage: "Mutuo prima casa ceduta",
282
+ pdfPrimaryHomeNotDeclared: "Non dichiarato",
283
+ pdfPrimaryHomeAssignedTo: "Assegnata a",
284
+ pdfPrimaryHomeMonthlyAmount: "Rata mensile",
285
+ pdfPrimaryHomeSplit: "Ripartizione mutuo",
286
+ pdfPrimaryHomeAppliedOnlyColl: "Considerato solo se casa ceduta al collocatario.",
257
287
  pdfExtraordinaryRow: "Spese straordinarie (quota mensile da annuo)",
258
288
  liveTotalIncome: "Entrate totali (reddito + assegni + INPS)",
259
289
  livePaidToOther: "Assegno mantenimento pagato all'altro coniuge",
@@ -328,10 +358,10 @@ const defaultExpenseItems = [
328
358
  scenarioColFabb: "Fabbis. figli",
329
359
  scenarioDeltaLabel: "\u0394 vs A",
330
360
  spiegTitle: "💡 Perché questo risultato?",
331
- spiegRedditiLabel: "💰 Redditi (peso contributivo)",
332
- spiegSpeseLabel: "👶 Spese → fabbisogno figli",
333
- spiegPermLabel: "📅 Permanenza → quota diretta",
334
- spiegResultLabel: "🎯 Come si forma l'assegno",
361
+ spiegRedditiLabel: "Redditi (peso contributivo)",
362
+ spiegSpeseLabel: "Spese → fabbisogno figli",
363
+ spiegPermLabel: "Permanenza → quota diretta",
364
+ spiegResultLabel: "Come si forma l'assegno",
335
365
  spiegTooltipTrigger: "Dettaglio calcolo",
336
366
  spiegDetailIncome: "Si parte dal netto disponibile di ciascun coniuge. Il peso contributivo e dato da netto coniuge / somma dei netti positivi.",
337
367
  spiegDetailExpense: "Il fabbisogno figli viene stimato come 35% del totale spese inserite (ordinarie + eventuali straordinarie mensilizzate).",
@@ -521,6 +551,7 @@ const defaultExpenseItems = [
521
551
  expenseDetailBtn: "Detail",
522
552
  expenseDetailTitle: "Open expense detail",
523
553
  expenseDetailPlaceholder: "Write details for this amount (e.g. months, share, reference).",
554
+ expenseDetailCharsRemaining: "Remaining characters: {count}",
524
555
  expenseRemoveTitle: "Remove expense item",
525
556
  expenseRemoveBtn: "Remove",
526
557
  expenseMinOneAlert: "At least one expense item must remain.",
@@ -547,6 +578,33 @@ const defaultExpenseItems = [
547
578
  extraAnnHint1: "Estimated yearly extraordinary share for {spouse} (e.g., non-recurring medical, extra school, non-ordinary activities).",
548
579
  extraAnnHint2: "Estimated yearly extraordinary share for {spouse} (e.g., non-recurring medical, extra school, non-ordinary activities).",
549
580
  extraMonthlyEstimate: "Estimated monthly share: {amount}",
581
+ firstHomeBoxTitle: "🏡 Assigned primary home mortgage",
582
+ firstHomeBoxNote: "Declare whether there is a mortgage on the spouses' primary home assigned to one spouse: the model counts the implicit transfer when the home is assigned to the custodial parent.",
583
+ firstHomeMortgageEnabledLabel: "Mortgage on spouses' primary home",
584
+ firstHomeMortgageEnabledHint: "Enable to include the assigned primary-home mortgage in compensative benefits.",
585
+ firstHomeMortgageAmountLabel: "Monthly mortgage payment ({currency})",
586
+ firstHomeMortgageAmountHint: "Total monthly amount of the primary-home mortgage payment.",
587
+ firstHomeAssignedToLabel: "Home assigned to",
588
+ firstHomeAssignedToHint: "Select which spouse receives assignment of the primary home.",
589
+ firstHomeAssignedToNone: "No assignment",
590
+ firstHomeAssignedToSpouse: "Home assigned to {spouse}",
591
+ firstHomeSplitLabel: "Mortgage share paid by {spouse} (%)",
592
+ firstHomeSplitHint: "Percentage of the monthly mortgage payment paid by {spouse}. The other spouse share is the complement to 100%.",
593
+ firstHomeSplitInfo: "Mortgage split: {spouse1} {p1}% · {spouse2} {p2}%",
594
+ calcCompBenefitsLabel: "Compensative benefits already allocated",
595
+ calcNoTransferWithBenefits: "No monetary transfer suggested. Already allocated benefits: {benefits}.",
596
+ calcBenefitFamilyAllowance: "INPS family allowance received by {spouse}",
597
+ calcBenefitPrimaryHomeMortgage: "Primary-home mortgage share assigned to custodial parent ({payer} -> {receiver})",
598
+ pdfCompBenefitsSection: "Compensative benefits already allocated",
599
+ pdfCompBenefitsItem: "Benefit",
600
+ pdfCompBenefitsAmount: "Value {currency}/month",
601
+ pdfCompBenefitsNone: "No additional compensative benefits declared.",
602
+ pdfPrimaryHomeMortgage: "Assigned primary-home mortgage",
603
+ pdfPrimaryHomeNotDeclared: "Not declared",
604
+ pdfPrimaryHomeAssignedTo: "Assigned to",
605
+ pdfPrimaryHomeMonthlyAmount: "Monthly payment",
606
+ pdfPrimaryHomeSplit: "Mortgage split",
607
+ pdfPrimaryHomeAppliedOnlyColl: "Counted only when the home is assigned to the custodial parent.",
550
608
  pdfExtraordinaryRow: "Extraordinary expenses (monthly share from yearly)",
551
609
  liveTotalIncome: "Total income (income + support + INPS)",
552
610
  livePaidToOther: "Support paid to the other spouse",
@@ -621,10 +679,10 @@ const defaultExpenseItems = [
621
679
  scenarioColFabb: "Children needs",
622
680
  scenarioDeltaLabel: "\u0394 vs A",
623
681
  spiegTitle: "💡 Why this result?",
624
- spiegRedditiLabel: "💰 Income (contribution weight)",
625
- spiegSpeseLabel: "👶 Expenses → children needs",
626
- spiegPermLabel: "📅 Permanence → direct share",
627
- spiegResultLabel: "🎯 How the support is formed",
682
+ spiegRedditiLabel: "Income (contribution weight)",
683
+ spiegSpeseLabel: "Expenses → children needs",
684
+ spiegPermLabel: "Permanence → direct share",
685
+ spiegResultLabel: "How the support is formed",
628
686
  spiegTooltipTrigger: "Calculation details",
629
687
  spiegDetailIncome: "The model starts from each spouse's available net. Contribution weight is spouse net / sum of positive nets.",
630
688
  spiegDetailExpense: "Children needs are estimated as 35% of total entered expenses (regular + any extraordinary annual costs converted to monthly).",
@@ -692,100 +750,145 @@ const defaultExpenseItems = [
692
750
  };
693
751
  let currentLang = "it";
694
752
  let currentCurrency = "EUR";
695
- const CALC_API_BASE_STORAGE_KEY = "keylock_calc_api_base";
696
- const FRONTEND_VARIANT_ENVS = window.KEYLOCK_FRONTEND_VARIANT_ENVS && typeof window.KEYLOCK_FRONTEND_VARIANT_ENVS === "object"
697
- ? window.KEYLOCK_FRONTEND_VARIANT_ENVS
698
- : {};
699
- const CALC_API_ENVS = window.KEYLOCK_CALC_API_ENVS && typeof window.KEYLOCK_CALC_API_ENVS === "object"
700
- ? window.KEYLOCK_CALC_API_ENVS
701
- : {};
753
+ const CALC_API_BASE_STORAGE_KEY = "keylock_calc_api_base";
754
+ const FRONTEND_VARIANT_ENVS = window.KEYLOCK_FRONTEND_VARIANT_ENVS && typeof window.KEYLOCK_FRONTEND_VARIANT_ENVS === "object"
755
+ ? window.KEYLOCK_FRONTEND_VARIANT_ENVS
756
+ : {};
757
+ const CALC_API_ENVS = window.KEYLOCK_CALC_API_ENVS && typeof window.KEYLOCK_CALC_API_ENVS === "object"
758
+ ? window.KEYLOCK_CALC_API_ENVS
759
+ : {};
702
760
 
703
- function normalizeApiBase(rawValue) {
704
- return String(rawValue || "").trim().replace(/\/+$/, "");
705
- }
761
+ function normalizeApiBase(rawValue) {
762
+ return String(rawValue || "").trim().replace(/\/+$/, "");
763
+ }
706
764
 
707
- function normalizeFrontendVariantUrl(rawValue) {
708
- return String(rawValue || "").trim();
709
- }
765
+ function normalizeFrontendVariantUrl(rawValue) {
766
+ return String(rawValue || "").trim();
767
+ }
710
768
 
711
- function maybeRedirectFrontendVariant() {
712
- try {
713
- const params = new URLSearchParams(window.location.search || "");
714
- const variant = String(params.get("frontend") || "").trim().toLowerCase();
715
- if (!variant || variant === "prod" || variant === "default" || variant === "reset") return;
769
+ function maybeRedirectFrontendVariant() {
770
+ try {
771
+ const params = new URLSearchParams(window.location.search || "");
772
+ const variant = String(params.get("frontend") || "").trim().toLowerCase();
773
+ if (!variant || variant === "prod" || variant === "default" || variant === "reset") return;
716
774
 
717
- const targetBase = normalizeFrontendVariantUrl(FRONTEND_VARIANT_ENVS[variant] || "");
718
- if (!targetBase) return;
775
+ const targetBase = normalizeFrontendVariantUrl(FRONTEND_VARIANT_ENVS[variant] || "");
776
+ if (!targetBase) return;
719
777
 
720
- const target = new URL(targetBase, window.location.href);
721
- const current = new URL(window.location.href);
778
+ const target = new URL(targetBase, window.location.href);
779
+ const current = new URL(window.location.href);
722
780
 
723
- // Avoid redirect loops when already on the target frontend.
724
- if (target.href === current.href) return;
725
- if (target.origin === current.origin && target.pathname === current.pathname) return;
781
+ if (target.href === current.href) return;
782
+ if (target.origin === current.origin && target.pathname === current.pathname) return;
726
783
 
727
- // Keep runtime API selection params when switching frontend variant.
728
- ["env", "apiBase"].forEach((k) => {
729
- if (params.has(k)) target.searchParams.set(k, String(params.get(k)));
730
- });
784
+ ["env", "apiBase"].forEach((k) => {
785
+ if (params.has(k)) target.searchParams.set(k, String(params.get(k)));
786
+ });
731
787
 
732
- window.location.replace(target.toString());
733
- } catch (_) {
734
- // Ignore malformed URLs and continue with default frontend.
788
+ window.location.replace(target.toString());
789
+ } catch (_) {
790
+ // Ignore malformed URLs and continue with default frontend.
791
+ }
735
792
  }
736
- }
737
793
 
738
- maybeRedirectFrontendVariant();
794
+ maybeRedirectFrontendVariant();
739
795
 
740
- function resolveNamedApiBase(envName) {
741
- const key = String(envName || "").trim().toLowerCase();
742
- if (!key) return "";
743
- return normalizeApiBase(CALC_API_ENVS[key] || "");
744
- }
796
+ function resolveNamedApiBase(envName) {
797
+ const key = String(envName || "").trim().toLowerCase();
798
+ if (!key) return "";
799
+ return normalizeApiBase(CALC_API_ENVS[key] || "");
800
+ }
745
801
 
746
- function resolveCalculationApiBase() {
747
- const configBase = normalizeApiBase(window.KEYLOCK_CALC_API_BASE || "");
748
- let storedBase = "";
802
+ function resolveCalculationApiBase() {
803
+ const configBase = normalizeApiBase(window.KEYLOCK_CALC_API_BASE || "");
804
+ let storedBase = "";
749
805
 
750
- try {
751
- storedBase = normalizeApiBase(localStorage.getItem(CALC_API_BASE_STORAGE_KEY) || "");
752
- } catch (_) {
753
- storedBase = "";
806
+ try {
807
+ storedBase = normalizeApiBase(localStorage.getItem(CALC_API_BASE_STORAGE_KEY) || "");
808
+ } catch (_) {
809
+ storedBase = "";
810
+ }
811
+
812
+ try {
813
+ const params = new URLSearchParams(window.location.search || "");
814
+ if (params.has("env")) {
815
+ const envName = String(params.get("env") || "").trim().toLowerCase();
816
+ const disable = envName === "off" || envName === "default" || envName === "reset" || envName === "prod";
817
+ const envBase = disable ? "" : resolveNamedApiBase(envName);
818
+ try {
819
+ if (disable || !envBase) {
820
+ localStorage.removeItem(CALC_API_BASE_STORAGE_KEY);
821
+ } else {
822
+ localStorage.setItem(CALC_API_BASE_STORAGE_KEY, envBase);
823
+ }
824
+ } catch (_) {}
825
+ return envBase;
826
+ }
827
+
828
+ if (params.has("apiBase")) {
829
+ const queryBaseRaw = String(params.get("apiBase") || "").trim();
830
+ const disable = queryBaseRaw === "off" || queryBaseRaw === "default" || queryBaseRaw === "reset";
831
+ const queryBase = disable ? "" : normalizeApiBase(queryBaseRaw);
832
+ try {
833
+ if (disable || !queryBase) {
834
+ localStorage.removeItem(CALC_API_BASE_STORAGE_KEY);
835
+ } else {
836
+ localStorage.setItem(CALC_API_BASE_STORAGE_KEY, queryBase);
837
+ }
838
+ } catch (_) {}
839
+ return queryBase;
840
+ }
841
+ } catch (_) {}
842
+
843
+ return storedBase || configBase;
754
844
  }
755
845
 
756
- try {
757
- const params = new URLSearchParams(window.location.search || "");
758
- if (params.has("env")) {
759
- const envName = String(params.get("env") || "").trim().toLowerCase();
760
- const disable = envName === "off" || envName === "default" || envName === "reset" || envName === "prod";
761
- const envBase = disable ? "" : resolveNamedApiBase(envName);
762
- try {
763
- if (disable || !envBase) {
764
- localStorage.removeItem(CALC_API_BASE_STORAGE_KEY);
765
- } else {
766
- localStorage.setItem(CALC_API_BASE_STORAGE_KEY, envBase);
767
- }
768
- } catch (_) {}
769
- return envBase;
846
+ function getRuntimeVariantLabels() {
847
+ const labels = [];
848
+
849
+ try {
850
+ const params = new URLSearchParams(window.location.search || "");
851
+ const frontendVariant = String(params.get("frontend") || "").trim().toLowerCase();
852
+ const envVariant = String(params.get("env") || "").trim().toLowerCase();
853
+ const apiBaseVariant = String(params.get("apiBase") || "").trim();
854
+
855
+ if (frontendVariant === "dev" || window.location.host.includes("githack.com")) {
856
+ labels.push("dev-frontend");
857
+ }
858
+ if (envVariant === "dev" || !!apiBaseVariant) {
859
+ labels.push("dev-api");
860
+ }
861
+ } catch (_) {}
862
+
863
+ return labels;
864
+ }
865
+
866
+ function renderRuntimeBadge() {
867
+ const heroTools = document.querySelector(".hero-tools");
868
+ if (!heroTools) return;
869
+
870
+ const labels = getRuntimeVariantLabels();
871
+ let badge = document.getElementById("runtimeEnvBadge");
872
+
873
+ if (!labels.length) {
874
+ if (badge) badge.remove();
875
+ return;
770
876
  }
771
877
 
772
- if (params.has("apiBase")) {
773
- const queryBaseRaw = String(params.get("apiBase") || "").trim();
774
- const disable = queryBaseRaw === "off" || queryBaseRaw === "default" || queryBaseRaw === "reset";
775
- const queryBase = disable ? "" : normalizeApiBase(queryBaseRaw);
776
- try {
777
- if (disable || !queryBase) {
778
- localStorage.removeItem(CALC_API_BASE_STORAGE_KEY);
779
- } else {
780
- localStorage.setItem(CALC_API_BASE_STORAGE_KEY, queryBase);
781
- }
782
- } catch (_) {}
783
- return queryBase;
878
+ if (!badge) {
879
+ badge = document.createElement("div");
880
+ badge.id = "runtimeEnvBadge";
881
+ badge.className = "runtime-badge";
882
+ heroTools.prepend(badge);
784
883
  }
785
- } catch (_) {}
786
884
 
787
- return storedBase || configBase;
788
- }
885
+ badge.innerHTML = labels.map((label) => {
886
+ if (label === "dev-frontend") {
887
+ return '<span class="runtime-badge-chip runtime-badge-chip--frontend">DEV FRONTEND</span>';
888
+ }
889
+ return '<span class="runtime-badge-chip runtime-badge-chip--api">DEV API</span>';
890
+ }).join("");
891
+ }
789
892
 
790
893
  function getRuntimeVariantLabels() {
791
894
  const labels = [];
@@ -858,8 +961,8 @@ const defaultExpenseItems = [
858
961
  }
859
962
 
860
963
  function resolveCalculationApiUrl() {
861
- const calcApiBase = resolveCalculationApiBase();
862
- if (calcApiBase) return `${calcApiBase}/api/calculate`;
964
+ const calcApiBase = resolveCalculationApiBase();
965
+ if (calcApiBase) return `${calcApiBase}/api/calculate`;
863
966
  return "/api/calculate";
864
967
  }
865
968
 
@@ -1020,6 +1123,16 @@ const defaultExpenseItems = [
1020
1123
  const permLegendC2 = document.getElementById("permLegendC2");
1021
1124
  const extraBoxTitle = document.getElementById("extraBoxTitle");
1022
1125
  const extraBoxNote = document.getElementById("extraBoxNote");
1126
+ const firstHomeBoxTitle = document.getElementById("firstHomeBoxTitle");
1127
+ const firstHomeBoxNote = document.getElementById("firstHomeBoxNote");
1128
+ const lblPrimaCasaMutuoEnabled = document.getElementById("lblPrimaCasaMutuoEnabled");
1129
+ const hintPrimaCasaMutuoEnabled = document.getElementById("hintPrimaCasaMutuoEnabled");
1130
+ const lblPrimaCasaMutuoImporto = document.getElementById("lblPrimaCasaMutuoImporto");
1131
+ const hintPrimaCasaMutuoImporto = document.getElementById("hintPrimaCasaMutuoImporto");
1132
+ const lblPrimaCasaAssegnataA = document.getElementById("lblPrimaCasaAssegnataA");
1133
+ const hintPrimaCasaAssegnataA = document.getElementById("hintPrimaCasaAssegnataA");
1134
+ const lblPrimaCasaMutuoPerc1 = document.getElementById("lblPrimaCasaMutuoPerc1");
1135
+ const hintPrimaCasaMutuoPerc1 = document.getElementById("hintPrimaCasaMutuoPerc1");
1023
1136
  const lblStraordAnn1 = document.getElementById("lblStraordAnn1");
1024
1137
  const lblStraordAnn2 = document.getElementById("lblStraordAnn2");
1025
1138
  const hintStraordAnn1 = document.getElementById("hintStraordAnn1");
@@ -1059,6 +1172,16 @@ const defaultExpenseItems = [
1059
1172
  if (permLegendC2) permLegendC2.textContent = c2n();
1060
1173
  if (extraBoxTitle) extraBoxTitle.textContent = tr("extraBoxTitle");
1061
1174
  if (extraBoxNote) extraBoxNote.textContent = tr("extraBoxNote");
1175
+ if (firstHomeBoxTitle) firstHomeBoxTitle.textContent = tr("firstHomeBoxTitle");
1176
+ if (firstHomeBoxNote) firstHomeBoxNote.textContent = tr("firstHomeBoxNote");
1177
+ if (lblPrimaCasaMutuoEnabled) lblPrimaCasaMutuoEnabled.textContent = tr("firstHomeMortgageEnabledLabel");
1178
+ if (hintPrimaCasaMutuoEnabled) hintPrimaCasaMutuoEnabled.title = tr("firstHomeMortgageEnabledHint");
1179
+ if (lblPrimaCasaMutuoImporto) lblPrimaCasaMutuoImporto.textContent = msg("firstHomeMortgageAmountLabel", { currency: currentCurrency });
1180
+ if (hintPrimaCasaMutuoImporto) hintPrimaCasaMutuoImporto.title = tr("firstHomeMortgageAmountHint");
1181
+ if (lblPrimaCasaAssegnataA) lblPrimaCasaAssegnataA.textContent = tr("firstHomeAssignedToLabel");
1182
+ if (hintPrimaCasaAssegnataA) hintPrimaCasaAssegnataA.title = tr("firstHomeAssignedToHint");
1183
+ if (lblPrimaCasaMutuoPerc1) lblPrimaCasaMutuoPerc1.textContent = msg("firstHomeSplitLabel", { spouse: c1n() });
1184
+ if (hintPrimaCasaMutuoPerc1) hintPrimaCasaMutuoPerc1.title = msg("firstHomeSplitHint", { spouse: c1n() });
1062
1185
  if (lblStraordAnn1) lblStraordAnn1.textContent = msg("extraAnnLabel1", { spouse: c1n(), currency: currentCurrency });
1063
1186
  if (lblStraordAnn2) lblStraordAnn2.textContent = msg("extraAnnLabel2", { spouse: c2n(), currency: currentCurrency });
1064
1187
  if (hintStraordAnn1) hintStraordAnn1.title = msg("extraAnnHint1", { spouse: c1n() });
@@ -1108,6 +1231,10 @@ const defaultExpenseItems = [
1108
1231
  if (visitorTotalLabel) visitorTotalLabel.textContent = tr("footerVisitorsTotal");
1109
1232
  if (visitorActiveLabel) visitorActiveLabel.textContent = tr("footerVisitorsActive");
1110
1233
  if (visitorLoggedLabel) visitorLoggedLabel.textContent = tr("footerLoggedUsers");
1234
+ rowsSpese.querySelectorAll("textarea.spese-detail-text").forEach((el) => {
1235
+ updateExpenseDetailCounter(el);
1236
+ });
1237
+ updateFirstHomeMortgageUi();
1111
1238
  updateExtraordinaryModuleUi();
1112
1239
  updatePermanenceCalendarSummary();
1113
1240
  renderVisitorCounters();
@@ -1182,6 +1309,73 @@ const defaultExpenseItems = [
1182
1309
  .replaceAll("'", "&#39;");
1183
1310
  }
1184
1311
 
1312
+ function getExpenseDetailMaxHeight(textarea) {
1313
+ if (!textarea) return 0;
1314
+ const style = window.getComputedStyle(textarea);
1315
+ const lineHeight = Number.parseFloat(style.lineHeight) || 18;
1316
+ const borderTop = Number.parseFloat(style.borderTopWidth) || 0;
1317
+ const borderBottom = Number.parseFloat(style.borderBottomWidth) || 0;
1318
+ const paddingTop = Number.parseFloat(style.paddingTop) || 0;
1319
+ const paddingBottom = Number.parseFloat(style.paddingBottom) || 0;
1320
+ return Math.round((lineHeight * EXPENSE_DETAIL_MAX_LINES) + paddingTop + paddingBottom + borderTop + borderBottom);
1321
+ }
1322
+
1323
+ function autoResizeExpenseDetailTextarea(textarea, preferredHeight = 0) {
1324
+ if (!textarea) return;
1325
+ const maxHeight = getExpenseDetailMaxHeight(textarea);
1326
+ textarea.style.height = "auto";
1327
+ const scrollHeight = Math.max(textarea.scrollHeight, 0);
1328
+ const targetHeight = preferredHeight > 0
1329
+ ? Math.min(maxHeight, Math.max(scrollHeight, preferredHeight))
1330
+ : Math.min(maxHeight, scrollHeight);
1331
+ textarea.style.height = `${Math.max(0, targetHeight)}px`;
1332
+ textarea.style.overflowY = scrollHeight > maxHeight ? "auto" : "hidden";
1333
+ }
1334
+
1335
+ function updateExpenseDetailCounter(textarea) {
1336
+ if (!textarea || !textarea.id) return;
1337
+ const counter = document.getElementById(`${textarea.id}Counter`);
1338
+ if (!counter) return;
1339
+ const maxLen = Number(textarea.getAttribute("maxlength") || EXPENSE_DETAIL_MAX_CHARS);
1340
+ const len = String(textarea.value || "").length;
1341
+ const remaining = Math.max(0, maxLen - len);
1342
+ counter.textContent = msg("expenseDetailCharsRemaining", { count: String(remaining) });
1343
+ counter.classList.toggle("is-limit", remaining <= Math.max(20, Math.round(maxLen * 0.05)));
1344
+ }
1345
+
1346
+ function updateExpenseDetailTextareaUi(textarea, preferredHeight = 0) {
1347
+ if (!textarea) return;
1348
+ autoResizeExpenseDetailTextarea(textarea, preferredHeight);
1349
+ updateExpenseDetailCounter(textarea);
1350
+ }
1351
+
1352
+ function collectExpenseDetailUiMeta(spouseKey, idx) {
1353
+ const wrap = document.getElementById(`${spouseKey}dw_${idx}`);
1354
+ const textArea = document.getElementById(`${spouseKey}d_${idx}`);
1355
+ const inlineHeight = textArea ? Number.parseFloat(String(textArea.style.height || "0")) : 0;
1356
+ return {
1357
+ open: wrap ? !wrap.classList.contains("is-hidden") : false,
1358
+ height: Number.isFinite(inlineHeight) && inlineHeight > 0 ? inlineHeight : 0
1359
+ };
1360
+ }
1361
+
1362
+ function applyExpenseDetailUiMeta(spouseKey, idx, meta) {
1363
+ const wrap = document.getElementById(`${spouseKey}dw_${idx}`);
1364
+ const textarea = document.getElementById(`${spouseKey}d_${idx}`);
1365
+ const btn = rowsSpese.querySelector(`button[data-detail-target='${spouseKey}d_${idx}']`);
1366
+ const open = !!(meta && meta.open);
1367
+
1368
+ if (wrap) wrap.classList.toggle("is-hidden", !open);
1369
+ if (btn) btn.classList.toggle("is-open", open);
1370
+
1371
+ const preferredHeight = Number(meta && meta.height);
1372
+ if (textarea && Number.isFinite(preferredHeight) && preferredHeight > 0) {
1373
+ updateExpenseDetailTextareaUi(textarea, preferredHeight);
1374
+ } else if (textarea) {
1375
+ updateExpenseDetailTextareaUi(textarea);
1376
+ }
1377
+ }
1378
+
1185
1379
  function normalizeExpenseItem(item, fallbackIdx = 0) {
1186
1380
  const rawLabel = String(item && item.label ? item.label : "").trim();
1187
1381
  const rawHelp = String(item && item.help ? item.help : "").trim();
@@ -2357,7 +2551,8 @@ const defaultExpenseItems = [
2357
2551
  <button class="btn-secondary spese-detail-btn" type="button" data-detail-target="c1d_${idx}" data-detail-wrap="c1dw_${idx}" title="${tr("expenseDetailTitle")}"><span class="spese-detail-label">${tr("expenseDetailBtn")}</span></button>
2358
2552
  </div>
2359
2553
  <div class="spese-detail-wrap is-hidden" id="c1dw_${idx}">
2360
- <textarea id="c1d_${idx}" class="spese-detail-text" rows="2" maxlength="280" placeholder="${escapeHtml(tr("expenseDetailPlaceholder"))}"></textarea>
2554
+ <textarea id="c1d_${idx}" class="spese-detail-text" rows="2" maxlength="${EXPENSE_DETAIL_MAX_CHARS}" placeholder="${escapeHtml(tr("expenseDetailPlaceholder"))}" aria-describedby="c1d_${idx}Counter"></textarea>
2555
+ <div class="spese-detail-counter" id="c1d_${idx}Counter" aria-live="polite"></div>
2361
2556
  </div>
2362
2557
  <span class="spese-partial" id="p1_${idx}" title="${tr("expensePartialTitle")}">${tr("expensePartialLabel")}: ${eurTiny(0)}</span>
2363
2558
  </div>
@@ -2369,7 +2564,8 @@ const defaultExpenseItems = [
2369
2564
  <button class="btn-secondary spese-detail-btn" type="button" data-detail-target="c2d_${idx}" data-detail-wrap="c2dw_${idx}" title="${tr("expenseDetailTitle")}"><span class="spese-detail-label">${tr("expenseDetailBtn")}</span></button>
2370
2565
  </div>
2371
2566
  <div class="spese-detail-wrap is-hidden" id="c2dw_${idx}">
2372
- <textarea id="c2d_${idx}" class="spese-detail-text" rows="2" maxlength="280" placeholder="${escapeHtml(tr("expenseDetailPlaceholder"))}"></textarea>
2567
+ <textarea id="c2d_${idx}" class="spese-detail-text" rows="2" maxlength="${EXPENSE_DETAIL_MAX_CHARS}" placeholder="${escapeHtml(tr("expenseDetailPlaceholder"))}" aria-describedby="c2d_${idx}Counter"></textarea>
2568
+ <div class="spese-detail-counter" id="c2d_${idx}Counter" aria-live="polite"></div>
2373
2569
  </div>
2374
2570
  <span class="spese-partial" id="p2_${idx}" title="${tr("expensePartialTitle")}">${tr("expensePartialLabel")}: ${eurTiny(0)}</span>
2375
2571
  </div>
@@ -2380,6 +2576,9 @@ const defaultExpenseItems = [
2380
2576
  `;
2381
2577
  rowsSpese.appendChild(rowEl);
2382
2578
  });
2579
+ rowsSpese.querySelectorAll("textarea.spese-detail-text").forEach((el) => {
2580
+ updateExpenseDetailTextareaUi(el);
2581
+ });
2383
2582
  refreshExpenseDetailButtonState();
2384
2583
  }
2385
2584
 
@@ -2388,7 +2587,9 @@ const defaultExpenseItems = [
2388
2587
  c1: num(`c1_${i}`),
2389
2588
  c2: num(`c2_${i}`),
2390
2589
  d1: String(document.getElementById(`c1d_${i}`)?.value || "").trim(),
2391
- d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim()
2590
+ d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim(),
2591
+ d1Ui: collectExpenseDetailUiMeta("c1", i),
2592
+ d2Ui: collectExpenseDetailUiMeta("c2", i)
2392
2593
  }));
2393
2594
  }
2394
2595
 
@@ -2403,6 +2604,8 @@ const defaultExpenseItems = [
2403
2604
  if (c2) c2.value = Number.isFinite(Number(row && row.c2)) ? Number(row.c2) : 0;
2404
2605
  if (d1) d1.value = String(row && row.d1 ? row.d1 : "");
2405
2606
  if (d2) d2.value = String(row && row.d2 ? row.d2 : "");
2607
+ applyExpenseDetailUiMeta("c1", i, row && row.d1Ui ? row.d1Ui : null);
2608
+ applyExpenseDetailUiMeta("c2", i, row && row.d2Ui ? row.d2Ui : null);
2406
2609
  });
2407
2610
  refreshExpenseDetailButtonState();
2408
2611
  }
@@ -2414,6 +2617,7 @@ const defaultExpenseItems = [
2414
2617
  const detailEl = document.getElementById(targetId);
2415
2618
  const hasNote = !!(detailEl && String(detailEl.value || "").trim());
2416
2619
  btn.classList.toggle("has-note", hasNote);
2620
+ btn.setAttribute("aria-label", hasNote ? `${tr("expenseDetailBtn")} ✓` : tr("expenseDetailBtn"));
2417
2621
  });
2418
2622
  }
2419
2623
 
@@ -2967,8 +3171,11 @@ const defaultExpenseItems = [
2967
3171
  const c2Spese = expenseItems.map((_, idx) => num(`c2_${idx}`));
2968
3172
  const c1SpeseDetails = expenseItems.map((_, idx) => String(document.getElementById(`c1d_${idx}`)?.value || "").trim());
2969
3173
  const c2SpeseDetails = expenseItems.map((_, idx) => String(document.getElementById(`c2d_${idx}`)?.value || "").trim());
3174
+ const c1SpeseDetailUi = expenseItems.map((_, idx) => collectExpenseDetailUiMeta("c1", idx));
3175
+ const c2SpeseDetailUi = expenseItems.map((_, idx) => collectExpenseDetailUiMeta("c2", idx));
2970
3176
  const extra1 = getExtraordinaryMonthly(1);
2971
3177
  const extra2 = getExtraordinaryMonthly(2);
3178
+ const firstHome = getFirstHomeMortgageInput();
2972
3179
  if (extra1 > 0) c1Spese.push(extra1);
2973
3180
  if (extra2 > 0) c2Spese.push(extra2);
2974
3181
 
@@ -2989,10 +3196,16 @@ const defaultExpenseItems = [
2989
3196
  aPag2: num("assegnoPagato2"),
2990
3197
  aFam1: num("assegnoFam1"),
2991
3198
  aFam2: num("assegnoFam2"),
3199
+ primaCasaMutuoEnabled: firstHome.enabled ? 1 : 0,
3200
+ primaCasaMutuoImporto: firstHome.amount,
3201
+ primaCasaAssegnataA: firstHome.assignedTo,
3202
+ primaCasaMutuoPerc1: firstHome.share1,
2992
3203
  straordAnn1: num("straordAnn1"),
2993
3204
  straordAnn2: num("straordAnn2"),
2994
3205
  c1SpeseDetails,
2995
3206
  c2SpeseDetails,
3207
+ c1SpeseDetailUi,
3208
+ c2SpeseDetailUi,
2996
3209
  c1Spese,
2997
3210
  c2Spese
2998
3211
  };
@@ -3023,6 +3236,14 @@ const defaultExpenseItems = [
3023
3236
  const aPag2 = Number(payload.aPag2 || 0);
3024
3237
  const aFam1 = Number(payload.aFam1 || 0);
3025
3238
  const aFam2 = Number(payload.aFam2 || 0);
3239
+ const primaCasaMutuoEnabled = Number(payload.primaCasaMutuoEnabled || 0) > 0;
3240
+ const primaCasaMutuoImporto = Math.max(0, Number(payload.primaCasaMutuoImporto || 0));
3241
+ const primaCasaAssegnataA = (String(payload.primaCasaAssegnataA || "") === "1" || String(payload.primaCasaAssegnataA || "") === "2")
3242
+ ? String(payload.primaCasaAssegnataA)
3243
+ : "";
3244
+ const rawMutuoPerc1 = payload.primaCasaMutuoPerc1 === undefined ? 50 : payload.primaCasaMutuoPerc1;
3245
+ const primaCasaMutuoPerc1 = Math.min(100, Math.max(0, Number(rawMutuoPerc1 || 0)));
3246
+ const primaCasaMutuoPerc2 = 100 - primaCasaMutuoPerc1;
3026
3247
 
3027
3248
  const match12 = Math.min(aPag1, aPerc2);
3028
3249
  const match21 = Math.min(aPag2, aPerc1);
@@ -3071,6 +3292,33 @@ const defaultExpenseItems = [
3071
3292
  assegnoDa2a1 = nonCollocatario === 2 ? contributoIndiretto : 0;
3072
3293
  }
3073
3294
 
3295
+ const assegnoBaseDa1a2 = assegnoDa1a2;
3296
+ const assegnoBaseDa2a1 = assegnoDa2a1;
3297
+
3298
+ const primaCasaConsidered = primaCasaMutuoEnabled && primaCasaMutuoImporto > 0
3299
+ && primaCasaAssegnataA !== ""
3300
+ && Number(primaCasaAssegnataA) === collocatario;
3301
+ let primaCasaTransfer1to2 = 0;
3302
+ let primaCasaTransfer2to1 = 0;
3303
+ if (primaCasaConsidered) {
3304
+ const quotaMutuo1 = primaCasaMutuoImporto * (primaCasaMutuoPerc1 / 100);
3305
+ const quotaMutuo2 = primaCasaMutuoImporto - quotaMutuo1;
3306
+ if (primaCasaAssegnataA === "1") {
3307
+ primaCasaTransfer2to1 = Math.max(0, quotaMutuo2);
3308
+ } else if (primaCasaAssegnataA === "2") {
3309
+ primaCasaTransfer1to2 = Math.max(0, quotaMutuo1);
3310
+ }
3311
+ }
3312
+
3313
+ assegnoDa1a2 = Math.max(0, assegnoDa1a2 - primaCasaTransfer1to2);
3314
+ assegnoDa2a1 = Math.max(0, assegnoDa2a1 - primaCasaTransfer2to1);
3315
+
3316
+ const compensativeBenefits = [];
3317
+ if (aFam1 > 0.005) compensativeBenefits.push({ type: "family", to: 1, amount: aFam1 });
3318
+ if (aFam2 > 0.005) compensativeBenefits.push({ type: "family", to: 2, amount: aFam2 });
3319
+ if (primaCasaTransfer1to2 > 0.005) compensativeBenefits.push({ type: "primary-home-mortgage", from: 1, to: 2, amount: primaCasaTransfer1to2 });
3320
+ if (primaCasaTransfer2to1 > 0.005) compensativeBenefits.push({ type: "primary-home-mortgage", from: 2, to: 1, amount: primaCasaTransfer2to1 });
3321
+
3074
3322
  const post1 = disp1 - assegnoDa1a2 + assegnoDa2a1;
3075
3323
  const post2 = disp2 - assegnoDa2a1 + assegnoDa1a2;
3076
3324
 
@@ -3085,6 +3333,11 @@ const defaultExpenseItems = [
3085
3333
  fabbisognoFigli, quotaTeorica1, quotaTeorica2,
3086
3334
  quotaDiretta1, quotaDiretta2,
3087
3335
  saldo1, saldo2,
3336
+ assegnoBaseDa1a2, assegnoBaseDa2a1,
3337
+ primaCasaMutuoEnabled, primaCasaMutuoImporto, primaCasaAssegnataA,
3338
+ primaCasaMutuoPerc1, primaCasaMutuoPerc2,
3339
+ primaCasaConsidered, primaCasaTransfer1to2, primaCasaTransfer2to1,
3340
+ compensativeBenefits,
3088
3341
  assegnoDa1a2, assegnoDa2a1,
3089
3342
  post1, post2
3090
3343
  };
@@ -3167,6 +3420,7 @@ const defaultExpenseItems = [
3167
3420
  if (lblStraordAnn2) lblStraordAnn2.textContent = msg("extraAnnLabel2", { spouse: c2n(), currency: currentCurrency });
3168
3421
  if (hintStraordAnn1) hintStraordAnn1.title = msg("extraAnnHint1", { spouse: c1n() });
3169
3422
  if (hintStraordAnn2) hintStraordAnn2.title = msg("extraAnnHint2", { spouse: c2n() });
3423
+ updateFirstHomeMortgageUi();
3170
3424
  updateExtraordinaryModuleUi();
3171
3425
  updatePermanenceCalendarSummary();
3172
3426
  }
@@ -3176,6 +3430,56 @@ const defaultExpenseItems = [
3176
3430
  return annual > 0 ? annual / 12 : 0;
3177
3431
  }
3178
3432
 
3433
+ function getFirstHomeMortgageInput() {
3434
+ const enabled = !!document.getElementById("primaCasaMutuoEnabled")?.checked;
3435
+ const amount = Math.max(0, num("primaCasaMutuoImporto"));
3436
+ const assignedToRaw = String(document.getElementById("primaCasaAssegnataA")?.value || "").trim();
3437
+ const assignedTo = (assignedToRaw === "1" || assignedToRaw === "2") ? assignedToRaw : "";
3438
+ const share1 = Math.min(100, Math.max(0, num("primaCasaMutuoPerc1")));
3439
+ const share2 = 100 - share1;
3440
+ return { enabled, amount, assignedTo, share1, share2 };
3441
+ }
3442
+
3443
+ function updateFirstHomeMortgageUi() {
3444
+ const enabledEl = document.getElementById("primaCasaMutuoEnabled");
3445
+ const amountEl = document.getElementById("primaCasaMutuoImporto");
3446
+ const assignedEl = document.getElementById("primaCasaAssegnataA");
3447
+ const shareEl = document.getElementById("primaCasaMutuoPerc1");
3448
+ const splitInfoEl = document.getElementById("primaCasaMutuoSplitInfo");
3449
+ const splitLabelEl = document.getElementById("lblPrimaCasaMutuoPerc1");
3450
+ const splitHintEl = document.getElementById("hintPrimaCasaMutuoPerc1");
3451
+ if (!enabledEl || !amountEl || !assignedEl || !shareEl) return;
3452
+
3453
+ const isEnabled = !!enabledEl.checked;
3454
+ amountEl.disabled = !isEnabled;
3455
+ assignedEl.disabled = !isEnabled;
3456
+ shareEl.disabled = !isEnabled;
3457
+
3458
+ const normalizedShare1 = Math.min(100, Math.max(0, num("primaCasaMutuoPerc1")));
3459
+ if (Math.abs(normalizedShare1 - Number(shareEl.value || 0)) > 0.0001) {
3460
+ shareEl.value = normalizedShare1.toFixed(0);
3461
+ }
3462
+
3463
+ const share2 = 100 - normalizedShare1;
3464
+ if (splitLabelEl) splitLabelEl.textContent = msg("firstHomeSplitLabel", { spouse: c1n() });
3465
+ if (splitHintEl) splitHintEl.title = msg("firstHomeSplitHint", { spouse: c1n() });
3466
+ if (splitInfoEl) {
3467
+ splitInfoEl.textContent = msg("firstHomeSplitInfo", {
3468
+ spouse1: c1n(),
3469
+ spouse2: c2n(),
3470
+ p1: normalizedShare1.toFixed(0),
3471
+ p2: share2.toFixed(0)
3472
+ });
3473
+ }
3474
+
3475
+ const noneOpt = assignedEl.querySelector("option[value='']");
3476
+ const spouse1Opt = assignedEl.querySelector("option[value='1']");
3477
+ const spouse2Opt = assignedEl.querySelector("option[value='2']");
3478
+ if (noneOpt) noneOpt.textContent = tr("firstHomeAssignedToNone");
3479
+ if (spouse1Opt) spouse1Opt.textContent = msg("firstHomeAssignedToSpouse", { spouse: c1n() });
3480
+ if (spouse2Opt) spouse2Opt.textContent = msg("firstHomeAssignedToSpouse", { spouse: c2n() });
3481
+ }
3482
+
3179
3483
  function updateExtraordinaryModuleUi() {
3180
3484
  const month1 = document.getElementById("straordMonth1");
3181
3485
  const month2 = document.getElementById("straordMonth2");
@@ -3635,6 +3939,11 @@ const defaultExpenseItems = [
3635
3939
  if (!el) return;
3636
3940
  el.value = value;
3637
3941
  };
3942
+ const setChecked = (id, value) => {
3943
+ const el = document.getElementById(id);
3944
+ if (!el) return;
3945
+ el.checked = !!value;
3946
+ };
3638
3947
 
3639
3948
  setVal("nome1", payload._nome1 || c1n());
3640
3949
  setVal("nome2", payload._nome2 || c2n());
@@ -3660,6 +3969,10 @@ const defaultExpenseItems = [
3660
3969
  setVal("assegnoPercepito2", Number(payload.aPerc2 || 0));
3661
3970
  setVal("assegnoPagato2", Number(payload.aPag2 || 0));
3662
3971
  setVal("assegnoFam2", Number(payload.aFam2 || 0));
3972
+ setChecked("primaCasaMutuoEnabled", Number(payload.primaCasaMutuoEnabled || 0) > 0);
3973
+ setVal("primaCasaMutuoImporto", Number(payload.primaCasaMutuoImporto || 0));
3974
+ setVal("primaCasaAssegnataA", (String(payload.primaCasaAssegnataA || "") === "1" || String(payload.primaCasaAssegnataA || "") === "2") ? String(payload.primaCasaAssegnataA) : "");
3975
+ setVal("primaCasaMutuoPerc1", Math.min(100, Math.max(0, Number((payload.primaCasaMutuoPerc1 === undefined ? 50 : payload.primaCasaMutuoPerc1) || 0))));
3663
3976
  setVal("straordAnn1", Number(payload.straordAnn1 || 0));
3664
3977
  setVal("straordAnn2", Number(payload.straordAnn2 || 0));
3665
3978
 
@@ -3667,6 +3980,8 @@ const defaultExpenseItems = [
3667
3980
  const c2Spese = Array.isArray(payload.c2Spese) ? payload.c2Spese : [];
3668
3981
  const c1SpeseDetails = Array.isArray(payload.c1SpeseDetails) ? payload.c1SpeseDetails : [];
3669
3982
  const c2SpeseDetails = Array.isArray(payload.c2SpeseDetails) ? payload.c2SpeseDetails : [];
3983
+ const c1SpeseDetailUi = Array.isArray(payload.c1SpeseDetailUi) ? payload.c1SpeseDetailUi : [];
3984
+ const c2SpeseDetailUi = Array.isArray(payload.c2SpeseDetailUi) ? payload.c2SpeseDetailUi : [];
3670
3985
  expenseItems.forEach((_, i) => {
3671
3986
  const c1 = document.getElementById(`c1_${i}`);
3672
3987
  const c2 = document.getElementById(`c2_${i}`);
@@ -3676,6 +3991,8 @@ const defaultExpenseItems = [
3676
3991
  if (c2) c2.value = Number(c2Spese[i] || 0);
3677
3992
  if (d1) d1.value = String(c1SpeseDetails[i] || "");
3678
3993
  if (d2) d2.value = String(c2SpeseDetails[i] || "");
3994
+ applyExpenseDetailUiMeta("c1", i, c1SpeseDetailUi[i]);
3995
+ applyExpenseDetailUiMeta("c2", i, c2SpeseDetailUi[i]);
3679
3996
  });
3680
3997
  refreshExpenseDetailButtonState();
3681
3998
 
@@ -3686,6 +4003,7 @@ const defaultExpenseItems = [
3686
4003
  };
3687
4004
 
3688
4005
  updateSpouseLabels();
4006
+ updateFirstHomeMortgageUi();
3689
4007
  if (payload._permanenceCalendar && typeof payload._permanenceCalendar === "object") {
3690
4008
  importPermanenceCalendarState(payload._permanenceCalendar);
3691
4009
  syncPermanenza("calendar");
@@ -3810,6 +4128,22 @@ const defaultExpenseItems = [
3810
4128
  const peso2Pct = (m.peso2 * 100).toFixed(1);
3811
4129
  const days1 = ((m.perm1 / 100) * 30).toFixed(1);
3812
4130
  const days2 = ((m.perm2 / 100) * 30).toFixed(1);
4131
+ const compBenefits = getCompensativeBenefitRows(m, c1Name, c2Name);
4132
+ const compBenefitsRowsHtml = compBenefits.length
4133
+ ? compBenefits.map((row) => `<tr><td>${escapeHtml(row.label)}</td><td class="num">${eur(row.amount)}</td></tr>`).join("")
4134
+ : `<tr><td colspan="2">${tr("pdfCompBenefitsNone")}</td></tr>`;
4135
+ const primaryHomeAssignedLabel = m.primaCasaAssegnataA === "1"
4136
+ ? c1NameEsc
4137
+ : m.primaCasaAssegnataA === "2"
4138
+ ? c2NameEsc
4139
+ : tr("pdfPrimaryHomeNotDeclared");
4140
+ const primaryHomeSummaryRows = m.primaCasaMutuoEnabled
4141
+ ? `
4142
+ <tr><td>${tr("pdfPrimaryHomeAssignedTo")}</td><td>${primaryHomeAssignedLabel}</td></tr>
4143
+ <tr><td>${tr("pdfPrimaryHomeMonthlyAmount")}</td><td>${eur(m.primaCasaMutuoImporto || 0)}</td></tr>
4144
+ <tr><td>${tr("pdfPrimaryHomeSplit")}</td><td>${c1NameEsc} ${(m.primaCasaMutuoPerc1 || 0).toFixed(0)}% · ${c2NameEsc} ${(m.primaCasaMutuoPerc2 || 0).toFixed(0)}%</td></tr>
4145
+ <tr><td>${tr("pdfPrimaryHomeAppliedOnlyColl")}</td><td>${m.primaCasaConsidered ? "OK" : tr("pdfPrimaryHomeNotDeclared")}</td></tr>`
4146
+ : `<tr><td>${tr("pdfPrimaryHomeMortgage")}</td><td>${tr("pdfPrimaryHomeNotDeclared")}</td></tr>`;
3813
4147
  const isAssegno1 = m.assegnoDa1a2 > 0.005;
3814
4148
  const isAssegno2 = m.assegnoDa2a1 > 0.005;
3815
4149
  const n1 = escapeHtml(c1n());
@@ -3824,18 +4158,24 @@ const defaultExpenseItems = [
3824
4158
  let resultDetail;
3825
4159
  if (isAssegno1) {
3826
4160
  resultHtml = `
3827
- <div class="spieg-line"><strong>${n1} &rarr; ${n2}</strong></div>
3828
- <div class="spieg-line">${n1}: ${eur(m.quotaTeorica1)} &minus; ${eur(m.quotaDiretta1)} = <strong class="ok">${eur(m.assegnoDa1a2)}</strong></div>
4161
+ <div class="spieg-result-flow">${n1} &rarr; ${n2}</div>
4162
+ <div class="spieg-result-formula">${n1}: ${eur(m.quotaTeorica1)} &minus; ${eur(m.quotaDiretta1)}</div>
4163
+ <div class="spieg-result-amount ok">${eur(m.assegnoDa1a2)}</div>
3829
4164
  `;
3830
4165
  resultDetail = tr("spiegDetailResultTransfer");
3831
4166
  } else if (isAssegno2) {
3832
4167
  resultHtml = `
3833
- <div class="spieg-line"><strong>${n2} &rarr; ${n1}</strong></div>
3834
- <div class="spieg-line">${n2}: ${eur(m.quotaTeorica2)} &minus; ${eur(m.quotaDiretta2)} = <strong class="ok">${eur(m.assegnoDa2a1)}</strong></div>
4168
+ <div class="spieg-result-flow">${n2} &rarr; ${n1}</div>
4169
+ <div class="spieg-result-formula">${n2}: ${eur(m.quotaTeorica2)} &minus; ${eur(m.quotaDiretta2)}</div>
4170
+ <div class="spieg-result-amount ok">${eur(m.assegnoDa2a1)}</div>
3835
4171
  `;
3836
4172
  resultDetail = tr("spiegDetailResultTransfer");
3837
4173
  } else {
3838
- resultHtml = `<span class="ok">${tr("calcNoTransferSuggested")}</span>`;
4174
+ const benefitRows = getCompensativeBenefitRows(m, c1n(), c2n());
4175
+ const benefitsHtml = benefitRows.length
4176
+ ? `<div class="spieg-line" style="margin-top:6px"><strong>${tr("calcCompBenefitsLabel")}:</strong> ${benefitRows.map((row) => `${escapeHtml(row.label)} (${eur(row.amount)})`).join(" | ")}</div>`
4177
+ : "";
4178
+ resultHtml = `<div class="spieg-result-empty ok">${tr("calcNoTransferSuggested")}</div>${benefitsHtml}`;
3839
4179
  resultDetail = tr("spiegDetailResultNoTransfer");
3840
4180
  }
3841
4181
 
@@ -3844,27 +4184,43 @@ const defaultExpenseItems = [
3844
4184
  <summary class="spieg-title">${tr("spiegTitle")}</summary>
3845
4185
  <div class="spieg-grid">
3846
4186
  <div class="spieg-item">
3847
- <div class="spieg-item-label">${tr("spiegRedditiLabel")} ${infoTip(tr("spiegDetailIncome"))}</div>
4187
+ <div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">&#128184;</span>${tr("spiegRedditiLabel")} ${infoTip(tr("spiegDetailIncome"))}</div>
3848
4188
  <div class="spieg-item-body">
3849
- <div class="spieg-line"><span>${n1}:</span> <strong class="spieg-value">${eur(m.disp1)}</strong> <span class="spieg-sep">|</span> <span>${n2}:</span> <strong class="spieg-value">${eur(m.disp2)}</strong></div>
3850
- <div class="spieg-line">${tr("pdfWeight")}: <strong class="spieg-value">${n1} ${peso1Pct}%</strong> / <strong class="spieg-value">${n2} ${peso2Pct}%</strong></div>
4189
+ <div class="spieg-people">
4190
+ <div class="spieg-person spieg-person--left">
4191
+ <div class="spieg-person-name">${n1}</div>
4192
+ <div class="spieg-person-value">${eur(m.disp1)}</div>
4193
+ <div class="spieg-person-sub">${tr("pdfWeight")}: ${peso1Pct}%</div>
4194
+ </div>
4195
+ <div class="spieg-person spieg-person--right">
4196
+ <div class="spieg-person-name">${n2}</div>
4197
+ <div class="spieg-person-value">${eur(m.disp2)}</div>
4198
+ <div class="spieg-person-sub">${tr("pdfWeight")}: ${peso2Pct}%</div>
4199
+ </div>
4200
+ </div>
3851
4201
  </div>
3852
4202
  </div>
3853
4203
  <div class="spieg-item">
3854
- <div class="spieg-item-label">${tr("spiegSpeseLabel")} ${infoTip(tr("spiegDetailExpense"))}</div>
4204
+ <div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">&#128221;</span>${tr("spiegSpeseLabel")} ${infoTip(tr("spiegDetailExpense"))}</div>
3855
4205
  <div class="spieg-item-body">
3856
- <div class="spieg-line"><strong class="spieg-value">${eur(m.speseTot)}</strong> &times; 35% = <strong class="spieg-value">${eur(m.fabbisognoFigli)}</strong></div>
4206
+ <div class="spieg-equation">
4207
+ <span class="spieg-pill">${eur(m.speseTot)}</span>
4208
+ <span class="spieg-op">&times;</span>
4209
+ <span class="spieg-pill">35%</span>
4210
+ <span class="spieg-op">=</span>
4211
+ <span class="spieg-pill spieg-pill--result">${eur(m.fabbisognoFigli)}</span>
4212
+ </div>
3857
4213
  </div>
3858
4214
  </div>
3859
4215
  <div class="spieg-item">
3860
- <div class="spieg-item-label">${tr("spiegPermLabel")} ${infoTip(tr("spiegDetailPerm"))}</div>
4216
+ <div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">&#128197;</span>${tr("spiegPermLabel")} ${infoTip(tr("spiegDetailPerm"))}</div>
3861
4217
  <div class="spieg-item-body">
3862
- <div class="spieg-line"><span>${n1}:</span> <strong class="spieg-value">${m.perm1.toFixed(0)}%</strong> (${days1} ${tr("langDaysSuffix")}) &rarr; <strong class="spieg-value">${eur(m.quotaDiretta1)}</strong></div>
3863
- <div class="spieg-line"><span>${n2}:</span> <strong class="spieg-value">${m.perm2.toFixed(0)}%</strong> (${days2} ${tr("langDaysSuffix")}) &rarr; <strong class="spieg-value">${eur(m.quotaDiretta2)}</strong></div>
4218
+ <div class="spieg-line spieg-line--kv"><span class="spieg-k">${n1}</span><span class="spieg-v">${m.perm1.toFixed(0)}% (${days1} ${tr("langDaysSuffix")}) &rarr; ${eur(m.quotaDiretta1)}</span></div>
4219
+ <div class="spieg-line spieg-line--kv"><span class="spieg-k">${n2}</span><span class="spieg-v">${m.perm2.toFixed(0)}% (${days2} ${tr("langDaysSuffix")}) &rarr; ${eur(m.quotaDiretta2)}</span></div>
3864
4220
  </div>
3865
4221
  </div>
3866
4222
  <div class="spieg-item spieg-item--result">
3867
- <div class="spieg-item-label">${tr("spiegResultLabel")} ${infoTip(resultDetail)}</div>
4223
+ <div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">&#127919;</span>${tr("spiegResultLabel")} ${infoTip(resultDetail)}</div>
3868
4224
  <div class="spieg-item-body spieg-item-body--result">${resultHtml}</div>
3869
4225
  </div>
3870
4226
  </div>
@@ -3884,6 +4240,31 @@ const defaultExpenseItems = [
3884
4240
  return tr("calcModeGenovaName");
3885
4241
  }
3886
4242
 
4243
+ function getCompensativeBenefitRows(m, name1 = c1n(), name2 = c2n()) {
4244
+ const rows = Array.isArray(m && m.compensativeBenefits) ? m.compensativeBenefits : [];
4245
+ return rows
4246
+ .filter((row) => row && Number(row.amount || 0) > 0.005)
4247
+ .map((row) => {
4248
+ const amount = Number(row.amount || 0);
4249
+ if (row.type === "family") {
4250
+ const spouse = Number(row.to) === 2 ? name2 : name1;
4251
+ return { label: msg("calcBenefitFamilyAllowance", { spouse }), amount };
4252
+ }
4253
+ if (row.type === "primary-home-mortgage") {
4254
+ const payer = Number(row.from) === 2 ? name2 : name1;
4255
+ const receiver = Number(row.to) === 2 ? name2 : name1;
4256
+ return { label: msg("calcBenefitPrimaryHomeMortgage", { payer, receiver }), amount };
4257
+ }
4258
+ return { label: tr("calcCompBenefitsLabel"), amount };
4259
+ });
4260
+ }
4261
+
4262
+ function formatCompensativeBenefitsInline(m, name1 = c1n(), name2 = c2n()) {
4263
+ return getCompensativeBenefitRows(m, name1, name2)
4264
+ .map((row) => `${row.label}: ${eur(row.amount)}`)
4265
+ .join(" | ");
4266
+ }
4267
+
3887
4268
  function calculate(model = null) {
3888
4269
  const m = model || computeModel();
3889
4270
  const formulaNote = document.getElementById("formulaNote");
@@ -3895,6 +4276,7 @@ const defaultExpenseItems = [
3895
4276
  const normProfileName = escapeHtml(getSelectedNormProfileLabel());
3896
4277
  const negotiationPayerName = m.assegnoDa1a2 > 0.005 ? c1n() : (m.assegnoDa2a1 > 0.005 ? c2n() : c1n());
3897
4278
  const negotiationReceiverName = m.assegnoDa1a2 > 0.005 ? c2n() : (m.assegnoDa2a1 > 0.005 ? c1n() : c2n());
4279
+ const benefitsInline = formatCompensativeBenefitsInline(m, c1n(), c2n());
3898
4280
 
3899
4281
  let modeSpecific = "";
3900
4282
  if (m.mode === "simple") {
@@ -3958,6 +4340,10 @@ const defaultExpenseItems = [
3958
4340
  mainText = `${c1n()} \u2192 ${c2n()}: ${eur(m.assegnoDa1a2)} ${tr("pdfPerMonth")}`;
3959
4341
  } else if (m.assegnoDa2a1 > 0.005) {
3960
4342
  mainText = `${c2n()} \u2192 ${c1n()}: ${eur(m.assegnoDa2a1)} ${tr("pdfPerMonth")}`;
4343
+ } else {
4344
+ if (benefitsInline) {
4345
+ mainText = msg("calcNoTransferWithBenefits", { benefits: benefitsInline });
4346
+ }
3961
4347
  }
3962
4348
  resultMain.textContent = mainText;
3963
4349
 
@@ -3977,6 +4363,10 @@ const defaultExpenseItems = [
3977
4363
  [tr("pdfAmountPerChild"), eur((Math.max(m.assegnoDa1a2, m.assegnoDa2a1)) / m.figli), "warn"]
3978
4364
  ];
3979
4365
 
4366
+ if (benefitsInline) {
4367
+ items.push([tr("calcCompBenefitsLabel"), benefitsInline, "warn"]);
4368
+ }
4369
+
3980
4370
  if (m.incomeMode === "cu") {
3981
4371
  const ratio1 = m.r1Raw > 0 ? ((m.r1 * 12 / m.r1Raw) * 100) : 0;
3982
4372
  const ratio2 = m.r2Raw > 0 ? ((m.r2 * 12 / m.r2Raw) * 100) : 0;
@@ -4089,6 +4479,11 @@ const defaultExpenseItems = [
4089
4479
  <div class="pdf-explain-formula">${c2NameEsc}: ${eur(m.quotaTeorica2)} &minus; ${eur(m.quotaDiretta2)}</div>
4090
4480
  <div class="pdf-explain-amount">${eur(m.assegnoDa2a1)}</div>
4091
4481
  `;
4482
+ } else if (compBenefits.length) {
4483
+ explainResultHtml = `
4484
+ <div class="pdf-explain-result-empty">${tr("calcNoTransferSuggested")}</div>
4485
+ <div class="pdf-explain-formula"><strong>${tr("calcCompBenefitsLabel")}:</strong> ${compBenefits.map((row) => `${escapeHtml(row.label)} (${eur(row.amount)})`).join(" | ")}</div>
4486
+ `;
4092
4487
  }
4093
4488
 
4094
4489
  const extraSpese1Monthly = getExtraordinaryMonthly(1);
@@ -4097,7 +4492,7 @@ const defaultExpenseItems = [
4097
4492
  const el = document.getElementById(`${spouseKey}d_${idx}`);
4098
4493
  const raw = String(el && el.value ? el.value : "").trim();
4099
4494
  if (!raw) return "";
4100
- return raw;
4495
+ return `${escapeHtml(raw)} <span class="expense-detail-meta">(${raw.length}/${EXPENSE_DETAIL_MAX_CHARS})</span>`;
4101
4496
  };
4102
4497
  const speseRowsBase = expenseItems.map((item, i) => {
4103
4498
  const c1 = num(`c1_${i}`);
@@ -4105,8 +4500,8 @@ const defaultExpenseItems = [
4105
4500
  const d1 = formatExpenseDetail(i, "c1");
4106
4501
  const d2 = formatExpenseDetail(i, "c2");
4107
4502
  const details = [
4108
- d1 ? `${escapeHtml(c1n())}: ${escapeHtml(d1)}` : "",
4109
- d2 ? `${escapeHtml(c2n())}: ${escapeHtml(d2)}` : ""
4503
+ d1 ? `${escapeHtml(c1n())}: ${d1}` : "",
4504
+ d2 ? `${escapeHtml(c2n())}: ${d2}` : ""
4110
4505
  ].filter(Boolean).join("<br>");
4111
4506
  return `<tr>
4112
4507
  <td>${item.label}</td>
@@ -4309,6 +4704,7 @@ const defaultExpenseItems = [
4309
4704
  tr:nth-child(even) td { background: #f5faf9; }
4310
4705
  .num { text-align: right; font-variant-numeric: tabular-nums; }
4311
4706
  .bold { font-weight: 700; }
4707
+ .expense-detail-meta { color: #5f7873; font-size: 7pt; font-weight: 600; white-space: nowrap; }
4312
4708
  .total-row td { background: #ddf0ec !important; font-weight: 700; border-top: 1.5px solid #74c3b9; }
4313
4709
 
4314
4710
  /* ── TWO-COL LAYOUT ── */
@@ -4540,6 +4936,7 @@ const defaultExpenseItems = [
4540
4936
  <tr><td>${tr("pdfChildrenCount")}</td><td>${m.figli}</td></tr>
4541
4937
  <tr><td>${tr("pdfPermanence")} ${c1n()}</td><td>${m.perm1.toFixed(0)}%</td></tr>
4542
4938
  <tr><td>${tr("pdfPermanence")} ${c2n()}</td><td>${m.perm2.toFixed(0)}%</td></tr>
4939
+ ${primaryHomeSummaryRows}
4543
4940
  </tbody>
4544
4941
  </table>
4545
4942
  <table class="data-table">
@@ -4616,6 +5013,21 @@ const defaultExpenseItems = [
4616
5013
  </div>
4617
5014
  </div>
4618
5015
 
5016
+ <div class="section">
5017
+ <div class="section-title">${tr("pdfCompBenefitsSection")}</div>
5018
+ <table>
5019
+ <thead>
5020
+ <tr>
5021
+ <th>${tr("pdfCompBenefitsItem")}</th>
5022
+ <th class="num">${msg("pdfCompBenefitsAmount", { currency: currentCurrency })}</th>
5023
+ </tr>
5024
+ </thead>
5025
+ <tbody>
5026
+ ${compBenefitsRowsHtml}
5027
+ </tbody>
5028
+ </table>
5029
+ </div>
5030
+
4619
5031
  <!-- KPI -->
4620
5032
  <div class="section">
4621
5033
  <div class="section-title">${tr("pdfKpiSection")}</div>
@@ -4797,6 +5209,10 @@ ${scenarioLab.length ? `
4797
5209
  assegnoPercepito2: num("assegnoPercepito2"),
4798
5210
  assegnoPagato2: num("assegnoPagato2"),
4799
5211
  assegnoFam2: num("assegnoFam2"),
5212
+ primaCasaMutuoEnabled: document.getElementById("primaCasaMutuoEnabled")?.checked ? 1 : 0,
5213
+ primaCasaMutuoImporto: num("primaCasaMutuoImporto"),
5214
+ primaCasaAssegnataA: String(document.getElementById("primaCasaAssegnataA")?.value || ""),
5215
+ primaCasaMutuoPerc1: num("primaCasaMutuoPerc1"),
4800
5216
  straordAnn1: num("straordAnn1"),
4801
5217
  straordAnn2: num("straordAnn2")
4802
5218
  };
@@ -4804,7 +5220,9 @@ ${scenarioLab.length ? `
4804
5220
  c1: num(`c1_${i}`),
4805
5221
  c2: num(`c2_${i}`),
4806
5222
  d1: String(document.getElementById(`c1d_${i}`)?.value || "").trim(),
4807
- d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim()
5223
+ d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim(),
5224
+ d1Ui: collectExpenseDetailUiMeta("c1", i),
5225
+ d2Ui: collectExpenseDetailUiMeta("c2", i)
4808
5226
  }));
4809
5227
  const expenseItemsState = expenseItems.map((item) => ({ label: item.label, help: item.help }));
4810
5228
  const scenariosState = scenarioLab.map((scenario, idx) => ({
@@ -4835,7 +5253,12 @@ ${scenarioLab.length ? `
4835
5253
  if (!state || !state.base || !state.spese) return;
4836
5254
  Object.entries(state.base).forEach(([k, v]) => {
4837
5255
  const el = document.getElementById(k);
4838
- if (el) el.value = v;
5256
+ if (!el) return;
5257
+ if (el.type === "checkbox") {
5258
+ el.checked = Number(v || 0) > 0;
5259
+ } else {
5260
+ el.value = v;
5261
+ }
4839
5262
  });
4840
5263
  if (Array.isArray(state.expenseItems) && state.expenseItems.length) {
4841
5264
  expenseItems = state.expenseItems.map((item, idx) => normalizeExpenseItem(item, idx));
@@ -4876,6 +5299,7 @@ ${scenarioLab.length ? `
4876
5299
  applyStaticTranslations();
4877
5300
  applyUiViewStateToDom();
4878
5301
  importPermanenceCalendarState(state.permanenceCalendar);
5302
+ updateFirstHomeMortgageUi();
4879
5303
  updateSpouseLabels();
4880
5304
  buildExpenseRows();
4881
5305
  syncPermanenza("calendar");
@@ -4888,6 +5312,8 @@ ${scenarioLab.length ? `
4888
5312
  if (c2) c2.value = row.c2;
4889
5313
  if (d1) d1.value = String(row && row.d1 ? row.d1 : "");
4890
5314
  if (d2) d2.value = String(row && row.d2 ? row.d2 : "");
5315
+ applyExpenseDetailUiMeta("c1", i, row && row.d1Ui ? row.d1Ui : null);
5316
+ applyExpenseDetailUiMeta("c2", i, row && row.d2Ui ? row.d2Ui : null);
4891
5317
  });
4892
5318
  refreshExpenseDetailButtonState();
4893
5319
  }
@@ -4898,6 +5324,10 @@ ${scenarioLab.length ? `
4898
5324
  el.value = el.defaultValue || 0;
4899
5325
  }
4900
5326
  });
5327
+ const firstHomeEnabled = document.getElementById("primaCasaMutuoEnabled");
5328
+ const firstHomeAssigned = document.getElementById("primaCasaAssegnataA");
5329
+ if (firstHomeEnabled) firstHomeEnabled.checked = !!firstHomeEnabled.defaultChecked;
5330
+ if (firstHomeAssigned) firstHomeAssigned.value = "";
4901
5331
  permanenceCalendarState.byMonth = {};
4902
5332
  selectedScenarioIdx = -1;
4903
5333
  uiViewState.spiegOpen = true;
@@ -4911,6 +5341,7 @@ ${scenarioLab.length ? `
4911
5341
  renderPermanenceCalendar(monthValue);
4912
5342
  applyPermanenceFromCalendar(monthValue, { silentRender: true });
4913
5343
  applyUiViewStateToDom();
5344
+ updateFirstHomeMortgageUi();
4914
5345
  syncPermanenza();
4915
5346
  renderAll();
4916
5347
  }
@@ -5026,7 +5457,10 @@ ${scenarioLab.length ? `
5026
5457
  const willOpen = wrap.classList.contains("is-hidden");
5027
5458
  wrap.classList.toggle("is-hidden", !willOpen);
5028
5459
  detailBtn.classList.toggle("is-open", willOpen);
5029
- if (willOpen && target) target.focus();
5460
+ if (target) {
5461
+ updateExpenseDetailTextareaUi(target);
5462
+ if (willOpen) target.focus();
5463
+ }
5030
5464
  }
5031
5465
  return;
5032
5466
  }
@@ -5039,6 +5473,7 @@ ${scenarioLab.length ? `
5039
5473
 
5040
5474
  rowsSpese.addEventListener("input", (e) => {
5041
5475
  if (e.target && e.target.matches("textarea.spese-detail-text")) {
5476
+ updateExpenseDetailTextareaUi(e.target);
5042
5477
  refreshExpenseDetailButtonState();
5043
5478
  }
5044
5479
  });
@@ -5170,6 +5605,9 @@ ${scenarioLab.length ? `
5170
5605
  }
5171
5606
  updateModeUi();
5172
5607
  renderAll();
5608
+ } else if (e.target && (e.target.id === "primaCasaMutuoEnabled" || e.target.id === "primaCasaAssegnataA")) {
5609
+ updateFirstHomeMortgageUi();
5610
+ renderAll();
5173
5611
  }
5174
5612
  });
5175
5613
 
@@ -5213,6 +5651,7 @@ ${scenarioLab.length ? `
5213
5651
  updateAuthUi();
5214
5652
  renderCloudHistoryPanel();
5215
5653
  applyUiViewStateToDom();
5654
+ updateFirstHomeMortgageUi();
5216
5655
  syncPermanenza();
5217
5656
  incomeModeLast = document.getElementById("incomeMode").value || "monthly";
5218
5657
  incomeValuesByMode[incomeModeLast] = {