mantenimento-app 2.1.2 → 2.1.4

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
@@ -1,6 +1,5 @@
1
1
  const defaultExpenseItems = [
2
2
  { label: "🏠 Affitto", help: "Canone mensile di locazione dell'abitazione." },
3
- { label: "🏦 Mutuo casa", help: "Rata mensile del mutuo abitazione." },
4
3
  { label: "🏡 Casa (valore locativo)", help: "Valore locativo teorico della casa in uso, se rilevante." },
5
4
  { label: "💡 Utenze", help: "Luce, gas, acqua, internet e altre utenze domestiche." },
6
5
  { label: "🛒 Cibo/Alimenti", help: "Spesa alimentare mensile imputabile al nucleo familiare." },
@@ -28,6 +27,8 @@ const defaultExpenseItems = [
28
27
  let scenarioTransitionTimer = null;
29
28
  const SCENARIO_LAB_MAX = 3;
30
29
  const SCENARIO_LABELS = ["A", "B", "C"];
30
+ const EXPENSE_DETAIL_MAX_CHARS = 560;
31
+ const EXPENSE_DETAIL_MAX_LINES = 10;
31
32
 
32
33
  const QUOTA_MANTENIMENTO_PERC = 35;
33
34
 
@@ -228,6 +229,7 @@ const defaultExpenseItems = [
228
229
  expenseDetailBtn: "Dettaglio",
229
230
  expenseDetailTitle: "Apri dettaglio voce spesa",
230
231
  expenseDetailPlaceholder: "Scrivi qui il dettaglio di questa cifra (es. mesi, quota, riferimento).",
232
+ expenseDetailCharsRemaining: "Caratteri rimanenti: {count}",
231
233
  expenseRemoveTitle: "Rimuovi voce spesa",
232
234
  expenseRemoveBtn: "Rimuovi",
233
235
  expenseMinOneAlert: "Deve restare almeno una voce spesa.",
@@ -254,6 +256,33 @@ const defaultExpenseItems = [
254
256
  extraAnnHint1: "Quota annuale straordinaria stimata a carico di {spouse} (es. sanitarie non ricorrenti, scolastiche extra, attività non ordinarie).",
255
257
  extraAnnHint2: "Quota annuale straordinaria stimata a carico di {spouse} (es. sanitarie non ricorrenti, scolastiche extra, attività non ordinarie).",
256
258
  extraMonthlyEstimate: "Quota mensile stimata: {amount}",
259
+ firstHomeBoxTitle: "🏡 Mutuo prima casa ceduta",
260
+ 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.",
261
+ firstHomeMortgageEnabledLabel: "Mutuo su prima casa dei coniugi",
262
+ firstHomeMortgageEnabledHint: "Attiva per includere il mutuo della prima casa ceduta nei benefici compensativi.",
263
+ firstHomeMortgageAmountLabel: "Rata mutuo mensile ({currency})",
264
+ firstHomeMortgageAmountHint: "Importo mensile complessivo della rata del mutuo prima casa.",
265
+ firstHomeAssignedToLabel: "Casa assegnata a",
266
+ firstHomeAssignedToHint: "Seleziona il coniuge a cui e ceduta la prima casa.",
267
+ firstHomeAssignedToNone: "Nessuna cessione",
268
+ firstHomeAssignedToSpouse: "Casa ceduta a {spouse}",
269
+ firstHomeSplitLabel: "Quota mutuo a carico {spouse} (%)",
270
+ firstHomeSplitHint: "Percentuale della rata mutuo pagata da {spouse}. La quota dell'altro coniuge e complementare a 100%.",
271
+ firstHomeSplitInfo: "Ripartizione mutuo: {spouse1} {p1}% · {spouse2} {p2}%",
272
+ calcCompBenefitsLabel: "Benefici compensativi gia allocati",
273
+ calcNoTransferWithBenefits: "Nessun trasferimento monetario suggerito. Benefici gia allocati: {benefits}.",
274
+ calcBenefitFamilyAllowance: "Assegno familiare INPS percepito da {spouse}",
275
+ calcBenefitPrimaryHomeMortgage: "Quota mutuo prima casa ceduta al collocatario ({payer} -> {receiver})",
276
+ pdfCompBenefitsSection: "Benefici compensativi gia allocati",
277
+ pdfCompBenefitsItem: "Beneficio",
278
+ pdfCompBenefitsAmount: "Valore {currency}/mese",
279
+ pdfCompBenefitsNone: "Nessun beneficio compensativo aggiuntivo dichiarato.",
280
+ pdfPrimaryHomeMortgage: "Mutuo prima casa ceduta",
281
+ pdfPrimaryHomeNotDeclared: "Non dichiarato",
282
+ pdfPrimaryHomeAssignedTo: "Assegnata a",
283
+ pdfPrimaryHomeMonthlyAmount: "Rata mensile",
284
+ pdfPrimaryHomeSplit: "Ripartizione mutuo",
285
+ pdfPrimaryHomeAppliedOnlyColl: "Considerato solo se casa ceduta al collocatario.",
257
286
  pdfExtraordinaryRow: "Spese straordinarie (quota mensile da annuo)",
258
287
  liveTotalIncome: "Entrate totali (reddito + assegni + INPS)",
259
288
  livePaidToOther: "Assegno mantenimento pagato all'altro coniuge",
@@ -521,6 +550,7 @@ const defaultExpenseItems = [
521
550
  expenseDetailBtn: "Detail",
522
551
  expenseDetailTitle: "Open expense detail",
523
552
  expenseDetailPlaceholder: "Write details for this amount (e.g. months, share, reference).",
553
+ expenseDetailCharsRemaining: "Remaining characters: {count}",
524
554
  expenseRemoveTitle: "Remove expense item",
525
555
  expenseRemoveBtn: "Remove",
526
556
  expenseMinOneAlert: "At least one expense item must remain.",
@@ -547,6 +577,33 @@ const defaultExpenseItems = [
547
577
  extraAnnHint1: "Estimated yearly extraordinary share for {spouse} (e.g., non-recurring medical, extra school, non-ordinary activities).",
548
578
  extraAnnHint2: "Estimated yearly extraordinary share for {spouse} (e.g., non-recurring medical, extra school, non-ordinary activities).",
549
579
  extraMonthlyEstimate: "Estimated monthly share: {amount}",
580
+ firstHomeBoxTitle: "🏡 Assigned primary home mortgage",
581
+ 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.",
582
+ firstHomeMortgageEnabledLabel: "Mortgage on spouses' primary home",
583
+ firstHomeMortgageEnabledHint: "Enable to include the assigned primary-home mortgage in compensative benefits.",
584
+ firstHomeMortgageAmountLabel: "Monthly mortgage payment ({currency})",
585
+ firstHomeMortgageAmountHint: "Total monthly amount of the primary-home mortgage payment.",
586
+ firstHomeAssignedToLabel: "Home assigned to",
587
+ firstHomeAssignedToHint: "Select which spouse receives assignment of the primary home.",
588
+ firstHomeAssignedToNone: "No assignment",
589
+ firstHomeAssignedToSpouse: "Home assigned to {spouse}",
590
+ firstHomeSplitLabel: "Mortgage share paid by {spouse} (%)",
591
+ firstHomeSplitHint: "Percentage of the monthly mortgage payment paid by {spouse}. The other spouse share is the complement to 100%.",
592
+ firstHomeSplitInfo: "Mortgage split: {spouse1} {p1}% · {spouse2} {p2}%",
593
+ calcCompBenefitsLabel: "Compensative benefits already allocated",
594
+ calcNoTransferWithBenefits: "No monetary transfer suggested. Already allocated benefits: {benefits}.",
595
+ calcBenefitFamilyAllowance: "INPS family allowance received by {spouse}",
596
+ calcBenefitPrimaryHomeMortgage: "Primary-home mortgage share assigned to custodial parent ({payer} -> {receiver})",
597
+ pdfCompBenefitsSection: "Compensative benefits already allocated",
598
+ pdfCompBenefitsItem: "Benefit",
599
+ pdfCompBenefitsAmount: "Value {currency}/month",
600
+ pdfCompBenefitsNone: "No additional compensative benefits declared.",
601
+ pdfPrimaryHomeMortgage: "Assigned primary-home mortgage",
602
+ pdfPrimaryHomeNotDeclared: "Not declared",
603
+ pdfPrimaryHomeAssignedTo: "Assigned to",
604
+ pdfPrimaryHomeMonthlyAmount: "Monthly payment",
605
+ pdfPrimaryHomeSplit: "Mortgage split",
606
+ pdfPrimaryHomeAppliedOnlyColl: "Counted only when the home is assigned to the custodial parent.",
550
607
  pdfExtraordinaryRow: "Extraordinary expenses (monthly share from yearly)",
551
608
  liveTotalIncome: "Total income (income + support + INPS)",
552
609
  livePaidToOther: "Support paid to the other spouse",
@@ -692,100 +749,145 @@ const defaultExpenseItems = [
692
749
  };
693
750
  let currentLang = "it";
694
751
  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
- : {};
752
+ const CALC_API_BASE_STORAGE_KEY = "keylock_calc_api_base";
753
+ const FRONTEND_VARIANT_ENVS = window.KEYLOCK_FRONTEND_VARIANT_ENVS && typeof window.KEYLOCK_FRONTEND_VARIANT_ENVS === "object"
754
+ ? window.KEYLOCK_FRONTEND_VARIANT_ENVS
755
+ : {};
756
+ const CALC_API_ENVS = window.KEYLOCK_CALC_API_ENVS && typeof window.KEYLOCK_CALC_API_ENVS === "object"
757
+ ? window.KEYLOCK_CALC_API_ENVS
758
+ : {};
702
759
 
703
- function normalizeApiBase(rawValue) {
704
- return String(rawValue || "").trim().replace(/\/+$/, "");
705
- }
760
+ function normalizeApiBase(rawValue) {
761
+ return String(rawValue || "").trim().replace(/\/+$/, "");
762
+ }
706
763
 
707
- function normalizeFrontendVariantUrl(rawValue) {
708
- return String(rawValue || "").trim();
709
- }
764
+ function normalizeFrontendVariantUrl(rawValue) {
765
+ return String(rawValue || "").trim();
766
+ }
710
767
 
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;
768
+ function maybeRedirectFrontendVariant() {
769
+ try {
770
+ const params = new URLSearchParams(window.location.search || "");
771
+ const variant = String(params.get("frontend") || "").trim().toLowerCase();
772
+ if (!variant || variant === "prod" || variant === "default" || variant === "reset") return;
716
773
 
717
- const targetBase = normalizeFrontendVariantUrl(FRONTEND_VARIANT_ENVS[variant] || "");
718
- if (!targetBase) return;
774
+ const targetBase = normalizeFrontendVariantUrl(FRONTEND_VARIANT_ENVS[variant] || "");
775
+ if (!targetBase) return;
719
776
 
720
- const target = new URL(targetBase, window.location.href);
721
- const current = new URL(window.location.href);
777
+ const target = new URL(targetBase, window.location.href);
778
+ const current = new URL(window.location.href);
722
779
 
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;
780
+ if (target.href === current.href) return;
781
+ if (target.origin === current.origin && target.pathname === current.pathname) return;
726
782
 
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
- });
783
+ ["env", "apiBase"].forEach((k) => {
784
+ if (params.has(k)) target.searchParams.set(k, String(params.get(k)));
785
+ });
731
786
 
732
- window.location.replace(target.toString());
733
- } catch (_) {
734
- // Ignore malformed URLs and continue with default frontend.
787
+ window.location.replace(target.toString());
788
+ } catch (_) {
789
+ // Ignore malformed URLs and continue with default frontend.
790
+ }
735
791
  }
736
- }
737
792
 
738
- maybeRedirectFrontendVariant();
793
+ maybeRedirectFrontendVariant();
739
794
 
740
- function resolveNamedApiBase(envName) {
741
- const key = String(envName || "").trim().toLowerCase();
742
- if (!key) return "";
743
- return normalizeApiBase(CALC_API_ENVS[key] || "");
744
- }
795
+ function resolveNamedApiBase(envName) {
796
+ const key = String(envName || "").trim().toLowerCase();
797
+ if (!key) return "";
798
+ return normalizeApiBase(CALC_API_ENVS[key] || "");
799
+ }
745
800
 
746
- function resolveCalculationApiBase() {
747
- const configBase = normalizeApiBase(window.KEYLOCK_CALC_API_BASE || "");
748
- let storedBase = "";
801
+ function resolveCalculationApiBase() {
802
+ const configBase = normalizeApiBase(window.KEYLOCK_CALC_API_BASE || "");
803
+ let storedBase = "";
749
804
 
750
- try {
751
- storedBase = normalizeApiBase(localStorage.getItem(CALC_API_BASE_STORAGE_KEY) || "");
752
- } catch (_) {
753
- storedBase = "";
805
+ try {
806
+ storedBase = normalizeApiBase(localStorage.getItem(CALC_API_BASE_STORAGE_KEY) || "");
807
+ } catch (_) {
808
+ storedBase = "";
809
+ }
810
+
811
+ try {
812
+ const params = new URLSearchParams(window.location.search || "");
813
+ if (params.has("env")) {
814
+ const envName = String(params.get("env") || "").trim().toLowerCase();
815
+ const disable = envName === "off" || envName === "default" || envName === "reset" || envName === "prod";
816
+ const envBase = disable ? "" : resolveNamedApiBase(envName);
817
+ try {
818
+ if (disable || !envBase) {
819
+ localStorage.removeItem(CALC_API_BASE_STORAGE_KEY);
820
+ } else {
821
+ localStorage.setItem(CALC_API_BASE_STORAGE_KEY, envBase);
822
+ }
823
+ } catch (_) {}
824
+ return envBase;
825
+ }
826
+
827
+ if (params.has("apiBase")) {
828
+ const queryBaseRaw = String(params.get("apiBase") || "").trim();
829
+ const disable = queryBaseRaw === "off" || queryBaseRaw === "default" || queryBaseRaw === "reset";
830
+ const queryBase = disable ? "" : normalizeApiBase(queryBaseRaw);
831
+ try {
832
+ if (disable || !queryBase) {
833
+ localStorage.removeItem(CALC_API_BASE_STORAGE_KEY);
834
+ } else {
835
+ localStorage.setItem(CALC_API_BASE_STORAGE_KEY, queryBase);
836
+ }
837
+ } catch (_) {}
838
+ return queryBase;
839
+ }
840
+ } catch (_) {}
841
+
842
+ return storedBase || configBase;
754
843
  }
755
844
 
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;
845
+ function getRuntimeVariantLabels() {
846
+ const labels = [];
847
+
848
+ try {
849
+ const params = new URLSearchParams(window.location.search || "");
850
+ const frontendVariant = String(params.get("frontend") || "").trim().toLowerCase();
851
+ const envVariant = String(params.get("env") || "").trim().toLowerCase();
852
+ const apiBaseVariant = String(params.get("apiBase") || "").trim();
853
+
854
+ if (frontendVariant === "dev" || window.location.host.includes("githack.com")) {
855
+ labels.push("dev-frontend");
856
+ }
857
+ if (envVariant === "dev" || !!apiBaseVariant) {
858
+ labels.push("dev-api");
859
+ }
860
+ } catch (_) {}
861
+
862
+ return labels;
863
+ }
864
+
865
+ function renderRuntimeBadge() {
866
+ const heroTools = document.querySelector(".hero-tools");
867
+ if (!heroTools) return;
868
+
869
+ const labels = getRuntimeVariantLabels();
870
+ let badge = document.getElementById("runtimeEnvBadge");
871
+
872
+ if (!labels.length) {
873
+ if (badge) badge.remove();
874
+ return;
770
875
  }
771
876
 
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;
877
+ if (!badge) {
878
+ badge = document.createElement("div");
879
+ badge.id = "runtimeEnvBadge";
880
+ badge.className = "runtime-badge";
881
+ heroTools.prepend(badge);
784
882
  }
785
- } catch (_) {}
786
883
 
787
- return storedBase || configBase;
788
- }
884
+ badge.innerHTML = labels.map((label) => {
885
+ if (label === "dev-frontend") {
886
+ return '<span class="runtime-badge-chip runtime-badge-chip--frontend">DEV FRONTEND</span>';
887
+ }
888
+ return '<span class="runtime-badge-chip runtime-badge-chip--api">DEV API</span>';
889
+ }).join("");
890
+ }
789
891
 
790
892
  function getRuntimeVariantLabels() {
791
893
  const labels = [];
@@ -858,8 +960,8 @@ const defaultExpenseItems = [
858
960
  }
859
961
 
860
962
  function resolveCalculationApiUrl() {
861
- const calcApiBase = resolveCalculationApiBase();
862
- if (calcApiBase) return `${calcApiBase}/api/calculate`;
963
+ const calcApiBase = resolveCalculationApiBase();
964
+ if (calcApiBase) return `${calcApiBase}/api/calculate`;
863
965
  return "/api/calculate";
864
966
  }
865
967
 
@@ -1020,6 +1122,16 @@ const defaultExpenseItems = [
1020
1122
  const permLegendC2 = document.getElementById("permLegendC2");
1021
1123
  const extraBoxTitle = document.getElementById("extraBoxTitle");
1022
1124
  const extraBoxNote = document.getElementById("extraBoxNote");
1125
+ const firstHomeBoxTitle = document.getElementById("firstHomeBoxTitle");
1126
+ const firstHomeBoxNote = document.getElementById("firstHomeBoxNote");
1127
+ const lblPrimaCasaMutuoEnabled = document.getElementById("lblPrimaCasaMutuoEnabled");
1128
+ const hintPrimaCasaMutuoEnabled = document.getElementById("hintPrimaCasaMutuoEnabled");
1129
+ const lblPrimaCasaMutuoImporto = document.getElementById("lblPrimaCasaMutuoImporto");
1130
+ const hintPrimaCasaMutuoImporto = document.getElementById("hintPrimaCasaMutuoImporto");
1131
+ const lblPrimaCasaAssegnataA = document.getElementById("lblPrimaCasaAssegnataA");
1132
+ const hintPrimaCasaAssegnataA = document.getElementById("hintPrimaCasaAssegnataA");
1133
+ const lblPrimaCasaMutuoPerc1 = document.getElementById("lblPrimaCasaMutuoPerc1");
1134
+ const hintPrimaCasaMutuoPerc1 = document.getElementById("hintPrimaCasaMutuoPerc1");
1023
1135
  const lblStraordAnn1 = document.getElementById("lblStraordAnn1");
1024
1136
  const lblStraordAnn2 = document.getElementById("lblStraordAnn2");
1025
1137
  const hintStraordAnn1 = document.getElementById("hintStraordAnn1");
@@ -1059,6 +1171,16 @@ const defaultExpenseItems = [
1059
1171
  if (permLegendC2) permLegendC2.textContent = c2n();
1060
1172
  if (extraBoxTitle) extraBoxTitle.textContent = tr("extraBoxTitle");
1061
1173
  if (extraBoxNote) extraBoxNote.textContent = tr("extraBoxNote");
1174
+ if (firstHomeBoxTitle) firstHomeBoxTitle.textContent = tr("firstHomeBoxTitle");
1175
+ if (firstHomeBoxNote) firstHomeBoxNote.textContent = tr("firstHomeBoxNote");
1176
+ if (lblPrimaCasaMutuoEnabled) lblPrimaCasaMutuoEnabled.textContent = tr("firstHomeMortgageEnabledLabel");
1177
+ if (hintPrimaCasaMutuoEnabled) hintPrimaCasaMutuoEnabled.title = tr("firstHomeMortgageEnabledHint");
1178
+ if (lblPrimaCasaMutuoImporto) lblPrimaCasaMutuoImporto.textContent = msg("firstHomeMortgageAmountLabel", { currency: currentCurrency });
1179
+ if (hintPrimaCasaMutuoImporto) hintPrimaCasaMutuoImporto.title = tr("firstHomeMortgageAmountHint");
1180
+ if (lblPrimaCasaAssegnataA) lblPrimaCasaAssegnataA.textContent = tr("firstHomeAssignedToLabel");
1181
+ if (hintPrimaCasaAssegnataA) hintPrimaCasaAssegnataA.title = tr("firstHomeAssignedToHint");
1182
+ if (lblPrimaCasaMutuoPerc1) lblPrimaCasaMutuoPerc1.textContent = msg("firstHomeSplitLabel", { spouse: c1n() });
1183
+ if (hintPrimaCasaMutuoPerc1) hintPrimaCasaMutuoPerc1.title = msg("firstHomeSplitHint", { spouse: c1n() });
1062
1184
  if (lblStraordAnn1) lblStraordAnn1.textContent = msg("extraAnnLabel1", { spouse: c1n(), currency: currentCurrency });
1063
1185
  if (lblStraordAnn2) lblStraordAnn2.textContent = msg("extraAnnLabel2", { spouse: c2n(), currency: currentCurrency });
1064
1186
  if (hintStraordAnn1) hintStraordAnn1.title = msg("extraAnnHint1", { spouse: c1n() });
@@ -1108,6 +1230,10 @@ const defaultExpenseItems = [
1108
1230
  if (visitorTotalLabel) visitorTotalLabel.textContent = tr("footerVisitorsTotal");
1109
1231
  if (visitorActiveLabel) visitorActiveLabel.textContent = tr("footerVisitorsActive");
1110
1232
  if (visitorLoggedLabel) visitorLoggedLabel.textContent = tr("footerLoggedUsers");
1233
+ rowsSpese.querySelectorAll("textarea.spese-detail-text").forEach((el) => {
1234
+ updateExpenseDetailCounter(el);
1235
+ });
1236
+ updateFirstHomeMortgageUi();
1111
1237
  updateExtraordinaryModuleUi();
1112
1238
  updatePermanenceCalendarSummary();
1113
1239
  renderVisitorCounters();
@@ -1182,6 +1308,73 @@ const defaultExpenseItems = [
1182
1308
  .replaceAll("'", "&#39;");
1183
1309
  }
1184
1310
 
1311
+ function getExpenseDetailMaxHeight(textarea) {
1312
+ if (!textarea) return 0;
1313
+ const style = window.getComputedStyle(textarea);
1314
+ const lineHeight = Number.parseFloat(style.lineHeight) || 18;
1315
+ const borderTop = Number.parseFloat(style.borderTopWidth) || 0;
1316
+ const borderBottom = Number.parseFloat(style.borderBottomWidth) || 0;
1317
+ const paddingTop = Number.parseFloat(style.paddingTop) || 0;
1318
+ const paddingBottom = Number.parseFloat(style.paddingBottom) || 0;
1319
+ return Math.round((lineHeight * EXPENSE_DETAIL_MAX_LINES) + paddingTop + paddingBottom + borderTop + borderBottom);
1320
+ }
1321
+
1322
+ function autoResizeExpenseDetailTextarea(textarea, preferredHeight = 0) {
1323
+ if (!textarea) return;
1324
+ const maxHeight = getExpenseDetailMaxHeight(textarea);
1325
+ textarea.style.height = "auto";
1326
+ const scrollHeight = Math.max(textarea.scrollHeight, 0);
1327
+ const targetHeight = preferredHeight > 0
1328
+ ? Math.min(maxHeight, Math.max(scrollHeight, preferredHeight))
1329
+ : Math.min(maxHeight, scrollHeight);
1330
+ textarea.style.height = `${Math.max(0, targetHeight)}px`;
1331
+ textarea.style.overflowY = scrollHeight > maxHeight ? "auto" : "hidden";
1332
+ }
1333
+
1334
+ function updateExpenseDetailCounter(textarea) {
1335
+ if (!textarea || !textarea.id) return;
1336
+ const counter = document.getElementById(`${textarea.id}Counter`);
1337
+ if (!counter) return;
1338
+ const maxLen = Number(textarea.getAttribute("maxlength") || EXPENSE_DETAIL_MAX_CHARS);
1339
+ const len = String(textarea.value || "").length;
1340
+ const remaining = Math.max(0, maxLen - len);
1341
+ counter.textContent = msg("expenseDetailCharsRemaining", { count: String(remaining) });
1342
+ counter.classList.toggle("is-limit", remaining <= Math.max(20, Math.round(maxLen * 0.05)));
1343
+ }
1344
+
1345
+ function updateExpenseDetailTextareaUi(textarea, preferredHeight = 0) {
1346
+ if (!textarea) return;
1347
+ autoResizeExpenseDetailTextarea(textarea, preferredHeight);
1348
+ updateExpenseDetailCounter(textarea);
1349
+ }
1350
+
1351
+ function collectExpenseDetailUiMeta(spouseKey, idx) {
1352
+ const wrap = document.getElementById(`${spouseKey}dw_${idx}`);
1353
+ const textArea = document.getElementById(`${spouseKey}d_${idx}`);
1354
+ const inlineHeight = textArea ? Number.parseFloat(String(textArea.style.height || "0")) : 0;
1355
+ return {
1356
+ open: wrap ? !wrap.classList.contains("is-hidden") : false,
1357
+ height: Number.isFinite(inlineHeight) && inlineHeight > 0 ? inlineHeight : 0
1358
+ };
1359
+ }
1360
+
1361
+ function applyExpenseDetailUiMeta(spouseKey, idx, meta) {
1362
+ const wrap = document.getElementById(`${spouseKey}dw_${idx}`);
1363
+ const textarea = document.getElementById(`${spouseKey}d_${idx}`);
1364
+ const btn = rowsSpese.querySelector(`button[data-detail-target='${spouseKey}d_${idx}']`);
1365
+ const open = !!(meta && meta.open);
1366
+
1367
+ if (wrap) wrap.classList.toggle("is-hidden", !open);
1368
+ if (btn) btn.classList.toggle("is-open", open);
1369
+
1370
+ const preferredHeight = Number(meta && meta.height);
1371
+ if (textarea && Number.isFinite(preferredHeight) && preferredHeight > 0) {
1372
+ updateExpenseDetailTextareaUi(textarea, preferredHeight);
1373
+ } else if (textarea) {
1374
+ updateExpenseDetailTextareaUi(textarea);
1375
+ }
1376
+ }
1377
+
1185
1378
  function normalizeExpenseItem(item, fallbackIdx = 0) {
1186
1379
  const rawLabel = String(item && item.label ? item.label : "").trim();
1187
1380
  const rawHelp = String(item && item.help ? item.help : "").trim();
@@ -2357,7 +2550,8 @@ const defaultExpenseItems = [
2357
2550
  <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
2551
  </div>
2359
2552
  <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>
2553
+ <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>
2554
+ <div class="spese-detail-counter" id="c1d_${idx}Counter" aria-live="polite"></div>
2361
2555
  </div>
2362
2556
  <span class="spese-partial" id="p1_${idx}" title="${tr("expensePartialTitle")}">${tr("expensePartialLabel")}: ${eurTiny(0)}</span>
2363
2557
  </div>
@@ -2369,7 +2563,8 @@ const defaultExpenseItems = [
2369
2563
  <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
2564
  </div>
2371
2565
  <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>
2566
+ <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>
2567
+ <div class="spese-detail-counter" id="c2d_${idx}Counter" aria-live="polite"></div>
2373
2568
  </div>
2374
2569
  <span class="spese-partial" id="p2_${idx}" title="${tr("expensePartialTitle")}">${tr("expensePartialLabel")}: ${eurTiny(0)}</span>
2375
2570
  </div>
@@ -2380,6 +2575,9 @@ const defaultExpenseItems = [
2380
2575
  `;
2381
2576
  rowsSpese.appendChild(rowEl);
2382
2577
  });
2578
+ rowsSpese.querySelectorAll("textarea.spese-detail-text").forEach((el) => {
2579
+ updateExpenseDetailTextareaUi(el);
2580
+ });
2383
2581
  refreshExpenseDetailButtonState();
2384
2582
  }
2385
2583
 
@@ -2388,7 +2586,9 @@ const defaultExpenseItems = [
2388
2586
  c1: num(`c1_${i}`),
2389
2587
  c2: num(`c2_${i}`),
2390
2588
  d1: String(document.getElementById(`c1d_${i}`)?.value || "").trim(),
2391
- d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim()
2589
+ d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim(),
2590
+ d1Ui: collectExpenseDetailUiMeta("c1", i),
2591
+ d2Ui: collectExpenseDetailUiMeta("c2", i)
2392
2592
  }));
2393
2593
  }
2394
2594
 
@@ -2403,6 +2603,8 @@ const defaultExpenseItems = [
2403
2603
  if (c2) c2.value = Number.isFinite(Number(row && row.c2)) ? Number(row.c2) : 0;
2404
2604
  if (d1) d1.value = String(row && row.d1 ? row.d1 : "");
2405
2605
  if (d2) d2.value = String(row && row.d2 ? row.d2 : "");
2606
+ applyExpenseDetailUiMeta("c1", i, row && row.d1Ui ? row.d1Ui : null);
2607
+ applyExpenseDetailUiMeta("c2", i, row && row.d2Ui ? row.d2Ui : null);
2406
2608
  });
2407
2609
  refreshExpenseDetailButtonState();
2408
2610
  }
@@ -2414,6 +2616,7 @@ const defaultExpenseItems = [
2414
2616
  const detailEl = document.getElementById(targetId);
2415
2617
  const hasNote = !!(detailEl && String(detailEl.value || "").trim());
2416
2618
  btn.classList.toggle("has-note", hasNote);
2619
+ btn.setAttribute("aria-label", hasNote ? `${tr("expenseDetailBtn")} ✓` : tr("expenseDetailBtn"));
2417
2620
  });
2418
2621
  }
2419
2622
 
@@ -2967,8 +3170,11 @@ const defaultExpenseItems = [
2967
3170
  const c2Spese = expenseItems.map((_, idx) => num(`c2_${idx}`));
2968
3171
  const c1SpeseDetails = expenseItems.map((_, idx) => String(document.getElementById(`c1d_${idx}`)?.value || "").trim());
2969
3172
  const c2SpeseDetails = expenseItems.map((_, idx) => String(document.getElementById(`c2d_${idx}`)?.value || "").trim());
3173
+ const c1SpeseDetailUi = expenseItems.map((_, idx) => collectExpenseDetailUiMeta("c1", idx));
3174
+ const c2SpeseDetailUi = expenseItems.map((_, idx) => collectExpenseDetailUiMeta("c2", idx));
2970
3175
  const extra1 = getExtraordinaryMonthly(1);
2971
3176
  const extra2 = getExtraordinaryMonthly(2);
3177
+ const firstHome = getFirstHomeMortgageInput();
2972
3178
  if (extra1 > 0) c1Spese.push(extra1);
2973
3179
  if (extra2 > 0) c2Spese.push(extra2);
2974
3180
 
@@ -2989,10 +3195,16 @@ const defaultExpenseItems = [
2989
3195
  aPag2: num("assegnoPagato2"),
2990
3196
  aFam1: num("assegnoFam1"),
2991
3197
  aFam2: num("assegnoFam2"),
3198
+ primaCasaMutuoEnabled: firstHome.enabled ? 1 : 0,
3199
+ primaCasaMutuoImporto: firstHome.amount,
3200
+ primaCasaAssegnataA: firstHome.assignedTo,
3201
+ primaCasaMutuoPerc1: firstHome.share1,
2992
3202
  straordAnn1: num("straordAnn1"),
2993
3203
  straordAnn2: num("straordAnn2"),
2994
3204
  c1SpeseDetails,
2995
3205
  c2SpeseDetails,
3206
+ c1SpeseDetailUi,
3207
+ c2SpeseDetailUi,
2996
3208
  c1Spese,
2997
3209
  c2Spese
2998
3210
  };
@@ -3023,14 +3235,26 @@ const defaultExpenseItems = [
3023
3235
  const aPag2 = Number(payload.aPag2 || 0);
3024
3236
  const aFam1 = Number(payload.aFam1 || 0);
3025
3237
  const aFam2 = Number(payload.aFam2 || 0);
3238
+ const primaCasaMutuoEnabled = Number(payload.primaCasaMutuoEnabled || 0) > 0;
3239
+ const primaCasaMutuoImporto = Math.max(0, Number(payload.primaCasaMutuoImporto || 0));
3240
+ const primaCasaAssegnataA = (String(payload.primaCasaAssegnataA || "") === "1" || String(payload.primaCasaAssegnataA || "") === "2")
3241
+ ? String(payload.primaCasaAssegnataA)
3242
+ : "";
3243
+ const rawMutuoPerc1 = payload.primaCasaMutuoPerc1 === undefined ? 50 : payload.primaCasaMutuoPerc1;
3244
+ const primaCasaMutuoPerc1 = Math.min(100, Math.max(0, Number(rawMutuoPerc1 || 0)));
3245
+ const primaCasaMutuoPerc2 = 100 - primaCasaMutuoPerc1;
3246
+ const quotaMutuoSpese1 = primaCasaMutuoEnabled ? (primaCasaMutuoImporto * (primaCasaMutuoPerc1 / 100)) : 0;
3247
+ const quotaMutuoSpese2 = primaCasaMutuoEnabled ? (primaCasaMutuoImporto - quotaMutuoSpese1) : 0;
3026
3248
 
3027
3249
  const match12 = Math.min(aPag1, aPerc2);
3028
3250
  const match21 = Math.min(aPag2, aPerc1);
3029
3251
  const esternoPag1 = Math.max(0, aPag1 - match12);
3030
3252
  const esternoPag2 = Math.max(0, aPag2 - match21);
3031
3253
 
3032
- const spese1 = (payload.c1Spese || []).reduce((acc, v) => acc + Number(v || 0), 0);
3033
- const spese2 = (payload.c2Spese || []).reduce((acc, v) => acc + Number(v || 0), 0);
3254
+ const speseBase1 = (payload.c1Spese || []).reduce((acc, v) => acc + Number(v || 0), 0);
3255
+ const speseBase2 = (payload.c2Spese || []).reduce((acc, v) => acc + Number(v || 0), 0);
3256
+ const spese1 = speseBase1 + quotaMutuoSpese1;
3257
+ const spese2 = speseBase2 + quotaMutuoSpese2;
3034
3258
  const speseTot = spese1 + spese2;
3035
3259
 
3036
3260
  const disp1 = r1 + aPerc1 + aFam1 - aPag1 - spese1;
@@ -3071,6 +3295,31 @@ const defaultExpenseItems = [
3071
3295
  assegnoDa2a1 = nonCollocatario === 2 ? contributoIndiretto : 0;
3072
3296
  }
3073
3297
 
3298
+ const assegnoBaseDa1a2 = assegnoDa1a2;
3299
+ const assegnoBaseDa2a1 = assegnoDa2a1;
3300
+
3301
+ const primaCasaConsidered = primaCasaMutuoEnabled && primaCasaMutuoImporto > 0
3302
+ && primaCasaAssegnataA !== ""
3303
+ && Number(primaCasaAssegnataA) === collocatario;
3304
+ let primaCasaTransfer1to2 = 0;
3305
+ let primaCasaTransfer2to1 = 0;
3306
+ if (primaCasaConsidered) {
3307
+ if (primaCasaAssegnataA === "1") {
3308
+ primaCasaTransfer2to1 = Math.max(0, quotaMutuoSpese2);
3309
+ } else if (primaCasaAssegnataA === "2") {
3310
+ primaCasaTransfer1to2 = Math.max(0, quotaMutuoSpese1);
3311
+ }
3312
+ }
3313
+
3314
+ assegnoDa1a2 = Math.max(0, assegnoDa1a2 - primaCasaTransfer1to2);
3315
+ assegnoDa2a1 = Math.max(0, assegnoDa2a1 - primaCasaTransfer2to1);
3316
+
3317
+ const compensativeBenefits = [];
3318
+ if (aFam1 > 0.005) compensativeBenefits.push({ type: "family", to: 1, amount: aFam1 });
3319
+ if (aFam2 > 0.005) compensativeBenefits.push({ type: "family", to: 2, amount: aFam2 });
3320
+ if (primaCasaTransfer1to2 > 0.005) compensativeBenefits.push({ type: "primary-home-mortgage", from: 1, to: 2, amount: primaCasaTransfer1to2 });
3321
+ if (primaCasaTransfer2to1 > 0.005) compensativeBenefits.push({ type: "primary-home-mortgage", from: 2, to: 1, amount: primaCasaTransfer2to1 });
3322
+
3074
3323
  const post1 = disp1 - assegnoDa1a2 + assegnoDa2a1;
3075
3324
  const post2 = disp2 - assegnoDa2a1 + assegnoDa1a2;
3076
3325
 
@@ -3078,6 +3327,7 @@ const defaultExpenseItems = [
3078
3327
  r1, r2, r1Raw, r2Raw, incomeMode, figli, perm1, perm2,
3079
3328
  aPerc1, aPag1, aPerc2, aPag2, aFam1, aFam2,
3080
3329
  match12, match21, esternoPag1, esternoPag2,
3330
+ speseBase1, speseBase2, quotaMutuoSpese1, quotaMutuoSpese2,
3081
3331
  spese1, spese2, speseTot,
3082
3332
  disp1, disp2, peso1, peso2,
3083
3333
  mode, simplePerc,
@@ -3085,6 +3335,11 @@ const defaultExpenseItems = [
3085
3335
  fabbisognoFigli, quotaTeorica1, quotaTeorica2,
3086
3336
  quotaDiretta1, quotaDiretta2,
3087
3337
  saldo1, saldo2,
3338
+ assegnoBaseDa1a2, assegnoBaseDa2a1,
3339
+ primaCasaMutuoEnabled, primaCasaMutuoImporto, primaCasaAssegnataA,
3340
+ primaCasaMutuoPerc1, primaCasaMutuoPerc2,
3341
+ primaCasaConsidered, primaCasaTransfer1to2, primaCasaTransfer2to1,
3342
+ compensativeBenefits,
3088
3343
  assegnoDa1a2, assegnoDa2a1,
3089
3344
  post1, post2
3090
3345
  };
@@ -3167,6 +3422,7 @@ const defaultExpenseItems = [
3167
3422
  if (lblStraordAnn2) lblStraordAnn2.textContent = msg("extraAnnLabel2", { spouse: c2n(), currency: currentCurrency });
3168
3423
  if (hintStraordAnn1) hintStraordAnn1.title = msg("extraAnnHint1", { spouse: c1n() });
3169
3424
  if (hintStraordAnn2) hintStraordAnn2.title = msg("extraAnnHint2", { spouse: c2n() });
3425
+ updateFirstHomeMortgageUi();
3170
3426
  updateExtraordinaryModuleUi();
3171
3427
  updatePermanenceCalendarSummary();
3172
3428
  }
@@ -3176,6 +3432,71 @@ const defaultExpenseItems = [
3176
3432
  return annual > 0 ? annual / 12 : 0;
3177
3433
  }
3178
3434
 
3435
+ function getFirstHomeMortgageInput() {
3436
+ const enabled = !!document.getElementById("primaCasaMutuoEnabled")?.checked;
3437
+ const amount = Math.max(0, num("primaCasaMutuoImporto"));
3438
+ const assignedToRaw = String(document.getElementById("primaCasaAssegnataA")?.value || "").trim();
3439
+ const assignedTo = (assignedToRaw === "1" || assignedToRaw === "2") ? assignedToRaw : "";
3440
+ const share1 = Math.min(100, Math.max(0, num("primaCasaMutuoPerc1")));
3441
+ const share2 = 100 - share1;
3442
+ return { enabled, amount, assignedTo, share1, share2 };
3443
+ }
3444
+
3445
+ function updateFirstHomeMortgageUi() {
3446
+ const enabledEl = document.getElementById("primaCasaMutuoEnabled");
3447
+ const amountEl = document.getElementById("primaCasaMutuoImporto");
3448
+ const assignedEl = document.getElementById("primaCasaAssegnataA");
3449
+ const shareEl = document.getElementById("primaCasaMutuoPerc1");
3450
+ const splitInfoEl = document.getElementById("primaCasaMutuoSplitInfo");
3451
+ const splitCenterEl = document.getElementById("primaCasaSplitCenter");
3452
+ const splitLeftNameEl = document.getElementById("primaCasaSplitLeftName");
3453
+ const splitRightNameEl = document.getElementById("primaCasaSplitRightName");
3454
+ const splitLeftAmountEl = document.getElementById("primaCasaSplitLeftAmount");
3455
+ const splitRightAmountEl = document.getElementById("primaCasaSplitRightAmount");
3456
+ const splitWrapEl = document.getElementById("primaCasaMutuoSliderWrap");
3457
+ const splitLabelEl = document.getElementById("lblPrimaCasaMutuoPerc1");
3458
+ const splitHintEl = document.getElementById("hintPrimaCasaMutuoPerc1");
3459
+ if (!enabledEl || !amountEl || !assignedEl || !shareEl) return;
3460
+
3461
+ const isEnabled = !!enabledEl.checked;
3462
+ amountEl.disabled = !isEnabled;
3463
+ assignedEl.disabled = !isEnabled;
3464
+ shareEl.disabled = !isEnabled;
3465
+ if (splitWrapEl) splitWrapEl.classList.toggle("is-disabled", !isEnabled);
3466
+
3467
+ const normalizedShare1 = Math.min(100, Math.max(0, num("primaCasaMutuoPerc1")));
3468
+ if (Math.abs(normalizedShare1 - Number(shareEl.value || 0)) > 0.0001) {
3469
+ shareEl.value = normalizedShare1.toFixed(0);
3470
+ }
3471
+
3472
+ const share2 = 100 - normalizedShare1;
3473
+ const amount = Math.max(0, num("primaCasaMutuoImporto"));
3474
+ const quota1 = amount * (normalizedShare1 / 100);
3475
+ const quota2 = amount - quota1;
3476
+ if (splitLabelEl) splitLabelEl.textContent = msg("firstHomeSplitLabel", { spouse: c1n() });
3477
+ if (splitHintEl) splitHintEl.title = msg("firstHomeSplitHint", { spouse: c1n() });
3478
+ if (splitInfoEl) {
3479
+ splitInfoEl.textContent = msg("firstHomeSplitInfo", {
3480
+ spouse1: c1n(),
3481
+ spouse2: c2n(),
3482
+ p1: normalizedShare1.toFixed(0),
3483
+ p2: share2.toFixed(0)
3484
+ });
3485
+ }
3486
+ if (splitCenterEl) splitCenterEl.textContent = `${normalizedShare1.toFixed(0)}% / ${share2.toFixed(0)}%`;
3487
+ if (splitLeftNameEl) splitLeftNameEl.textContent = c1n();
3488
+ if (splitRightNameEl) splitRightNameEl.textContent = c2n();
3489
+ if (splitLeftAmountEl) splitLeftAmountEl.textContent = eur(quota1);
3490
+ if (splitRightAmountEl) splitRightAmountEl.textContent = eur(quota2);
3491
+
3492
+ const noneOpt = assignedEl.querySelector("option[value='']");
3493
+ const spouse1Opt = assignedEl.querySelector("option[value='1']");
3494
+ const spouse2Opt = assignedEl.querySelector("option[value='2']");
3495
+ if (noneOpt) noneOpt.textContent = tr("firstHomeAssignedToNone");
3496
+ if (spouse1Opt) spouse1Opt.textContent = msg("firstHomeAssignedToSpouse", { spouse: c1n() });
3497
+ if (spouse2Opt) spouse2Opt.textContent = msg("firstHomeAssignedToSpouse", { spouse: c2n() });
3498
+ }
3499
+
3179
3500
  function updateExtraordinaryModuleUi() {
3180
3501
  const month1 = document.getElementById("straordMonth1");
3181
3502
  const month2 = document.getElementById("straordMonth2");
@@ -3635,6 +3956,11 @@ const defaultExpenseItems = [
3635
3956
  if (!el) return;
3636
3957
  el.value = value;
3637
3958
  };
3959
+ const setChecked = (id, value) => {
3960
+ const el = document.getElementById(id);
3961
+ if (!el) return;
3962
+ el.checked = !!value;
3963
+ };
3638
3964
 
3639
3965
  setVal("nome1", payload._nome1 || c1n());
3640
3966
  setVal("nome2", payload._nome2 || c2n());
@@ -3660,6 +3986,10 @@ const defaultExpenseItems = [
3660
3986
  setVal("assegnoPercepito2", Number(payload.aPerc2 || 0));
3661
3987
  setVal("assegnoPagato2", Number(payload.aPag2 || 0));
3662
3988
  setVal("assegnoFam2", Number(payload.aFam2 || 0));
3989
+ setChecked("primaCasaMutuoEnabled", Number(payload.primaCasaMutuoEnabled || 0) > 0);
3990
+ setVal("primaCasaMutuoImporto", Number(payload.primaCasaMutuoImporto || 0));
3991
+ setVal("primaCasaAssegnataA", (String(payload.primaCasaAssegnataA || "") === "1" || String(payload.primaCasaAssegnataA || "") === "2") ? String(payload.primaCasaAssegnataA) : "");
3992
+ setVal("primaCasaMutuoPerc1", Math.min(100, Math.max(0, Number((payload.primaCasaMutuoPerc1 === undefined ? 50 : payload.primaCasaMutuoPerc1) || 0))));
3663
3993
  setVal("straordAnn1", Number(payload.straordAnn1 || 0));
3664
3994
  setVal("straordAnn2", Number(payload.straordAnn2 || 0));
3665
3995
 
@@ -3667,6 +3997,8 @@ const defaultExpenseItems = [
3667
3997
  const c2Spese = Array.isArray(payload.c2Spese) ? payload.c2Spese : [];
3668
3998
  const c1SpeseDetails = Array.isArray(payload.c1SpeseDetails) ? payload.c1SpeseDetails : [];
3669
3999
  const c2SpeseDetails = Array.isArray(payload.c2SpeseDetails) ? payload.c2SpeseDetails : [];
4000
+ const c1SpeseDetailUi = Array.isArray(payload.c1SpeseDetailUi) ? payload.c1SpeseDetailUi : [];
4001
+ const c2SpeseDetailUi = Array.isArray(payload.c2SpeseDetailUi) ? payload.c2SpeseDetailUi : [];
3670
4002
  expenseItems.forEach((_, i) => {
3671
4003
  const c1 = document.getElementById(`c1_${i}`);
3672
4004
  const c2 = document.getElementById(`c2_${i}`);
@@ -3676,6 +4008,8 @@ const defaultExpenseItems = [
3676
4008
  if (c2) c2.value = Number(c2Spese[i] || 0);
3677
4009
  if (d1) d1.value = String(c1SpeseDetails[i] || "");
3678
4010
  if (d2) d2.value = String(c2SpeseDetails[i] || "");
4011
+ applyExpenseDetailUiMeta("c1", i, c1SpeseDetailUi[i]);
4012
+ applyExpenseDetailUiMeta("c2", i, c2SpeseDetailUi[i]);
3679
4013
  });
3680
4014
  refreshExpenseDetailButtonState();
3681
4015
 
@@ -3686,6 +4020,7 @@ const defaultExpenseItems = [
3686
4020
  };
3687
4021
 
3688
4022
  updateSpouseLabels();
4023
+ updateFirstHomeMortgageUi();
3689
4024
  if (payload._permanenceCalendar && typeof payload._permanenceCalendar === "object") {
3690
4025
  importPermanenceCalendarState(payload._permanenceCalendar);
3691
4026
  syncPermanenza("calendar");
@@ -3810,6 +4145,22 @@ const defaultExpenseItems = [
3810
4145
  const peso2Pct = (m.peso2 * 100).toFixed(1);
3811
4146
  const days1 = ((m.perm1 / 100) * 30).toFixed(1);
3812
4147
  const days2 = ((m.perm2 / 100) * 30).toFixed(1);
4148
+ const compBenefits = getCompensativeBenefitRows(m, c1Name, c2Name);
4149
+ const compBenefitsRowsHtml = compBenefits.length
4150
+ ? compBenefits.map((row) => `<tr><td>${escapeHtml(row.label)}</td><td class="num">${eur(row.amount)}</td></tr>`).join("")
4151
+ : `<tr><td colspan="2">${tr("pdfCompBenefitsNone")}</td></tr>`;
4152
+ const primaryHomeAssignedLabel = m.primaCasaAssegnataA === "1"
4153
+ ? c1NameEsc
4154
+ : m.primaCasaAssegnataA === "2"
4155
+ ? c2NameEsc
4156
+ : tr("pdfPrimaryHomeNotDeclared");
4157
+ const primaryHomeSummaryRows = m.primaCasaMutuoEnabled
4158
+ ? `
4159
+ <tr><td>${tr("pdfPrimaryHomeAssignedTo")}</td><td>${primaryHomeAssignedLabel}</td></tr>
4160
+ <tr><td>${tr("pdfPrimaryHomeMonthlyAmount")}</td><td>${eur(m.primaCasaMutuoImporto || 0)}</td></tr>
4161
+ <tr><td>${tr("pdfPrimaryHomeSplit")}</td><td>${c1NameEsc} ${(m.primaCasaMutuoPerc1 || 0).toFixed(0)}% · ${c2NameEsc} ${(m.primaCasaMutuoPerc2 || 0).toFixed(0)}%</td></tr>
4162
+ <tr><td>${tr("pdfPrimaryHomeAppliedOnlyColl")}</td><td>${m.primaCasaConsidered ? "OK" : tr("pdfPrimaryHomeNotDeclared")}</td></tr>`
4163
+ : `<tr><td>${tr("pdfPrimaryHomeMortgage")}</td><td>${tr("pdfPrimaryHomeNotDeclared")}</td></tr>`;
3813
4164
  const isAssegno1 = m.assegnoDa1a2 > 0.005;
3814
4165
  const isAssegno2 = m.assegnoDa2a1 > 0.005;
3815
4166
  const n1 = escapeHtml(c1n());
@@ -3824,18 +4175,24 @@ const defaultExpenseItems = [
3824
4175
  let resultDetail;
3825
4176
  if (isAssegno1) {
3826
4177
  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>
4178
+ <div class="spieg-result-flow">${n1} &rarr; ${n2}</div>
4179
+ <div class="spieg-result-formula">${n1}: ${eur(m.quotaTeorica1)} &minus; ${eur(m.quotaDiretta1)}</div>
4180
+ <div class="spieg-result-amount ok">${eur(m.assegnoDa1a2)}</div>
3829
4181
  `;
3830
4182
  resultDetail = tr("spiegDetailResultTransfer");
3831
4183
  } else if (isAssegno2) {
3832
4184
  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>
4185
+ <div class="spieg-result-flow">${n2} &rarr; ${n1}</div>
4186
+ <div class="spieg-result-formula">${n2}: ${eur(m.quotaTeorica2)} &minus; ${eur(m.quotaDiretta2)}</div>
4187
+ <div class="spieg-result-amount ok">${eur(m.assegnoDa2a1)}</div>
3835
4188
  `;
3836
4189
  resultDetail = tr("spiegDetailResultTransfer");
3837
4190
  } else {
3838
- resultHtml = `<span class="ok">${tr("calcNoTransferSuggested")}</span>`;
4191
+ const benefitRows = getCompensativeBenefitRows(m, c1n(), c2n());
4192
+ const benefitsHtml = benefitRows.length
4193
+ ? `<div class="spieg-line" style="margin-top:6px"><strong>${tr("calcCompBenefitsLabel")}:</strong> ${benefitRows.map((row) => `${escapeHtml(row.label)} (${eur(row.amount)})`).join(" | ")}</div>`
4194
+ : "";
4195
+ resultHtml = `<div class="spieg-result-empty ok">${tr("calcNoTransferSuggested")}</div>${benefitsHtml}`;
3839
4196
  resultDetail = tr("spiegDetailResultNoTransfer");
3840
4197
  }
3841
4198
 
@@ -3844,27 +4201,43 @@ const defaultExpenseItems = [
3844
4201
  <summary class="spieg-title">${tr("spiegTitle")}</summary>
3845
4202
  <div class="spieg-grid">
3846
4203
  <div class="spieg-item">
3847
- <div class="spieg-item-label">${tr("spiegRedditiLabel")} ${infoTip(tr("spiegDetailIncome"))}</div>
4204
+ <div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">&#128184;</span>${tr("spiegRedditiLabel")} ${infoTip(tr("spiegDetailIncome"))}</div>
3848
4205
  <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>
4206
+ <div class="spieg-people">
4207
+ <div class="spieg-person spieg-person--left">
4208
+ <div class="spieg-person-name">${n1}</div>
4209
+ <div class="spieg-person-value">${eur(m.disp1)}</div>
4210
+ <div class="spieg-person-sub">${tr("pdfWeight")}: ${peso1Pct}%</div>
4211
+ </div>
4212
+ <div class="spieg-person spieg-person--right">
4213
+ <div class="spieg-person-name">${n2}</div>
4214
+ <div class="spieg-person-value">${eur(m.disp2)}</div>
4215
+ <div class="spieg-person-sub">${tr("pdfWeight")}: ${peso2Pct}%</div>
4216
+ </div>
4217
+ </div>
3851
4218
  </div>
3852
4219
  </div>
3853
4220
  <div class="spieg-item">
3854
- <div class="spieg-item-label">${tr("spiegSpeseLabel")} ${infoTip(tr("spiegDetailExpense"))}</div>
4221
+ <div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">&#128221;</span>${tr("spiegSpeseLabel")} ${infoTip(tr("spiegDetailExpense"))}</div>
3855
4222
  <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>
4223
+ <div class="spieg-equation">
4224
+ <span class="spieg-pill">${eur(m.speseTot)}</span>
4225
+ <span class="spieg-op">&times;</span>
4226
+ <span class="spieg-pill">35%</span>
4227
+ <span class="spieg-op">=</span>
4228
+ <span class="spieg-pill spieg-pill--result">${eur(m.fabbisognoFigli)}</span>
4229
+ </div>
3857
4230
  </div>
3858
4231
  </div>
3859
4232
  <div class="spieg-item">
3860
- <div class="spieg-item-label">${tr("spiegPermLabel")} ${infoTip(tr("spiegDetailPerm"))}</div>
4233
+ <div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">&#128197;</span>${tr("spiegPermLabel")} ${infoTip(tr("spiegDetailPerm"))}</div>
3861
4234
  <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>
4235
+ <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>
4236
+ <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
4237
  </div>
3865
4238
  </div>
3866
4239
  <div class="spieg-item spieg-item--result">
3867
- <div class="spieg-item-label">${tr("spiegResultLabel")} ${infoTip(resultDetail)}</div>
4240
+ <div class="spieg-item-label"><span class="spieg-item-icon" aria-hidden="true">&#127919;</span>${tr("spiegResultLabel")} ${infoTip(resultDetail)}</div>
3868
4241
  <div class="spieg-item-body spieg-item-body--result">${resultHtml}</div>
3869
4242
  </div>
3870
4243
  </div>
@@ -3884,6 +4257,31 @@ const defaultExpenseItems = [
3884
4257
  return tr("calcModeGenovaName");
3885
4258
  }
3886
4259
 
4260
+ function getCompensativeBenefitRows(m, name1 = c1n(), name2 = c2n()) {
4261
+ const rows = Array.isArray(m && m.compensativeBenefits) ? m.compensativeBenefits : [];
4262
+ return rows
4263
+ .filter((row) => row && Number(row.amount || 0) > 0.005)
4264
+ .map((row) => {
4265
+ const amount = Number(row.amount || 0);
4266
+ if (row.type === "family") {
4267
+ const spouse = Number(row.to) === 2 ? name2 : name1;
4268
+ return { label: msg("calcBenefitFamilyAllowance", { spouse }), amount };
4269
+ }
4270
+ if (row.type === "primary-home-mortgage") {
4271
+ const payer = Number(row.from) === 2 ? name2 : name1;
4272
+ const receiver = Number(row.to) === 2 ? name2 : name1;
4273
+ return { label: msg("calcBenefitPrimaryHomeMortgage", { payer, receiver }), amount };
4274
+ }
4275
+ return { label: tr("calcCompBenefitsLabel"), amount };
4276
+ });
4277
+ }
4278
+
4279
+ function formatCompensativeBenefitsInline(m, name1 = c1n(), name2 = c2n()) {
4280
+ return getCompensativeBenefitRows(m, name1, name2)
4281
+ .map((row) => `${row.label}: ${eur(row.amount)}`)
4282
+ .join(" | ");
4283
+ }
4284
+
3887
4285
  function calculate(model = null) {
3888
4286
  const m = model || computeModel();
3889
4287
  const formulaNote = document.getElementById("formulaNote");
@@ -3895,6 +4293,7 @@ const defaultExpenseItems = [
3895
4293
  const normProfileName = escapeHtml(getSelectedNormProfileLabel());
3896
4294
  const negotiationPayerName = m.assegnoDa1a2 > 0.005 ? c1n() : (m.assegnoDa2a1 > 0.005 ? c2n() : c1n());
3897
4295
  const negotiationReceiverName = m.assegnoDa1a2 > 0.005 ? c2n() : (m.assegnoDa2a1 > 0.005 ? c1n() : c2n());
4296
+ const benefitsInline = formatCompensativeBenefitsInline(m, c1n(), c2n());
3898
4297
 
3899
4298
  let modeSpecific = "";
3900
4299
  if (m.mode === "simple") {
@@ -3953,13 +4352,33 @@ const defaultExpenseItems = [
3953
4352
  ${m.incomeMode === "cu" ? `<br /><strong>${tr("calcIncomeBaseNote")}</strong> ${tr("cuNetNoteText")}` : ""}
3954
4353
  `;
3955
4354
 
3956
- let mainText = tr("calcNoTransferSuggested");
4355
+ let mainHtml = `<span class="result-main-line">${escapeHtml(tr("calcNoTransferSuggested"))}</span>`;
3957
4356
  if (m.assegnoDa1a2 > 0.005) {
3958
- mainText = `${c1n()} \u2192 ${c2n()}: ${eur(m.assegnoDa1a2)} ${tr("pdfPerMonth")}`;
4357
+ mainHtml = `
4358
+ <span class="result-main-flow">${escapeHtml(c1n())} &rarr; ${escapeHtml(c2n())}</span>
4359
+ <span class="result-main-amount">${eur(m.assegnoDa1a2)} ${escapeHtml(tr("pdfPerMonth"))}</span>
4360
+ `;
3959
4361
  } else if (m.assegnoDa2a1 > 0.005) {
3960
- mainText = `${c2n()} \u2192 ${c1n()}: ${eur(m.assegnoDa2a1)} ${tr("pdfPerMonth")}`;
4362
+ mainHtml = `
4363
+ <span class="result-main-flow">${escapeHtml(c2n())} &rarr; ${escapeHtml(c1n())}</span>
4364
+ <span class="result-main-amount">${eur(m.assegnoDa2a1)} ${escapeHtml(tr("pdfPerMonth"))}</span>
4365
+ `;
4366
+ } else {
4367
+ const benefitRows = getCompensativeBenefitRows(m, c1n(), c2n());
4368
+ if (benefitRows.length) {
4369
+ const benefitsHtml = benefitRows
4370
+ .map((row) => `<li><span>${escapeHtml(row.label)}</span><strong>${eur(row.amount)}</strong></li>`)
4371
+ .join("");
4372
+ mainHtml = `
4373
+ <span class="result-main-line">${escapeHtml(tr("calcNoTransferSuggested"))}</span>
4374
+ <span class="result-main-sub">${escapeHtml(tr("calcCompBenefitsLabel"))}</span>
4375
+ <ul class="result-benefits-list">${benefitsHtml}</ul>
4376
+ `;
4377
+ } else if (benefitsInline) {
4378
+ mainHtml = `<span class="result-main-line">${escapeHtml(msg("calcNoTransferWithBenefits", { benefits: benefitsInline }))}</span>`;
4379
+ }
3961
4380
  }
3962
- resultMain.textContent = mainText;
4381
+ resultMain.innerHTML = mainHtml;
3963
4382
 
3964
4383
  kpi.innerHTML = "";
3965
4384
 
@@ -3977,6 +4396,10 @@ const defaultExpenseItems = [
3977
4396
  [tr("pdfAmountPerChild"), eur((Math.max(m.assegnoDa1a2, m.assegnoDa2a1)) / m.figli), "warn"]
3978
4397
  ];
3979
4398
 
4399
+ if (benefitsInline) {
4400
+ items.push([tr("calcCompBenefitsLabel"), benefitsInline, "warn"]);
4401
+ }
4402
+
3980
4403
  if (m.incomeMode === "cu") {
3981
4404
  const ratio1 = m.r1Raw > 0 ? ((m.r1 * 12 / m.r1Raw) * 100) : 0;
3982
4405
  const ratio2 = m.r2Raw > 0 ? ((m.r2 * 12 / m.r2Raw) * 100) : 0;
@@ -4089,6 +4512,11 @@ const defaultExpenseItems = [
4089
4512
  <div class="pdf-explain-formula">${c2NameEsc}: ${eur(m.quotaTeorica2)} &minus; ${eur(m.quotaDiretta2)}</div>
4090
4513
  <div class="pdf-explain-amount">${eur(m.assegnoDa2a1)}</div>
4091
4514
  `;
4515
+ } else if (compBenefits.length) {
4516
+ explainResultHtml = `
4517
+ <div class="pdf-explain-result-empty">${tr("calcNoTransferSuggested")}</div>
4518
+ <div class="pdf-explain-formula"><strong>${tr("calcCompBenefitsLabel")}:</strong> ${compBenefits.map((row) => `${escapeHtml(row.label)} (${eur(row.amount)})`).join(" | ")}</div>
4519
+ `;
4092
4520
  }
4093
4521
 
4094
4522
  const extraSpese1Monthly = getExtraordinaryMonthly(1);
@@ -4097,7 +4525,7 @@ const defaultExpenseItems = [
4097
4525
  const el = document.getElementById(`${spouseKey}d_${idx}`);
4098
4526
  const raw = String(el && el.value ? el.value : "").trim();
4099
4527
  if (!raw) return "";
4100
- return raw;
4528
+ return `${escapeHtml(raw)} <span class="expense-detail-meta">(${raw.length}/${EXPENSE_DETAIL_MAX_CHARS})</span>`;
4101
4529
  };
4102
4530
  const speseRowsBase = expenseItems.map((item, i) => {
4103
4531
  const c1 = num(`c1_${i}`);
@@ -4105,8 +4533,8 @@ const defaultExpenseItems = [
4105
4533
  const d1 = formatExpenseDetail(i, "c1");
4106
4534
  const d2 = formatExpenseDetail(i, "c2");
4107
4535
  const details = [
4108
- d1 ? `${escapeHtml(c1n())}: ${escapeHtml(d1)}` : "",
4109
- d2 ? `${escapeHtml(c2n())}: ${escapeHtml(d2)}` : ""
4536
+ d1 ? `${escapeHtml(c1n())}: ${d1}` : "",
4537
+ d2 ? `${escapeHtml(c2n())}: ${d2}` : ""
4110
4538
  ].filter(Boolean).join("<br>");
4111
4539
  return `<tr>
4112
4540
  <td>${item.label}</td>
@@ -4125,7 +4553,16 @@ const defaultExpenseItems = [
4125
4553
  <td class="expense-detail-cell">–</td>
4126
4554
  </tr>`
4127
4555
  : "";
4128
- const speseRows = speseRowsBase + extraSpeseRow;
4556
+ const firstHomeExpenseRow = (m.quotaMutuoSpese1 > 0.005 || m.quotaMutuoSpese2 > 0.005)
4557
+ ? `<tr>
4558
+ <td>${tr("pdfPrimaryHomeMortgage")}</td>
4559
+ <td class="num">${m.quotaMutuoSpese1 > 0.005 ? eur(m.quotaMutuoSpese1) : "–"}</td>
4560
+ <td class="num">${m.quotaMutuoSpese2 > 0.005 ? eur(m.quotaMutuoSpese2) : "–"}</td>
4561
+ <td class="num bold">${eur((m.quotaMutuoSpese1 || 0) + (m.quotaMutuoSpese2 || 0))}</td>
4562
+ <td class="expense-detail-cell">${tr("firstHomeAssignedToLabel")}: ${primaryHomeAssignedLabel}</td>
4563
+ </tr>`
4564
+ : "";
4565
+ const speseRows = speseRowsBase + firstHomeExpenseRow + extraSpeseRow;
4129
4566
 
4130
4567
  const scenarioMetrics = [
4131
4568
  { label: tr("scenarioColMode"), val: (sm) => getModeName(sm.mode, sm.simplePerc), fmt: (v) => escapeHtml(v), numeric: false },
@@ -4309,6 +4746,7 @@ const defaultExpenseItems = [
4309
4746
  tr:nth-child(even) td { background: #f5faf9; }
4310
4747
  .num { text-align: right; font-variant-numeric: tabular-nums; }
4311
4748
  .bold { font-weight: 700; }
4749
+ .expense-detail-meta { color: #5f7873; font-size: 7pt; font-weight: 600; white-space: nowrap; }
4312
4750
  .total-row td { background: #ddf0ec !important; font-weight: 700; border-top: 1.5px solid #74c3b9; }
4313
4751
 
4314
4752
  /* ── TWO-COL LAYOUT ── */
@@ -4540,6 +4978,7 @@ const defaultExpenseItems = [
4540
4978
  <tr><td>${tr("pdfChildrenCount")}</td><td>${m.figli}</td></tr>
4541
4979
  <tr><td>${tr("pdfPermanence")} ${c1n()}</td><td>${m.perm1.toFixed(0)}%</td></tr>
4542
4980
  <tr><td>${tr("pdfPermanence")} ${c2n()}</td><td>${m.perm2.toFixed(0)}%</td></tr>
4981
+ ${primaryHomeSummaryRows}
4543
4982
  </tbody>
4544
4983
  </table>
4545
4984
  <table class="data-table">
@@ -4616,6 +5055,21 @@ const defaultExpenseItems = [
4616
5055
  </div>
4617
5056
  </div>
4618
5057
 
5058
+ <div class="section">
5059
+ <div class="section-title">${tr("pdfCompBenefitsSection")}</div>
5060
+ <table>
5061
+ <thead>
5062
+ <tr>
5063
+ <th>${tr("pdfCompBenefitsItem")}</th>
5064
+ <th class="num">${msg("pdfCompBenefitsAmount", { currency: currentCurrency })}</th>
5065
+ </tr>
5066
+ </thead>
5067
+ <tbody>
5068
+ ${compBenefitsRowsHtml}
5069
+ </tbody>
5070
+ </table>
5071
+ </div>
5072
+
4619
5073
  <!-- KPI -->
4620
5074
  <div class="section">
4621
5075
  <div class="section-title">${tr("pdfKpiSection")}</div>
@@ -4797,6 +5251,10 @@ ${scenarioLab.length ? `
4797
5251
  assegnoPercepito2: num("assegnoPercepito2"),
4798
5252
  assegnoPagato2: num("assegnoPagato2"),
4799
5253
  assegnoFam2: num("assegnoFam2"),
5254
+ primaCasaMutuoEnabled: document.getElementById("primaCasaMutuoEnabled")?.checked ? 1 : 0,
5255
+ primaCasaMutuoImporto: num("primaCasaMutuoImporto"),
5256
+ primaCasaAssegnataA: String(document.getElementById("primaCasaAssegnataA")?.value || ""),
5257
+ primaCasaMutuoPerc1: num("primaCasaMutuoPerc1"),
4800
5258
  straordAnn1: num("straordAnn1"),
4801
5259
  straordAnn2: num("straordAnn2")
4802
5260
  };
@@ -4804,7 +5262,9 @@ ${scenarioLab.length ? `
4804
5262
  c1: num(`c1_${i}`),
4805
5263
  c2: num(`c2_${i}`),
4806
5264
  d1: String(document.getElementById(`c1d_${i}`)?.value || "").trim(),
4807
- d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim()
5265
+ d2: String(document.getElementById(`c2d_${i}`)?.value || "").trim(),
5266
+ d1Ui: collectExpenseDetailUiMeta("c1", i),
5267
+ d2Ui: collectExpenseDetailUiMeta("c2", i)
4808
5268
  }));
4809
5269
  const expenseItemsState = expenseItems.map((item) => ({ label: item.label, help: item.help }));
4810
5270
  const scenariosState = scenarioLab.map((scenario, idx) => ({
@@ -4835,7 +5295,12 @@ ${scenarioLab.length ? `
4835
5295
  if (!state || !state.base || !state.spese) return;
4836
5296
  Object.entries(state.base).forEach(([k, v]) => {
4837
5297
  const el = document.getElementById(k);
4838
- if (el) el.value = v;
5298
+ if (!el) return;
5299
+ if (el.type === "checkbox") {
5300
+ el.checked = Number(v || 0) > 0;
5301
+ } else {
5302
+ el.value = v;
5303
+ }
4839
5304
  });
4840
5305
  if (Array.isArray(state.expenseItems) && state.expenseItems.length) {
4841
5306
  expenseItems = state.expenseItems.map((item, idx) => normalizeExpenseItem(item, idx));
@@ -4876,6 +5341,7 @@ ${scenarioLab.length ? `
4876
5341
  applyStaticTranslations();
4877
5342
  applyUiViewStateToDom();
4878
5343
  importPermanenceCalendarState(state.permanenceCalendar);
5344
+ updateFirstHomeMortgageUi();
4879
5345
  updateSpouseLabels();
4880
5346
  buildExpenseRows();
4881
5347
  syncPermanenza("calendar");
@@ -4888,16 +5354,22 @@ ${scenarioLab.length ? `
4888
5354
  if (c2) c2.value = row.c2;
4889
5355
  if (d1) d1.value = String(row && row.d1 ? row.d1 : "");
4890
5356
  if (d2) d2.value = String(row && row.d2 ? row.d2 : "");
5357
+ applyExpenseDetailUiMeta("c1", i, row && row.d1Ui ? row.d1Ui : null);
5358
+ applyExpenseDetailUiMeta("c2", i, row && row.d2Ui ? row.d2Ui : null);
4891
5359
  });
4892
5360
  refreshExpenseDetailButtonState();
4893
5361
  }
4894
5362
 
4895
5363
  function resetAll() {
4896
- document.querySelectorAll("input[type='number'], input[data-numeric='1']").forEach((el) => {
5364
+ document.querySelectorAll("input[type='number'], input[type='range'], input[data-numeric='1']").forEach((el) => {
4897
5365
  if (el.id !== "perm2") {
4898
5366
  el.value = el.defaultValue || 0;
4899
5367
  }
4900
5368
  });
5369
+ const firstHomeEnabled = document.getElementById("primaCasaMutuoEnabled");
5370
+ const firstHomeAssigned = document.getElementById("primaCasaAssegnataA");
5371
+ if (firstHomeEnabled) firstHomeEnabled.checked = !!firstHomeEnabled.defaultChecked;
5372
+ if (firstHomeAssigned) firstHomeAssigned.value = "";
4901
5373
  permanenceCalendarState.byMonth = {};
4902
5374
  selectedScenarioIdx = -1;
4903
5375
  uiViewState.spiegOpen = true;
@@ -4911,6 +5383,7 @@ ${scenarioLab.length ? `
4911
5383
  renderPermanenceCalendar(monthValue);
4912
5384
  applyPermanenceFromCalendar(monthValue, { silentRender: true });
4913
5385
  applyUiViewStateToDom();
5386
+ updateFirstHomeMortgageUi();
4914
5387
  syncPermanenza();
4915
5388
  renderAll();
4916
5389
  }
@@ -5026,7 +5499,10 @@ ${scenarioLab.length ? `
5026
5499
  const willOpen = wrap.classList.contains("is-hidden");
5027
5500
  wrap.classList.toggle("is-hidden", !willOpen);
5028
5501
  detailBtn.classList.toggle("is-open", willOpen);
5029
- if (willOpen && target) target.focus();
5502
+ if (target) {
5503
+ updateExpenseDetailTextareaUi(target);
5504
+ if (willOpen) target.focus();
5505
+ }
5030
5506
  }
5031
5507
  return;
5032
5508
  }
@@ -5039,6 +5515,7 @@ ${scenarioLab.length ? `
5039
5515
 
5040
5516
  rowsSpese.addEventListener("input", (e) => {
5041
5517
  if (e.target && e.target.matches("textarea.spese-detail-text")) {
5518
+ updateExpenseDetailTextareaUi(e.target);
5042
5519
  refreshExpenseDetailButtonState();
5043
5520
  }
5044
5521
  });
@@ -5145,11 +5622,13 @@ ${scenarioLab.length ? `
5145
5622
  }
5146
5623
 
5147
5624
  document.addEventListener("input", (e) => {
5148
- if (e.target && e.target.matches("input[type='number'], input[data-numeric='1']")) {
5625
+ if (e.target && e.target.matches("input[type='number'], input[type='range'], input[data-numeric='1']")) {
5149
5626
  if (e.target.id === "perm1") {
5150
5627
  syncPermanenza("perm1");
5151
5628
  } else if (e.target.id === "perm2") {
5152
5629
  syncPermanenza("perm2");
5630
+ } else if (e.target.id === "primaCasaMutuoPerc1" || e.target.id === "primaCasaMutuoImporto") {
5631
+ updateFirstHomeMortgageUi();
5153
5632
  } else if (e.target.id === "reddito1" || e.target.id === "reddito2") {
5154
5633
  const activeMode = document.getElementById("incomeMode")?.value || "monthly";
5155
5634
  incomeValuesByMode[activeMode] = {
@@ -5170,6 +5649,9 @@ ${scenarioLab.length ? `
5170
5649
  }
5171
5650
  updateModeUi();
5172
5651
  renderAll();
5652
+ } else if (e.target && (e.target.id === "primaCasaMutuoEnabled" || e.target.id === "primaCasaAssegnataA")) {
5653
+ updateFirstHomeMortgageUi();
5654
+ renderAll();
5173
5655
  }
5174
5656
  });
5175
5657
 
@@ -5213,6 +5695,7 @@ ${scenarioLab.length ? `
5213
5695
  updateAuthUi();
5214
5696
  renderCloudHistoryPanel();
5215
5697
  applyUiViewStateToDom();
5698
+ updateFirstHomeMortgageUi();
5216
5699
  syncPermanenza();
5217
5700
  incomeModeLast = document.getElementById("incomeMode").value || "monthly";
5218
5701
  incomeValuesByMode[incomeModeLast] = {