mantenimento-app 2.2.5 → 2.2.7

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
@@ -3457,15 +3457,17 @@ const defaultExpenseItems = [
3457
3457
  const housingUtilityNonColl = collocatario === 1 ? housingUtility2 : housingUtility1;
3458
3458
 
3459
3459
  const baseDuplicazione = speseConvivenza > 0 ? (speseTot - speseConvivenza) : null;
3460
+ // OECD-modified equivalence logic suggests split-household extra burden is material but not full duplication.
3461
+ const HOUSING_UTILITY_ADJ_FACTOR = 0.35;
3460
3462
  const separationAdjustmentHousingUtilities = baseDuplicazione !== null && baseDuplicazione <= 0.005
3461
- ? Math.max(0, housingUtilityNonColl)
3463
+ ? Math.max(0, housingUtilityNonColl * HOUSING_UTILITY_ADJ_FACTOR)
3462
3464
  : 0;
3463
- const speseConvivenzaEffettive = speseConvivenza > 0
3464
- ? Math.max(0, speseConvivenza - separationAdjustmentHousingUtilities)
3465
+ const speseConvivenzaEffettive = speseConvivenza > 0 ? speseConvivenza : null;
3466
+ const costoSeparazioneMensile = baseDuplicazione !== null
3467
+ ? Math.max(0, baseDuplicazione + separationAdjustmentHousingUtilities)
3465
3468
  : null;
3466
- const costoSeparazioneMensile = speseConvivenzaEffettive !== null ? (speseTot - speseConvivenzaEffettive) : null;
3467
3469
  const nettoInsiemeCombinato = speseConvivenzaEffettive !== null ? (r1 + r2 - speseConvivenzaEffettive) : null;
3468
- const nettoSeparatoTotale = post1 + post2;
3470
+ const nettoSeparatoTotale = (post1 + post2) - separationAdjustmentHousingUtilities;
3469
3471
  const perditaMensile = nettoInsiemeCombinato !== null ? nettoInsiemeCombinato - nettoSeparatoTotale : null;
3470
3472
  const perditaAnnua = perditaMensile !== null ? perditaMensile * 12 : null;
3471
3473
  const totReddito = Math.max(0.001, r1 + r2);
@@ -4673,24 +4675,42 @@ const defaultExpenseItems = [
4673
4675
  <strong class="sep-cost-value ${cls}">${formatted}</strong>
4674
4676
  </div>`;
4675
4677
  };
4678
+ const statHtml = (label, value, tone) => {
4679
+ const cls = lossClass(value);
4680
+ const formatted = value === null ? "&mdash;" : eur(value);
4681
+ return `<div class="sep-cost-stat ${tone || ""}">
4682
+ <span class="sep-cost-stat-label">${label}</span>
4683
+ <strong class="sep-cost-stat-value ${cls}">${formatted}</strong>
4684
+ </div>`;
4685
+ };
4686
+ const pillHtml = (label, value) => {
4687
+ const cls = lossClass(value);
4688
+ const formatted = value === null ? "&mdash;" : eur(value);
4689
+ return `<div class="sep-cost-pill">
4690
+ <span class="sep-cost-pill-label">${label}</span>
4691
+ <strong class="sep-cost-pill-value ${cls}">${formatted}</strong>
4692
+ </div>`;
4693
+ };
4676
4694
 
4677
4695
  panel.innerHTML = `
4678
4696
  <div class="sep-cost-panel">
4679
- <h3 class="sep-cost-title">${escapeHtml(tr("sepCostPanelTitle"))}</h3>
4680
- <div class="sep-cost-section">
4697
+ <h3 class="sep-cost-title"><span class="sep-cost-title-icon">&#128148;</span>${escapeHtml(tr("sepCostPanelTitle"))}</h3>
4698
+ <div class="sep-cost-hero">
4699
+ ${statHtml(tr("sepCostLossMonthly"), m.perditaMensile, "sep-cost-stat--primary")}
4700
+ ${statHtml(tr("sepCostLossAnnually"), m.perditaAnnua, "sep-cost-stat--secondary")}
4701
+ </div>
4702
+ <div class="sep-cost-section sep-cost-section--grid">
4681
4703
  ${rowHtml(tr("sepCostNetTogether"), m.nettoInsiemeCombinato, false)}
4682
4704
  ${rowHtml(tr("sepCostNetSeparated"), m.nettoSeparatoTotale, false)}
4683
- </div>
4684
- <div class="sep-cost-divider"></div>
4685
- <div class="sep-cost-section">
4686
4705
  ${m.separationAdjustmentHousingUtilities > 0
4687
4706
  ? rowHtml(tr("sepCostHousingUtilityAdj"), m.separationAdjustmentHousingUtilities, false)
4688
4707
  : ""}
4689
4708
  ${rowHtml(tr("sepCostDuplication"), m.costoSeparazioneMensile, false)}
4690
- ${rowHtml(tr("sepCostLossMonthly"), m.perditaMensile, true)}
4691
- ${rowHtml(tr("sepCostLossAnnually"), m.perditaAnnua, true)}
4692
- ${rowHtml(msg("sepCostLossSpouse", { spouse: c1NameEsc }), m.perditaSpouse1, false)}
4693
- ${rowHtml(msg("sepCostLossSpouse", { spouse: c2NameEsc }), m.perditaSpouse2, false)}
4709
+ </div>
4710
+ <div class="sep-cost-divider"></div>
4711
+ <div class="sep-cost-pill-wrap">
4712
+ ${pillHtml(msg("sepCostLossSpouse", { spouse: c1NameEsc }), m.perditaSpouse1)}
4713
+ ${pillHtml(msg("sepCostLossSpouse", { spouse: c2NameEsc }), m.perditaSpouse2)}
4694
4714
  </div>
4695
4715
  </div>
4696
4716
  `;
@@ -132,15 +132,16 @@ function calculateModel(input) {
132
132
  const housingUtility2 = c2Spese.reduce((acc, n, idx) => acc + (housingIdx.has(idx) ? toNumber(n) : 0), 0) + quotaMutuoSpese2;
133
133
  const housingUtilityNonColl = collocatario === 1 ? housingUtility2 : housingUtility1;
134
134
  const baseDuplicazione = speseConvivenza > 0 ? (speseTot - speseConvivenza) : null;
135
+ const HOUSING_UTILITY_ADJ_FACTOR = 0.35;
135
136
  const separationAdjustmentHousingUtilities = baseDuplicazione !== null && baseDuplicazione <= 0.005
136
- ? Math.max(0, housingUtilityNonColl)
137
+ ? Math.max(0, housingUtilityNonColl * HOUSING_UTILITY_ADJ_FACTOR)
137
138
  : 0;
138
- const speseConvivenzaEffettive = speseConvivenza > 0
139
- ? Math.max(0, speseConvivenza - separationAdjustmentHousingUtilities)
139
+ const speseConvivenzaEffettive = speseConvivenza > 0 ? speseConvivenza : null;
140
+ const costoSeparazioneMensile = baseDuplicazione !== null
141
+ ? Math.max(0, baseDuplicazione + separationAdjustmentHousingUtilities)
140
142
  : null;
141
- const costoSeparazioneMensile = speseConvivenzaEffettive !== null ? (speseTot - speseConvivenzaEffettive) : null;
142
143
  const nettoInsiemeCombinato = speseConvivenzaEffettive !== null ? (r1 + r2 - speseConvivenzaEffettive) : null;
143
- const nettoSeparatoTotale = post1 + post2;
144
+ const nettoSeparatoTotale = (post1 + post2) - separationAdjustmentHousingUtilities;
144
145
  const perditaMensile = nettoInsiemeCombinato !== null ? nettoInsiemeCombinato - nettoSeparatoTotale : null;
145
146
  const perditaAnnua = perditaMensile !== null ? perditaMensile * 12 : null;
146
147
  const totReddito = Math.max(0.001, r1 + r2);
@@ -3457,15 +3457,17 @@ const defaultExpenseItems = [
3457
3457
  const housingUtilityNonColl = collocatario === 1 ? housingUtility2 : housingUtility1;
3458
3458
 
3459
3459
  const baseDuplicazione = speseConvivenza > 0 ? (speseTot - speseConvivenza) : null;
3460
+ // OECD-modified equivalence logic suggests split-household extra burden is material but not full duplication.
3461
+ const HOUSING_UTILITY_ADJ_FACTOR = 0.35;
3460
3462
  const separationAdjustmentHousingUtilities = baseDuplicazione !== null && baseDuplicazione <= 0.005
3461
- ? Math.max(0, housingUtilityNonColl)
3463
+ ? Math.max(0, housingUtilityNonColl * HOUSING_UTILITY_ADJ_FACTOR)
3462
3464
  : 0;
3463
- const speseConvivenzaEffettive = speseConvivenza > 0
3464
- ? Math.max(0, speseConvivenza - separationAdjustmentHousingUtilities)
3465
+ const speseConvivenzaEffettive = speseConvivenza > 0 ? speseConvivenza : null;
3466
+ const costoSeparazioneMensile = baseDuplicazione !== null
3467
+ ? Math.max(0, baseDuplicazione + separationAdjustmentHousingUtilities)
3465
3468
  : null;
3466
- const costoSeparazioneMensile = speseConvivenzaEffettive !== null ? (speseTot - speseConvivenzaEffettive) : null;
3467
3469
  const nettoInsiemeCombinato = speseConvivenzaEffettive !== null ? (r1 + r2 - speseConvivenzaEffettive) : null;
3468
- const nettoSeparatoTotale = post1 + post2;
3470
+ const nettoSeparatoTotale = (post1 + post2) - separationAdjustmentHousingUtilities;
3469
3471
  const perditaMensile = nettoInsiemeCombinato !== null ? nettoInsiemeCombinato - nettoSeparatoTotale : null;
3470
3472
  const perditaAnnua = perditaMensile !== null ? perditaMensile * 12 : null;
3471
3473
  const totReddito = Math.max(0.001, r1 + r2);
@@ -4673,24 +4675,42 @@ const defaultExpenseItems = [
4673
4675
  <strong class="sep-cost-value ${cls}">${formatted}</strong>
4674
4676
  </div>`;
4675
4677
  };
4678
+ const statHtml = (label, value, tone) => {
4679
+ const cls = lossClass(value);
4680
+ const formatted = value === null ? "&mdash;" : eur(value);
4681
+ return `<div class="sep-cost-stat ${tone || ""}">
4682
+ <span class="sep-cost-stat-label">${label}</span>
4683
+ <strong class="sep-cost-stat-value ${cls}">${formatted}</strong>
4684
+ </div>`;
4685
+ };
4686
+ const pillHtml = (label, value) => {
4687
+ const cls = lossClass(value);
4688
+ const formatted = value === null ? "&mdash;" : eur(value);
4689
+ return `<div class="sep-cost-pill">
4690
+ <span class="sep-cost-pill-label">${label}</span>
4691
+ <strong class="sep-cost-pill-value ${cls}">${formatted}</strong>
4692
+ </div>`;
4693
+ };
4676
4694
 
4677
4695
  panel.innerHTML = `
4678
4696
  <div class="sep-cost-panel">
4679
- <h3 class="sep-cost-title">${escapeHtml(tr("sepCostPanelTitle"))}</h3>
4680
- <div class="sep-cost-section">
4697
+ <h3 class="sep-cost-title"><span class="sep-cost-title-icon">&#128148;</span>${escapeHtml(tr("sepCostPanelTitle"))}</h3>
4698
+ <div class="sep-cost-hero">
4699
+ ${statHtml(tr("sepCostLossMonthly"), m.perditaMensile, "sep-cost-stat--primary")}
4700
+ ${statHtml(tr("sepCostLossAnnually"), m.perditaAnnua, "sep-cost-stat--secondary")}
4701
+ </div>
4702
+ <div class="sep-cost-section sep-cost-section--grid">
4681
4703
  ${rowHtml(tr("sepCostNetTogether"), m.nettoInsiemeCombinato, false)}
4682
4704
  ${rowHtml(tr("sepCostNetSeparated"), m.nettoSeparatoTotale, false)}
4683
- </div>
4684
- <div class="sep-cost-divider"></div>
4685
- <div class="sep-cost-section">
4686
4705
  ${m.separationAdjustmentHousingUtilities > 0
4687
4706
  ? rowHtml(tr("sepCostHousingUtilityAdj"), m.separationAdjustmentHousingUtilities, false)
4688
4707
  : ""}
4689
4708
  ${rowHtml(tr("sepCostDuplication"), m.costoSeparazioneMensile, false)}
4690
- ${rowHtml(tr("sepCostLossMonthly"), m.perditaMensile, true)}
4691
- ${rowHtml(tr("sepCostLossAnnually"), m.perditaAnnua, true)}
4692
- ${rowHtml(msg("sepCostLossSpouse", { spouse: c1NameEsc }), m.perditaSpouse1, false)}
4693
- ${rowHtml(msg("sepCostLossSpouse", { spouse: c2NameEsc }), m.perditaSpouse2, false)}
4709
+ </div>
4710
+ <div class="sep-cost-divider"></div>
4711
+ <div class="sep-cost-pill-wrap">
4712
+ ${pillHtml(msg("sepCostLossSpouse", { spouse: c1NameEsc }), m.perditaSpouse1)}
4713
+ ${pillHtml(msg("sepCostLossSpouse", { spouse: c2NameEsc }), m.perditaSpouse2)}
4694
4714
  </div>
4695
4715
  </div>
4696
4716
  `;
@@ -599,7 +599,7 @@
599
599
  <script src="supabase.min.js"></script>
600
600
  <script src="fabric.min.js"></script>
601
601
  <script src="html2pdf.bundle.min.js"></script>
602
- <script src="app.js?v=2.2.5"></script>
602
+ <script src="app.js?v=2.2.7"></script>
603
603
  </body>
604
604
  </html>
605
605
 
@@ -1847,19 +1847,78 @@
1847
1847
 
1848
1848
  .sep-cost-panel {
1849
1849
  margin-top: 10px;
1850
- border-radius: 14px;
1850
+ border-radius: 16px;
1851
1851
  border: 1.5px solid #e2a5a5;
1852
- background: linear-gradient(145deg, #fff5f3, #ffeceb 52%, #fff6f0);
1853
- box-shadow: 0 8px 22px rgba(130, 33, 33, 0.12);
1854
- padding: 12px;
1852
+ background:
1853
+ radial-gradient(120% 180% at 0% -20%, rgba(255, 241, 230, 0.75), transparent 58%),
1854
+ radial-gradient(120% 180% at 100% -20%, rgba(255, 228, 228, 0.72), transparent 56%),
1855
+ linear-gradient(145deg, #fff5f3, #ffeceb 52%, #fff6f0);
1856
+ box-shadow: 0 10px 24px rgba(130, 33, 33, 0.14);
1857
+ padding: 14px;
1855
1858
  }
1856
1859
 
1857
1860
  .sep-cost-title {
1858
- margin: 0 0 8px;
1859
- font-size: 0.98rem;
1861
+ margin: 0 0 10px;
1862
+ font-size: 1.01rem;
1860
1863
  font-weight: 900;
1861
1864
  color: #842d2d;
1862
1865
  letter-spacing: 0.2px;
1866
+ display: flex;
1867
+ align-items: center;
1868
+ gap: 8px;
1869
+ }
1870
+
1871
+ .sep-cost-title-icon {
1872
+ width: 24px;
1873
+ height: 24px;
1874
+ border-radius: 999px;
1875
+ background: rgba(140, 32, 32, 0.12);
1876
+ display: inline-flex;
1877
+ align-items: center;
1878
+ justify-content: center;
1879
+ font-size: 0.85rem;
1880
+ }
1881
+
1882
+ .sep-cost-hero {
1883
+ display: grid;
1884
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1885
+ gap: 8px;
1886
+ margin-bottom: 9px;
1887
+ }
1888
+
1889
+ .sep-cost-stat {
1890
+ border-radius: 12px;
1891
+ border: 1px solid rgba(138, 44, 44, 0.16);
1892
+ background: rgba(255,255,255,0.82);
1893
+ padding: 8px 10px;
1894
+ display: grid;
1895
+ gap: 2px;
1896
+ min-width: 0;
1897
+ }
1898
+
1899
+ .sep-cost-stat--primary {
1900
+ background: linear-gradient(130deg, rgba(255, 233, 232, 0.95), rgba(255, 244, 232, 0.92));
1901
+ border-color: rgba(170, 67, 46, 0.35);
1902
+ }
1903
+
1904
+ .sep-cost-stat--secondary {
1905
+ background: linear-gradient(130deg, rgba(255, 245, 240, 0.95), rgba(255, 235, 231, 0.92));
1906
+ border-color: rgba(164, 61, 61, 0.28);
1907
+ }
1908
+
1909
+ .sep-cost-stat-label {
1910
+ font-size: 0.72rem;
1911
+ font-weight: 800;
1912
+ color: #7a3d3d;
1913
+ text-transform: uppercase;
1914
+ letter-spacing: 0.04em;
1915
+ }
1916
+
1917
+ .sep-cost-stat-value {
1918
+ font-size: 1.12rem;
1919
+ font-weight: 900;
1920
+ font-variant-numeric: tabular-nums;
1921
+ white-space: nowrap;
1863
1922
  }
1864
1923
 
1865
1924
  .sep-cost-section {
@@ -1867,6 +1926,10 @@
1867
1926
  gap: 7px;
1868
1927
  }
1869
1928
 
1929
+ .sep-cost-section--grid {
1930
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1931
+ }
1932
+
1870
1933
  .sep-cost-divider {
1871
1934
  height: 1px;
1872
1935
  margin: 8px 0;
@@ -1893,6 +1956,7 @@
1893
1956
  font-size: 0.82rem;
1894
1957
  color: #6a3939;
1895
1958
  font-weight: 700;
1959
+ min-width: 0;
1896
1960
  }
1897
1961
 
1898
1962
  .sep-cost-value {
@@ -1903,6 +1967,47 @@
1903
1967
  color: #5d2e2e;
1904
1968
  }
1905
1969
 
1970
+ .sep-cost-pill-wrap {
1971
+ display: grid;
1972
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1973
+ gap: 8px;
1974
+ }
1975
+
1976
+ .sep-cost-pill {
1977
+ border: 1px solid rgba(134, 40, 40, 0.18);
1978
+ border-radius: 11px;
1979
+ background: rgba(255,255,255,0.82);
1980
+ padding: 8px 10px;
1981
+ display: grid;
1982
+ gap: 2px;
1983
+ }
1984
+
1985
+ .sep-cost-pill-label {
1986
+ font-size: 0.76rem;
1987
+ color: #714343;
1988
+ font-weight: 700;
1989
+ line-height: 1.2;
1990
+ }
1991
+
1992
+ .sep-cost-pill-value {
1993
+ font-size: 1rem;
1994
+ font-weight: 900;
1995
+ font-variant-numeric: tabular-nums;
1996
+ white-space: nowrap;
1997
+ }
1998
+
1999
+ @media (max-width: 720px) {
2000
+ .sep-cost-hero,
2001
+ .sep-cost-section--grid,
2002
+ .sep-cost-pill-wrap {
2003
+ grid-template-columns: 1fr;
2004
+ }
2005
+ .sep-cost-stat-value,
2006
+ .sep-cost-pill-value {
2007
+ white-space: normal;
2008
+ }
2009
+ }
2010
+
1906
2011
  .sep-loss-negative {
1907
2012
  color: #9b1f1f;
1908
2013
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mantenimento-app",
3
- "version": "2.2.5",
3
+ "version": "2.2.7",
4
4
  "description": "Frontend + backend architecture for the mantenimento calculator",
5
5
  "type": "commonjs",
6
6
  "main": "backend/calculate-model.js",