mantenimento-app 2.1.3 → 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." },
@@ -3244,14 +3243,18 @@ const defaultExpenseItems = [
3244
3243
  const rawMutuoPerc1 = payload.primaCasaMutuoPerc1 === undefined ? 50 : payload.primaCasaMutuoPerc1;
3245
3244
  const primaCasaMutuoPerc1 = Math.min(100, Math.max(0, Number(rawMutuoPerc1 || 0)));
3246
3245
  const primaCasaMutuoPerc2 = 100 - primaCasaMutuoPerc1;
3246
+ const quotaMutuoSpese1 = primaCasaMutuoEnabled ? (primaCasaMutuoImporto * (primaCasaMutuoPerc1 / 100)) : 0;
3247
+ const quotaMutuoSpese2 = primaCasaMutuoEnabled ? (primaCasaMutuoImporto - quotaMutuoSpese1) : 0;
3247
3248
 
3248
3249
  const match12 = Math.min(aPag1, aPerc2);
3249
3250
  const match21 = Math.min(aPag2, aPerc1);
3250
3251
  const esternoPag1 = Math.max(0, aPag1 - match12);
3251
3252
  const esternoPag2 = Math.max(0, aPag2 - match21);
3252
3253
 
3253
- const spese1 = (payload.c1Spese || []).reduce((acc, v) => acc + Number(v || 0), 0);
3254
- 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;
3255
3258
  const speseTot = spese1 + spese2;
3256
3259
 
3257
3260
  const disp1 = r1 + aPerc1 + aFam1 - aPag1 - spese1;
@@ -3301,12 +3304,10 @@ const defaultExpenseItems = [
3301
3304
  let primaCasaTransfer1to2 = 0;
3302
3305
  let primaCasaTransfer2to1 = 0;
3303
3306
  if (primaCasaConsidered) {
3304
- const quotaMutuo1 = primaCasaMutuoImporto * (primaCasaMutuoPerc1 / 100);
3305
- const quotaMutuo2 = primaCasaMutuoImporto - quotaMutuo1;
3306
3307
  if (primaCasaAssegnataA === "1") {
3307
- primaCasaTransfer2to1 = Math.max(0, quotaMutuo2);
3308
+ primaCasaTransfer2to1 = Math.max(0, quotaMutuoSpese2);
3308
3309
  } else if (primaCasaAssegnataA === "2") {
3309
- primaCasaTransfer1to2 = Math.max(0, quotaMutuo1);
3310
+ primaCasaTransfer1to2 = Math.max(0, quotaMutuoSpese1);
3310
3311
  }
3311
3312
  }
3312
3313
 
@@ -3326,6 +3327,7 @@ const defaultExpenseItems = [
3326
3327
  r1, r2, r1Raw, r2Raw, incomeMode, figli, perm1, perm2,
3327
3328
  aPerc1, aPag1, aPerc2, aPag2, aFam1, aFam2,
3328
3329
  match12, match21, esternoPag1, esternoPag2,
3330
+ speseBase1, speseBase2, quotaMutuoSpese1, quotaMutuoSpese2,
3329
3331
  spese1, spese2, speseTot,
3330
3332
  disp1, disp2, peso1, peso2,
3331
3333
  mode, simplePerc,
@@ -3446,6 +3448,12 @@ const defaultExpenseItems = [
3446
3448
  const assignedEl = document.getElementById("primaCasaAssegnataA");
3447
3449
  const shareEl = document.getElementById("primaCasaMutuoPerc1");
3448
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");
3449
3457
  const splitLabelEl = document.getElementById("lblPrimaCasaMutuoPerc1");
3450
3458
  const splitHintEl = document.getElementById("hintPrimaCasaMutuoPerc1");
3451
3459
  if (!enabledEl || !amountEl || !assignedEl || !shareEl) return;
@@ -3454,6 +3462,7 @@ const defaultExpenseItems = [
3454
3462
  amountEl.disabled = !isEnabled;
3455
3463
  assignedEl.disabled = !isEnabled;
3456
3464
  shareEl.disabled = !isEnabled;
3465
+ if (splitWrapEl) splitWrapEl.classList.toggle("is-disabled", !isEnabled);
3457
3466
 
3458
3467
  const normalizedShare1 = Math.min(100, Math.max(0, num("primaCasaMutuoPerc1")));
3459
3468
  if (Math.abs(normalizedShare1 - Number(shareEl.value || 0)) > 0.0001) {
@@ -3461,6 +3470,9 @@ const defaultExpenseItems = [
3461
3470
  }
3462
3471
 
3463
3472
  const share2 = 100 - normalizedShare1;
3473
+ const amount = Math.max(0, num("primaCasaMutuoImporto"));
3474
+ const quota1 = amount * (normalizedShare1 / 100);
3475
+ const quota2 = amount - quota1;
3464
3476
  if (splitLabelEl) splitLabelEl.textContent = msg("firstHomeSplitLabel", { spouse: c1n() });
3465
3477
  if (splitHintEl) splitHintEl.title = msg("firstHomeSplitHint", { spouse: c1n() });
3466
3478
  if (splitInfoEl) {
@@ -3471,6 +3483,11 @@ const defaultExpenseItems = [
3471
3483
  p2: share2.toFixed(0)
3472
3484
  });
3473
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);
3474
3491
 
3475
3492
  const noneOpt = assignedEl.querySelector("option[value='']");
3476
3493
  const spouse1Opt = assignedEl.querySelector("option[value='1']");
@@ -4335,17 +4352,33 @@ const defaultExpenseItems = [
4335
4352
  ${m.incomeMode === "cu" ? `<br /><strong>${tr("calcIncomeBaseNote")}</strong> ${tr("cuNetNoteText")}` : ""}
4336
4353
  `;
4337
4354
 
4338
- let mainText = tr("calcNoTransferSuggested");
4355
+ let mainHtml = `<span class="result-main-line">${escapeHtml(tr("calcNoTransferSuggested"))}</span>`;
4339
4356
  if (m.assegnoDa1a2 > 0.005) {
4340
- 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
+ `;
4341
4361
  } else if (m.assegnoDa2a1 > 0.005) {
4342
- 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
+ `;
4343
4366
  } else {
4344
- if (benefitsInline) {
4345
- mainText = msg("calcNoTransferWithBenefits", { benefits: benefitsInline });
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>`;
4346
4379
  }
4347
4380
  }
4348
- resultMain.textContent = mainText;
4381
+ resultMain.innerHTML = mainHtml;
4349
4382
 
4350
4383
  kpi.innerHTML = "";
4351
4384
 
@@ -4520,7 +4553,16 @@ const defaultExpenseItems = [
4520
4553
  <td class="expense-detail-cell">–</td>
4521
4554
  </tr>`
4522
4555
  : "";
4523
- 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;
4524
4566
 
4525
4567
  const scenarioMetrics = [
4526
4568
  { label: tr("scenarioColMode"), val: (sm) => getModeName(sm.mode, sm.simplePerc), fmt: (v) => escapeHtml(v), numeric: false },
@@ -5319,7 +5361,7 @@ ${scenarioLab.length ? `
5319
5361
  }
5320
5362
 
5321
5363
  function resetAll() {
5322
- 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) => {
5323
5365
  if (el.id !== "perm2") {
5324
5366
  el.value = el.defaultValue || 0;
5325
5367
  }
@@ -5580,11 +5622,13 @@ ${scenarioLab.length ? `
5580
5622
  }
5581
5623
 
5582
5624
  document.addEventListener("input", (e) => {
5583
- 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']")) {
5584
5626
  if (e.target.id === "perm1") {
5585
5627
  syncPermanenza("perm1");
5586
5628
  } else if (e.target.id === "perm2") {
5587
5629
  syncPermanenza("perm2");
5630
+ } else if (e.target.id === "primaCasaMutuoPerc1" || e.target.id === "primaCasaMutuoImporto") {
5631
+ updateFirstHomeMortgageUi();
5588
5632
  } else if (e.target.id === "reddito1" || e.target.id === "reddito2") {
5589
5633
  const activeMode = document.getElementById("incomeMode")?.value || "monthly";
5590
5634
  incomeValuesByMode[activeMode] = {
@@ -38,6 +38,8 @@ function calculateModel(input) {
38
38
  const rawMutuoPerc1 = input.primaCasaMutuoPerc1 === undefined ? 50 : input.primaCasaMutuoPerc1;
39
39
  const primaCasaMutuoPerc1 = clamp(toNumber(rawMutuoPerc1), 0, 100);
40
40
  const primaCasaMutuoPerc2 = 100 - primaCasaMutuoPerc1;
41
+ const quotaMutuoSpese1 = primaCasaMutuoEnabled ? (primaCasaMutuoImporto * (primaCasaMutuoPerc1 / 100)) : 0;
42
+ const quotaMutuoSpese2 = primaCasaMutuoEnabled ? (primaCasaMutuoImporto - quotaMutuoSpese1) : 0;
41
43
 
42
44
  const match12 = Math.min(aPag1, aPerc2);
43
45
  const match21 = Math.min(aPag2, aPerc1);
@@ -46,8 +48,10 @@ function calculateModel(input) {
46
48
 
47
49
  const c1Spese = Array.isArray(input.c1Spese) ? input.c1Spese : [];
48
50
  const c2Spese = Array.isArray(input.c2Spese) ? input.c2Spese : [];
49
- const spese1 = c1Spese.reduce((acc, n) => acc + toNumber(n), 0);
50
- const spese2 = c2Spese.reduce((acc, n) => acc + toNumber(n), 0);
51
+ const speseBase1 = c1Spese.reduce((acc, n) => acc + toNumber(n), 0);
52
+ const speseBase2 = c2Spese.reduce((acc, n) => acc + toNumber(n), 0);
53
+ const spese1 = speseBase1 + quotaMutuoSpese1;
54
+ const spese2 = speseBase2 + quotaMutuoSpese2;
51
55
  const speseTot = spese1 + spese2;
52
56
 
53
57
  const disp1 = r1 + aPerc1 + aFam1 - aPag1 - spese1;
@@ -102,12 +106,10 @@ function calculateModel(input) {
102
106
  let primaCasaTransfer1to2 = 0;
103
107
  let primaCasaTransfer2to1 = 0;
104
108
  if (primaCasaConsidered) {
105
- const quotaMutuo1 = primaCasaMutuoImporto * (primaCasaMutuoPerc1 / 100);
106
- const quotaMutuo2 = primaCasaMutuoImporto - quotaMutuo1;
107
109
  if (assigned === '1') {
108
- primaCasaTransfer2to1 = Math.max(0, quotaMutuo2);
110
+ primaCasaTransfer2to1 = Math.max(0, quotaMutuoSpese2);
109
111
  } else if (assigned === '2') {
110
- primaCasaTransfer1to2 = Math.max(0, quotaMutuo1);
112
+ primaCasaTransfer1to2 = Math.max(0, quotaMutuoSpese1);
111
113
  }
112
114
  }
113
115
 
@@ -127,6 +129,7 @@ function calculateModel(input) {
127
129
  r1, r2, r1Raw, r2Raw, incomeMode, figli, perm1, perm2,
128
130
  aPerc1, aPag1, aPerc2, aPag2, aFam1, aFam2,
129
131
  match12, match21, esternoPag1, esternoPag2,
132
+ speseBase1, speseBase2, quotaMutuoSpese1, quotaMutuoSpese2,
130
133
  spese1, spese2, speseTot,
131
134
  disp1, disp2, peso1, peso2,
132
135
  mode, simplePerc,
@@ -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." },
@@ -3244,14 +3243,18 @@ const defaultExpenseItems = [
3244
3243
  const rawMutuoPerc1 = payload.primaCasaMutuoPerc1 === undefined ? 50 : payload.primaCasaMutuoPerc1;
3245
3244
  const primaCasaMutuoPerc1 = Math.min(100, Math.max(0, Number(rawMutuoPerc1 || 0)));
3246
3245
  const primaCasaMutuoPerc2 = 100 - primaCasaMutuoPerc1;
3246
+ const quotaMutuoSpese1 = primaCasaMutuoEnabled ? (primaCasaMutuoImporto * (primaCasaMutuoPerc1 / 100)) : 0;
3247
+ const quotaMutuoSpese2 = primaCasaMutuoEnabled ? (primaCasaMutuoImporto - quotaMutuoSpese1) : 0;
3247
3248
 
3248
3249
  const match12 = Math.min(aPag1, aPerc2);
3249
3250
  const match21 = Math.min(aPag2, aPerc1);
3250
3251
  const esternoPag1 = Math.max(0, aPag1 - match12);
3251
3252
  const esternoPag2 = Math.max(0, aPag2 - match21);
3252
3253
 
3253
- const spese1 = (payload.c1Spese || []).reduce((acc, v) => acc + Number(v || 0), 0);
3254
- 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;
3255
3258
  const speseTot = spese1 + spese2;
3256
3259
 
3257
3260
  const disp1 = r1 + aPerc1 + aFam1 - aPag1 - spese1;
@@ -3301,12 +3304,10 @@ const defaultExpenseItems = [
3301
3304
  let primaCasaTransfer1to2 = 0;
3302
3305
  let primaCasaTransfer2to1 = 0;
3303
3306
  if (primaCasaConsidered) {
3304
- const quotaMutuo1 = primaCasaMutuoImporto * (primaCasaMutuoPerc1 / 100);
3305
- const quotaMutuo2 = primaCasaMutuoImporto - quotaMutuo1;
3306
3307
  if (primaCasaAssegnataA === "1") {
3307
- primaCasaTransfer2to1 = Math.max(0, quotaMutuo2);
3308
+ primaCasaTransfer2to1 = Math.max(0, quotaMutuoSpese2);
3308
3309
  } else if (primaCasaAssegnataA === "2") {
3309
- primaCasaTransfer1to2 = Math.max(0, quotaMutuo1);
3310
+ primaCasaTransfer1to2 = Math.max(0, quotaMutuoSpese1);
3310
3311
  }
3311
3312
  }
3312
3313
 
@@ -3326,6 +3327,7 @@ const defaultExpenseItems = [
3326
3327
  r1, r2, r1Raw, r2Raw, incomeMode, figli, perm1, perm2,
3327
3328
  aPerc1, aPag1, aPerc2, aPag2, aFam1, aFam2,
3328
3329
  match12, match21, esternoPag1, esternoPag2,
3330
+ speseBase1, speseBase2, quotaMutuoSpese1, quotaMutuoSpese2,
3329
3331
  spese1, spese2, speseTot,
3330
3332
  disp1, disp2, peso1, peso2,
3331
3333
  mode, simplePerc,
@@ -3446,6 +3448,12 @@ const defaultExpenseItems = [
3446
3448
  const assignedEl = document.getElementById("primaCasaAssegnataA");
3447
3449
  const shareEl = document.getElementById("primaCasaMutuoPerc1");
3448
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");
3449
3457
  const splitLabelEl = document.getElementById("lblPrimaCasaMutuoPerc1");
3450
3458
  const splitHintEl = document.getElementById("hintPrimaCasaMutuoPerc1");
3451
3459
  if (!enabledEl || !amountEl || !assignedEl || !shareEl) return;
@@ -3454,6 +3462,7 @@ const defaultExpenseItems = [
3454
3462
  amountEl.disabled = !isEnabled;
3455
3463
  assignedEl.disabled = !isEnabled;
3456
3464
  shareEl.disabled = !isEnabled;
3465
+ if (splitWrapEl) splitWrapEl.classList.toggle("is-disabled", !isEnabled);
3457
3466
 
3458
3467
  const normalizedShare1 = Math.min(100, Math.max(0, num("primaCasaMutuoPerc1")));
3459
3468
  if (Math.abs(normalizedShare1 - Number(shareEl.value || 0)) > 0.0001) {
@@ -3461,6 +3470,9 @@ const defaultExpenseItems = [
3461
3470
  }
3462
3471
 
3463
3472
  const share2 = 100 - normalizedShare1;
3473
+ const amount = Math.max(0, num("primaCasaMutuoImporto"));
3474
+ const quota1 = amount * (normalizedShare1 / 100);
3475
+ const quota2 = amount - quota1;
3464
3476
  if (splitLabelEl) splitLabelEl.textContent = msg("firstHomeSplitLabel", { spouse: c1n() });
3465
3477
  if (splitHintEl) splitHintEl.title = msg("firstHomeSplitHint", { spouse: c1n() });
3466
3478
  if (splitInfoEl) {
@@ -3471,6 +3483,11 @@ const defaultExpenseItems = [
3471
3483
  p2: share2.toFixed(0)
3472
3484
  });
3473
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);
3474
3491
 
3475
3492
  const noneOpt = assignedEl.querySelector("option[value='']");
3476
3493
  const spouse1Opt = assignedEl.querySelector("option[value='1']");
@@ -4335,17 +4352,33 @@ const defaultExpenseItems = [
4335
4352
  ${m.incomeMode === "cu" ? `<br /><strong>${tr("calcIncomeBaseNote")}</strong> ${tr("cuNetNoteText")}` : ""}
4336
4353
  `;
4337
4354
 
4338
- let mainText = tr("calcNoTransferSuggested");
4355
+ let mainHtml = `<span class="result-main-line">${escapeHtml(tr("calcNoTransferSuggested"))}</span>`;
4339
4356
  if (m.assegnoDa1a2 > 0.005) {
4340
- 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
+ `;
4341
4361
  } else if (m.assegnoDa2a1 > 0.005) {
4342
- 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
+ `;
4343
4366
  } else {
4344
- if (benefitsInline) {
4345
- mainText = msg("calcNoTransferWithBenefits", { benefits: benefitsInline });
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>`;
4346
4379
  }
4347
4380
  }
4348
- resultMain.textContent = mainText;
4381
+ resultMain.innerHTML = mainHtml;
4349
4382
 
4350
4383
  kpi.innerHTML = "";
4351
4384
 
@@ -4520,7 +4553,16 @@ const defaultExpenseItems = [
4520
4553
  <td class="expense-detail-cell">–</td>
4521
4554
  </tr>`
4522
4555
  : "";
4523
- 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;
4524
4566
 
4525
4567
  const scenarioMetrics = [
4526
4568
  { label: tr("scenarioColMode"), val: (sm) => getModeName(sm.mode, sm.simplePerc), fmt: (v) => escapeHtml(v), numeric: false },
@@ -5319,7 +5361,7 @@ ${scenarioLab.length ? `
5319
5361
  }
5320
5362
 
5321
5363
  function resetAll() {
5322
- 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) => {
5323
5365
  if (el.id !== "perm2") {
5324
5366
  el.value = el.defaultValue || 0;
5325
5367
  }
@@ -5580,11 +5622,13 @@ ${scenarioLab.length ? `
5580
5622
  }
5581
5623
 
5582
5624
  document.addEventListener("input", (e) => {
5583
- 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']")) {
5584
5626
  if (e.target.id === "perm1") {
5585
5627
  syncPermanenza("perm1");
5586
5628
  } else if (e.target.id === "perm2") {
5587
5629
  syncPermanenza("perm2");
5630
+ } else if (e.target.id === "primaCasaMutuoPerc1" || e.target.id === "primaCasaMutuoImporto") {
5631
+ updateFirstHomeMortgageUi();
5588
5632
  } else if (e.target.id === "reddito1" || e.target.id === "reddito2") {
5589
5633
  const activeMode = document.getElementById("incomeMode")?.value || "monthly";
5590
5634
  incomeValuesByMode[activeMode] = {
@@ -377,7 +377,20 @@
377
377
  <label for="primaCasaMutuoPerc1" class="label-row"><span id="lblPrimaCasaMutuoPerc1">Quota mutuo a carico Coniuge 1 (%)</span>
378
378
  <span class="hint" id="hintPrimaCasaMutuoPerc1" title="Percentuale della rata mutuo pagata da Coniuge 1. La quota di Coniuge 2 e complementare a 100%.">i</span>
379
379
  </label>
380
- <input id="primaCasaMutuoPerc1" type="number" min="0" max="100" step="1" value="50" />
380
+ <div class="mortgage-split-slider" id="primaCasaMutuoSliderWrap">
381
+ <div class="mortgage-split-side mortgage-split-side-left" id="primaCasaSplitLeft">
382
+ <div class="mortgage-split-name" id="primaCasaSplitLeftName">Coniuge 1</div>
383
+ <div class="mortgage-split-amount" id="primaCasaSplitLeftAmount">0 EUR</div>
384
+ </div>
385
+ <div class="mortgage-split-range-wrap">
386
+ <input id="primaCasaMutuoPerc1" type="range" min="0" max="100" step="1" value="50" />
387
+ <div class="mortgage-split-center" id="primaCasaSplitCenter">50% / 50%</div>
388
+ </div>
389
+ <div class="mortgage-split-side mortgage-split-side-right" id="primaCasaSplitRight">
390
+ <div class="mortgage-split-name" id="primaCasaSplitRightName">Coniuge 2</div>
391
+ <div class="mortgage-split-amount" id="primaCasaSplitRightAmount">0 EUR</div>
392
+ </div>
393
+ </div>
381
394
  <div class="extra-monthly" id="primaCasaMutuoSplitInfo">Ripartizione mutuo: Coniuge 1 50% · Coniuge 2 50%</div>
382
395
  </div>
383
396
  </div>
@@ -563,6 +576,6 @@
563
576
  <script src="supabase.min.js"></script>
564
577
  <script src="fabric.min.js"></script>
565
578
  <script src="html2pdf.bundle.min.js"></script>
566
- <script src="app.js?v=2.1.3"></script>
579
+ <script src="app.js?v=2.1.4"></script>
567
580
  </body>
568
581
  </html>
@@ -80,6 +80,37 @@
80
80
  gap: 10px;
81
81
  }
82
82
 
83
+ .runtime-badge {
84
+ display: inline-flex;
85
+ align-items: center;
86
+ gap: 6px;
87
+ margin-right: 2px;
88
+ }
89
+
90
+ .runtime-badge-chip {
91
+ display: inline-flex;
92
+ align-items: center;
93
+ min-height: 30px;
94
+ padding: 5px 10px;
95
+ border-radius: 999px;
96
+ font-size: 0.72rem;
97
+ font-weight: 800;
98
+ letter-spacing: 0.05em;
99
+ text-transform: uppercase;
100
+ border: 1px solid rgba(255,255,255,0.28);
101
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.18);
102
+ }
103
+
104
+ .runtime-badge-chip--frontend {
105
+ color: #fff6da;
106
+ background: linear-gradient(135deg, rgba(145, 77, 7, 0.92), rgba(201, 118, 18, 0.92));
107
+ }
108
+
109
+ .runtime-badge-chip--api {
110
+ color: #f4fffb;
111
+ background: linear-gradient(135deg, rgba(17, 101, 92, 0.95), rgba(29, 146, 128, 0.95));
112
+ }
113
+
83
114
  .top-actions {
84
115
  position: relative;
85
116
  display: inline-block;
@@ -819,6 +850,113 @@
819
850
  font-weight: 700;
820
851
  color: #184a44;
821
852
  }
853
+
854
+ .mortgage-split-slider {
855
+ margin-top: 6px;
856
+ border: 1px solid #b9d6cf;
857
+ border-radius: 14px;
858
+ background:
859
+ radial-gradient(120% 160% at 0% 0%, rgba(229, 247, 242, 0.8), transparent 58%),
860
+ radial-gradient(120% 160% at 100% 0%, rgba(255, 241, 214, 0.8), transparent 58%),
861
+ linear-gradient(180deg, #fafdfc, #eef7f5);
862
+ padding: 10px;
863
+ display: grid;
864
+ grid-template-columns: minmax(0, 1fr) minmax(180px, 2fr) minmax(0, 1fr);
865
+ gap: 8px;
866
+ align-items: center;
867
+ transition: opacity 0.2s ease;
868
+ }
869
+
870
+ .mortgage-split-slider.is-disabled {
871
+ opacity: 0.58;
872
+ }
873
+
874
+ .mortgage-split-side {
875
+ border: 1px solid #c6ded8;
876
+ border-radius: 11px;
877
+ padding: 8px;
878
+ background: rgba(255, 255, 255, 0.84);
879
+ min-height: 66px;
880
+ display: grid;
881
+ align-content: center;
882
+ gap: 2px;
883
+ }
884
+
885
+ .mortgage-split-side-left {
886
+ box-shadow: inset 3px 0 0 #1b8d7f;
887
+ }
888
+
889
+ .mortgage-split-side-right {
890
+ box-shadow: inset -3px 0 0 #d89a35;
891
+ text-align: right;
892
+ }
893
+
894
+ .mortgage-split-name {
895
+ font-size: 0.76rem;
896
+ font-weight: 800;
897
+ color: #2a4a46;
898
+ overflow-wrap: anywhere;
899
+ }
900
+
901
+ .mortgage-split-amount {
902
+ font-size: 0.95rem;
903
+ font-weight: 900;
904
+ color: #124f48;
905
+ letter-spacing: 0.2px;
906
+ }
907
+
908
+ .mortgage-split-range-wrap {
909
+ position: relative;
910
+ padding: 20px 0 10px;
911
+ }
912
+
913
+ .mortgage-split-range-wrap input[type="range"] {
914
+ -webkit-appearance: none;
915
+ appearance: none;
916
+ width: 100%;
917
+ height: 8px;
918
+ border-radius: 999px;
919
+ background: linear-gradient(90deg, #2b9d8e 0%, #6cb9a9 46%, #dfb264 54%, #cb8a2d 100%);
920
+ outline: none;
921
+ margin: 0;
922
+ }
923
+
924
+ .mortgage-split-range-wrap input[type="range"]::-webkit-slider-thumb {
925
+ -webkit-appearance: none;
926
+ appearance: none;
927
+ width: 22px;
928
+ height: 22px;
929
+ border-radius: 50%;
930
+ border: 2px solid #ffffff;
931
+ background: radial-gradient(circle at 35% 30%, #ffffff 0%, #e8f8f5 42%, #177a6f 100%);
932
+ box-shadow: 0 3px 9px rgba(13, 70, 64, 0.35);
933
+ cursor: pointer;
934
+ }
935
+
936
+ .mortgage-split-range-wrap input[type="range"]::-moz-range-thumb {
937
+ width: 22px;
938
+ height: 22px;
939
+ border-radius: 50%;
940
+ border: 2px solid #ffffff;
941
+ background: radial-gradient(circle at 35% 30%, #ffffff 0%, #e8f8f5 42%, #177a6f 100%);
942
+ box-shadow: 0 3px 9px rgba(13, 70, 64, 0.35);
943
+ cursor: pointer;
944
+ }
945
+
946
+ .mortgage-split-center {
947
+ position: absolute;
948
+ top: -2px;
949
+ left: 50%;
950
+ transform: translateX(-50%);
951
+ font-size: 0.74rem;
952
+ font-weight: 900;
953
+ color: #114c45;
954
+ background: #ffffff;
955
+ border: 1px solid #b8d5ce;
956
+ border-radius: 999px;
957
+ padding: 2px 8px;
958
+ white-space: nowrap;
959
+ }
822
960
 
823
961
  .label-row {
824
962
  display: inline-flex;
@@ -1024,9 +1162,16 @@
1024
1162
  max-width: 110px;
1025
1163
  padding-right: 9px;
1026
1164
  border-color: #0b6e66;
1027
- color: #0b6e66;
1028
- background: #e8f6f2;
1165
+ color: #ffffff;
1166
+ background: linear-gradient(135deg, #0b6e66, #149a8f);
1029
1167
  font-weight: 700;
1168
+ box-shadow: 0 0 0 1px rgba(11, 110, 102, 0.28), 0 3px 8px rgba(11, 110, 102, 0.18);
1169
+ }
1170
+
1171
+ .spese-detail-btn.has-note::before {
1172
+ content: "✓";
1173
+ font-size: 12px;
1174
+ font-weight: 900;
1030
1175
  }
1031
1176
 
1032
1177
  .spese-detail-btn.has-note .spese-detail-label {
@@ -1040,7 +1185,9 @@
1040
1185
  .spese-detail-text {
1041
1186
  width: 100%;
1042
1187
  min-height: 52px;
1043
- resize: vertical;
1188
+ max-height: 178px;
1189
+ resize: none;
1190
+ overflow-y: hidden;
1044
1191
  border-radius: 8px;
1045
1192
  border: 1px solid #c8dad4;
1046
1193
  background: #f9fcfb;
@@ -1050,6 +1197,18 @@
1050
1197
  color: #204644;
1051
1198
  }
1052
1199
 
1200
+ .spese-detail-counter {
1201
+ margin-top: 3px;
1202
+ font-size: 0.66rem;
1203
+ color: #56716d;
1204
+ text-align: right;
1205
+ font-weight: 700;
1206
+ }
1207
+
1208
+ .spese-detail-counter.is-limit {
1209
+ color: #9d3c2f;
1210
+ }
1211
+
1053
1212
  .spese-partial {
1054
1213
  font-size: 0.66rem;
1055
1214
  color: #2f4745;
@@ -1220,6 +1379,299 @@
1220
1379
  border: 1px solid #c6cdc3;
1221
1380
  }
1222
1381
 
1382
+ /* ── Scenario Lab ─────────────────────────────────────────────── */
1383
+ .scenario-lab-card {
1384
+ margin-top: 14px;
1385
+ }
1386
+
1387
+ .scenario-lab-actions {
1388
+ display: flex;
1389
+ gap: 8px;
1390
+ flex-wrap: wrap;
1391
+ margin-bottom: 12px;
1392
+ }
1393
+
1394
+ .scenario-lab-empty {
1395
+ color: #6b7e78;
1396
+ font-style: italic;
1397
+ margin: 0;
1398
+ }
1399
+
1400
+ .scenario-table-wrap {
1401
+ overflow-x: auto;
1402
+ -webkit-overflow-scrolling: touch;
1403
+ }
1404
+
1405
+ .scenario-table {
1406
+ border-collapse: collapse;
1407
+ width: 100%;
1408
+ font-size: 0.82rem;
1409
+ }
1410
+
1411
+ .scenario-table th,
1412
+ .scenario-table td {
1413
+ border: 1px solid var(--line);
1414
+ padding: 6px 10px;
1415
+ text-align: center;
1416
+ vertical-align: middle;
1417
+ }
1418
+
1419
+ .scenario-table thead th {
1420
+ background: linear-gradient(90deg, rgba(249,248,242,0.95), rgba(237,246,243,0.95));
1421
+ font-weight: 700;
1422
+ }
1423
+
1424
+ .scenario-table td.metric-label,
1425
+ .scenario-table th.metric-label {
1426
+ text-align: left;
1427
+ font-weight: 600;
1428
+ background: rgba(243,248,245,0.7);
1429
+ white-space: nowrap;
1430
+ min-width: 110px;
1431
+ }
1432
+
1433
+ .scenario-col-head { min-width: 120px; }
1434
+
1435
+ .scenario-badge {
1436
+ display: inline-block;
1437
+ background: var(--brand);
1438
+ color: #fff;
1439
+ border-radius: 4px;
1440
+ padding: 1px 7px;
1441
+ font-size: 0.74rem;
1442
+ font-weight: 700;
1443
+ }
1444
+
1445
+ .scenario-val { min-width: 90px; white-space: nowrap; }
1446
+
1447
+ .scenario-remove-btn {
1448
+ font-size: 0.72rem !important;
1449
+ padding: 2px 8px !important;
1450
+ margin-top: 4px;
1451
+ min-height: unset !important;
1452
+ }
1453
+
1454
+ .delta-col, .delta-col-head { min-width: 76px; font-weight: 600; white-space: nowrap; }
1455
+ .delta-pos { color: #18855e; }
1456
+ .delta-neg { color: #c0392b; }
1457
+ .delta-zero { color: #7a8b80; }
1458
+
1459
+ /* ── Spiegabilita ─────────────────────────────────────────────── */
1460
+ .spieg-panel { margin-top: 12px; }
1461
+
1462
+ .spieg-details {
1463
+ border: 1px solid #bfd7cf;
1464
+ border-radius: 14px;
1465
+ overflow: hidden;
1466
+ background: linear-gradient(180deg, #fbfdfc, #f4f9f7);
1467
+ box-shadow: 0 8px 24px rgba(8, 74, 67, 0.08);
1468
+ }
1469
+
1470
+ .spieg-title {
1471
+ cursor: pointer;
1472
+ padding: 10px 14px;
1473
+ background: linear-gradient(90deg, rgba(248, 247, 240, 0.95), rgba(229, 243, 238, 0.95));
1474
+ font-weight: 700;
1475
+ font-size: 0.92rem;
1476
+ line-height: 1.3;
1477
+ user-select: none;
1478
+ list-style: none;
1479
+ color: #194f48;
1480
+ border-bottom: 1px solid #d3e4de;
1481
+ }
1482
+
1483
+ .spieg-title::-webkit-details-marker { display: none; }
1484
+ .spieg-title::before { content: "\25B8\0020"; font-size: 0.76rem; color: #1e6d63; }
1485
+ details[open] .spieg-title::before { content: "\25BE\0020"; }
1486
+
1487
+ .spieg-grid {
1488
+ display: grid;
1489
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1490
+ gap: 10px;
1491
+ padding: 12px;
1492
+ }
1493
+
1494
+ @media (max-width: 720px) {
1495
+ .spieg-grid { grid-template-columns: 1fr; }
1496
+ }
1497
+
1498
+ .spieg-item {
1499
+ background: #f8fcfa;
1500
+ border: 1px solid #cfe3dc;
1501
+ border-radius: 10px;
1502
+ padding: 10px 12px;
1503
+ font-size: 0.82rem;
1504
+ min-width: 0;
1505
+ }
1506
+
1507
+ .spieg-item--result {
1508
+ grid-column: 1 / -1;
1509
+ background: linear-gradient(120deg, #ebf8f2, #e3f4ed);
1510
+ border-color: #a5d2be;
1511
+ }
1512
+
1513
+ .spieg-item-label {
1514
+ display: inline-flex;
1515
+ align-items: center;
1516
+ gap: 6px;
1517
+ font-weight: 800;
1518
+ font-size: 0.73rem;
1519
+ color: #0f6a61;
1520
+ margin-bottom: 8px;
1521
+ text-transform: uppercase;
1522
+ letter-spacing: 0.04em;
1523
+ }
1524
+
1525
+ .spieg-item-icon {
1526
+ display: inline-flex;
1527
+ align-items: center;
1528
+ justify-content: center;
1529
+ width: 18px;
1530
+ height: 18px;
1531
+ border-radius: 999px;
1532
+ background: #e0f1eb;
1533
+ color: #0f6a61;
1534
+ font-size: 0.73rem;
1535
+ line-height: 1;
1536
+ }
1537
+
1538
+ .spieg-item-body {
1539
+ display: grid;
1540
+ gap: 8px;
1541
+ line-height: 1.5;
1542
+ min-width: 0;
1543
+ }
1544
+
1545
+ .spieg-people {
1546
+ display: grid;
1547
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1548
+ gap: 8px;
1549
+ }
1550
+
1551
+ @media (max-width: 560px) {
1552
+ .spieg-people { grid-template-columns: 1fr; }
1553
+ }
1554
+
1555
+ .spieg-person {
1556
+ border: 1px solid #d8e8e2;
1557
+ border-radius: 8px;
1558
+ background: #ffffff;
1559
+ padding: 8px;
1560
+ min-width: 0;
1561
+ }
1562
+
1563
+ .spieg-person--left { border-left: 4px solid #1d7d72; }
1564
+ .spieg-person--right { border-left: 4px solid #bf7a1f; }
1565
+
1566
+ .spieg-person-name {
1567
+ font-weight: 700;
1568
+ color: #204e48;
1569
+ margin-bottom: 2px;
1570
+ overflow-wrap: anywhere;
1571
+ }
1572
+
1573
+ .spieg-person-value {
1574
+ font-size: 1rem;
1575
+ font-weight: 800;
1576
+ color: #0f6259;
1577
+ font-variant-numeric: tabular-nums;
1578
+ }
1579
+
1580
+ .spieg-person-sub {
1581
+ margin-top: 2px;
1582
+ color: #5f7772;
1583
+ font-size: 0.74rem;
1584
+ }
1585
+
1586
+ .spieg-equation {
1587
+ display: flex;
1588
+ align-items: center;
1589
+ flex-wrap: wrap;
1590
+ gap: 6px;
1591
+ }
1592
+
1593
+ .spieg-pill {
1594
+ display: inline-flex;
1595
+ align-items: center;
1596
+ padding: 2px 8px;
1597
+ border-radius: 999px;
1598
+ background: #e8f3ef;
1599
+ border: 1px solid #cbe1d9;
1600
+ color: #184a44;
1601
+ font-weight: 700;
1602
+ font-variant-numeric: tabular-nums;
1603
+ max-width: 100%;
1604
+ overflow-wrap: anywhere;
1605
+ }
1606
+
1607
+ .spieg-pill--result {
1608
+ background: #dff3ea;
1609
+ border-color: #9dceb8;
1610
+ color: #0a6157;
1611
+ }
1612
+
1613
+ .spieg-op {
1614
+ color: #6d817c;
1615
+ font-weight: 700;
1616
+ }
1617
+
1618
+ .spieg-line--kv {
1619
+ display: grid;
1620
+ grid-template-columns: minmax(92px, 128px) minmax(0, 1fr);
1621
+ gap: 8px;
1622
+ align-items: baseline;
1623
+ }
1624
+
1625
+ @media (max-width: 560px) {
1626
+ .spieg-line--kv { grid-template-columns: 1fr; gap: 2px; }
1627
+ }
1628
+
1629
+ .spieg-k {
1630
+ font-weight: 700;
1631
+ color: #295b54;
1632
+ overflow-wrap: anywhere;
1633
+ }
1634
+
1635
+ .spieg-v {
1636
+ color: #123f39;
1637
+ font-variant-numeric: tabular-nums;
1638
+ overflow-wrap: anywhere;
1639
+ }
1640
+
1641
+ .spieg-item-body--result {
1642
+ background: #ffffff;
1643
+ border: 1px dashed #9fceb9;
1644
+ border-radius: 10px;
1645
+ padding: 10px 12px;
1646
+ gap: 6px;
1647
+ }
1648
+
1649
+ .spieg-result-flow {
1650
+ font-weight: 800;
1651
+ color: #0f6a61;
1652
+ letter-spacing: 0.01em;
1653
+ }
1654
+
1655
+ .spieg-result-formula {
1656
+ color: #284e49;
1657
+ overflow-wrap: anywhere;
1658
+ font-variant-numeric: tabular-nums;
1659
+ }
1660
+
1661
+ .spieg-result-amount {
1662
+ font-size: 1.2rem;
1663
+ font-weight: 900;
1664
+ line-height: 1.2;
1665
+ color: #0e6b62;
1666
+ font-variant-numeric: tabular-nums;
1667
+ }
1668
+
1669
+ .spieg-result-empty {
1670
+ font-size: 0.95rem;
1671
+ font-weight: 800;
1672
+ color: #0f6a61;
1673
+ }
1674
+
1223
1675
  @page {
1224
1676
  size: A4 landscape;
1225
1677
  margin: 8mm;
@@ -1633,15 +2085,105 @@
1633
2085
  .result-pill {
1634
2086
  border-radius: 14px;
1635
2087
  padding: 14px;
1636
- background: linear-gradient(165deg, #f4faf8, #ebf6f3);
1637
- border: 1px solid #bfd7d1;
2088
+ background:
2089
+ radial-gradient(120% 180% at 0% -20%, rgba(215, 243, 236, 0.95), transparent 56%),
2090
+ radial-gradient(120% 180% at 100% -20%, rgba(255, 237, 201, 0.92), transparent 56%),
2091
+ linear-gradient(165deg, #f7fcfb, #ebf6f3);
2092
+ border: 1px solid #afcfc7;
2093
+ box-shadow: 0 12px 26px rgba(17, 70, 64, 0.16);
1638
2094
  margin-bottom: 10px;
2095
+ position: relative;
2096
+ overflow: hidden;
2097
+ }
2098
+
2099
+ .result-pill::after {
2100
+ content: "";
2101
+ position: absolute;
2102
+ inset: -28px auto auto -28px;
2103
+ width: 120px;
2104
+ height: 120px;
2105
+ border-radius: 50%;
2106
+ background: radial-gradient(circle, rgba(59, 170, 154, 0.2) 0%, rgba(59, 170, 154, 0) 70%);
2107
+ pointer-events: none;
1639
2108
  }
1640
2109
 
1641
2110
  .result-pill .big {
1642
- font-size: 1.58rem;
1643
- font-weight: 800;
2111
+ font-size: 1.3rem;
2112
+ font-weight: 900;
1644
2113
  margin-top: 6px;
2114
+ line-height: 1.34;
2115
+ letter-spacing: 0.2px;
2116
+ color: #123b38;
2117
+ }
2118
+
2119
+ .result-main-flow {
2120
+ display: block;
2121
+ font-size: 1.02rem;
2122
+ font-weight: 800;
2123
+ color: #124741;
2124
+ text-transform: uppercase;
2125
+ letter-spacing: 0.3px;
2126
+ margin-bottom: 3px;
2127
+ }
2128
+
2129
+ .result-main-amount {
2130
+ display: block;
2131
+ font-size: 1.68rem;
2132
+ font-weight: 900;
2133
+ color: #0b625b;
2134
+ text-shadow: 0 1px 0 rgba(255,255,255,0.7);
2135
+ }
2136
+
2137
+ .result-main-line {
2138
+ display: block;
2139
+ font-size: 1.25rem;
2140
+ font-weight: 800;
2141
+ color: #1c3f3b;
2142
+ margin-bottom: 6px;
2143
+ }
2144
+
2145
+ .result-main-sub {
2146
+ display: inline-flex;
2147
+ align-items: center;
2148
+ gap: 5px;
2149
+ font-size: 0.76rem;
2150
+ text-transform: uppercase;
2151
+ letter-spacing: 0.4px;
2152
+ font-weight: 900;
2153
+ color: #4e665f;
2154
+ background: rgba(255,255,255,0.78);
2155
+ border: 1px solid #bfd6d0;
2156
+ border-radius: 999px;
2157
+ padding: 3px 9px;
2158
+ margin-bottom: 8px;
2159
+ }
2160
+
2161
+ .result-benefits-list {
2162
+ margin: 0;
2163
+ padding: 0;
2164
+ list-style: none;
2165
+ display: grid;
2166
+ gap: 6px;
2167
+ }
2168
+
2169
+ .result-benefits-list li {
2170
+ display: flex;
2171
+ align-items: flex-start;
2172
+ justify-content: space-between;
2173
+ gap: 8px;
2174
+ padding: 7px 9px;
2175
+ border-radius: 10px;
2176
+ border: 1px solid #c5dbd5;
2177
+ background: rgba(255,255,255,0.86);
2178
+ font-size: 0.9rem;
2179
+ font-weight: 700;
2180
+ color: #284744;
2181
+ }
2182
+
2183
+ .result-benefits-list strong {
2184
+ white-space: nowrap;
2185
+ color: #0f5f57;
2186
+ font-size: 0.95rem;
1645
2187
  }
1646
2188
 
1647
2189
  .live-box {
@@ -2242,6 +2784,18 @@
2242
2784
  .extra-grid {
2243
2785
  grid-template-columns: 1fr;
2244
2786
  }
2787
+
2788
+ .mortgage-split-slider {
2789
+ grid-template-columns: 1fr;
2790
+ }
2791
+
2792
+ .mortgage-split-side-right {
2793
+ text-align: left;
2794
+ }
2795
+
2796
+ .mortgage-split-range-wrap {
2797
+ order: -1;
2798
+ }
2245
2799
  .spieg-grid {
2246
2800
  grid-template-columns: 1fr;
2247
2801
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mantenimento-app",
3
- "version": "2.1.3",
3
+ "version": "2.1.4",
4
4
  "description": "Frontend + backend architecture for the mantenimento calculator",
5
5
  "type": "commonjs",
6
6
  "main": "backend/calculate-model.js",