function fmtKr(n) {
return Math.round(n).toLocaleString('nb-NO') + '\u202fkr';
}
function fmtShort(n) {
if (n >= 1e6) return (n/1e6).toFixed(1).replace('.',',') + '\u202fM';
if (n >= 1e3) return Math.round(n/1e3) + '\u202fk';
return Math.round(n);
}
function getData() {
const A0 = +document.getElementById('salA').value;
const B0 = +document.getElementById('salB').value;
const pct = +document.getElementById('pct').value / 100;
const Y = +document.getElementById('yrs').value;
const labels=[], sA=[], sB=[], gaps=[], monthly=[], cumA=[], cumB=[], cumGaps=[];
let rA=0, rB=0;
for (let y=0; y<=Y; y++) {
labels.push(y===0 ? 'I dag' : (y % 5 === 0 || Y <= 10) ? 'År '+y : (y===Y ? 'År '+y : ''));
const a = A0 * Math.pow(1+pct, y);
const b = B0 * Math.pow(1+pct, y);
sA.push(a); sB.push(b);
gaps.push(b-a);
monthly.push((b-a)/12);
rA += a; rB += b;
cumA.push(rA); cumB.push(rB); cumGaps.push(rB-rA);
}
return {A0,B0,pct,Y,labels,sA,sB,gaps,monthly,cumA,cumB,cumGaps};
}
const GRID_COLOR = 'rgba(0,0,0,0.06)';
const TICK_COLOR = '#8a857e';
function baseOpts() {
return {
responsive: true, maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
callbacks: { label: c => ' ' + fmtKr(c.parsed.y) },
backgroundColor: '#1a1714',
titleColor: '#f5f2eb',
bodyColor: 'rgba(245,242,235,0.7)',
padding: 10,
cornerRadius: 4,
titleFont: { family: 'DM Sans', size: 12 },
bodyFont: { family: 'DM Sans', size: 12 }
}
},
scales: {
x: {
ticks: { color: TICK_COLOR, font: { size: 11, family: 'DM Sans' }, autoSkip: false, maxRotation: 0 },
grid: { display: false },
border: { color: 'rgba(0,0,0,0.1)' }
},
y: {
ticks: { color: TICK_COLOR, font: { size: 11, family: 'DM Sans' }, callback: v => fmtShort(v) },
grid: { color: GRID_COLOR },
border: { dash: [3,3], color: 'transparent' }
}
}
};
}
const chart1 = new Chart(document.getElementById('chart1'), {
type: 'line',
data: { labels:[], datasets:[
{ data:[], borderColor:'#1a4a8a', backgroundColor:'rgba(26,74,138,0.07)', fill:true, tension:0.35, pointRadius:0, borderWidth:2 },
{ data:[], borderColor:'#c0392b', backgroundColor:'rgba(192,57,43,0.07)', fill:true, tension:0.35, pointRadius:0, borderWidth:2 }
]},
options: baseOpts()
});
const chart2 = new Chart(document.getElementById('chart2'), {
type: 'line',
data: { labels:[], datasets:[
{ data:[], borderColor:'#4a3a8a', backgroundColor:'rgba(74,58,138,0.1)', fill:true, tension:0.35, pointRadius:0, borderWidth:2 }
]},
options: baseOpts()
});
const chart3 = new Chart(document.getElementById('chart3'), {
type: 'line',
data: { labels:[], datasets:[
{ data:[], borderColor:'#2c6e49', backgroundColor:'rgba(44,110,73,0.1)', fill:true, tension:0.35, pointRadius:0, borderWidth:2 }
]},
options: baseOpts()
});
const chart4 = new Chart(document.getElementById('chart4'), {
type: 'line',
data: { labels:[], datasets:[
{ data:[], borderColor:'#1a4a8a', backgroundColor:'rgba(26,74,138,0.07)', fill:true, tension:0.35, pointRadius:0, borderWidth:2 },
{ data:[], borderColor:'#c0392b', backgroundColor:'rgba(192,57,43,0.07)', fill:true, tension:0.35, pointRadius:0, borderWidth:2 },
{ data:[], borderColor:'#b5620a', backgroundColor:'rgba(181,98,10,0.1)', fill:true, tension:0.35, pointRadius:0, borderWidth:2, borderDash:[6,4] }
]},
options: baseOpts()
});
// Alle verdier som settes via innerHTML kommer fra numeriske beregninger (slider-verdier),
// ikke fra brukerinput-tekst — ingen XSS-risiko.
function update() {
const {A0,B0,pct,Y,labels,sA,sB,gaps,monthly,cumA,cumB,cumGaps} = getData();
// Oppdater visning
document.querySelectorAll('.yr-lbl').forEach(el => el.textContent = Y);
document.getElementById('salA-out').textContent = Math.round(A0).toLocaleString('nb-NO') + '\u202fkr';
document.getElementById('salB-out').textContent = Math.round(B0).toLocaleString('nb-NO') + '\u202fkr';
document.getElementById('pct-out').textContent = (+document.getElementById('pct').value).toLocaleString('nb-NO', {minimumFractionDigits:1}) + '\u202f%';
document.getElementById('yrs-out').textContent = Y + '\u202får';
// Statistikk
document.getElementById('st-finalA').textContent = fmtKr(sA[Y]);
document.getElementById('st-monthA').textContent = fmtKr(sA[Y]/12) + '/mnd';
document.getElementById('st-subA').textContent = '+' + fmtKr(sA[Y]-A0) + ' fra start';
document.getElementById('st-finalB').textContent = fmtKr(sB[Y]);
document.getElementById('st-monthB').textContent = fmtKr(sB[Y]/12) + '/mnd';
document.getElementById('st-subB').textContent = '+' + fmtKr(sB[Y]-B0) + ' fra start';
document.getElementById('st-diffyr').textContent = fmtKr(gaps[Y]) + '/år';
document.getElementById('st-diffmnd').textContent = fmtKr(monthly[Y]) + '/mnd';
document.getElementById('st-diffstart').textContent = 'Var ' + fmtKr(B0-A0) + '/år ved start';
document.getElementById('st-cumgap').textContent = fmtKr(cumGaps[Y]);
// Banner
document.getElementById('banner-diff').textContent = fmtKr(B0 - A0);
document.getElementById('banner-gap').textContent = fmtKr(cumGaps[Y]);
// Innsikt — innerHTML er trygt her: verdiene er kun formaterte tall fra slidere
const monthlyNow = Math.round((B0-A0)/12);
const monthlyEnd = Math.round(monthly[Y]);
document.getElementById('insight-monthly').innerHTML =
`Månedlig gap i dag: ${fmtKr(monthlyNow)} ekstra til B. `+
`Etter ${Y} år er det ${fmtKr(monthlyEnd)} per måned — `+
`en økning på ${fmtKr(monthlyEnd - monthlyNow)} kun pga. prosentvekst.`;
// Diagrammer
chart1.data.labels = labels;
chart1.data.datasets[0].data = sA;
chart1.data.datasets[1].data = sB;
chart1.update('none');
chart2.data.labels = labels;
chart2.data.datasets[0].data = gaps;
chart2.update('none');
chart3.data.labels = labels;
chart3.data.datasets[0].data = monthly;
chart3.update('none');
chart4.data.labels = labels;
chart4.data.datasets[0].data = cumA;
chart4.data.datasets[1].data = cumB;
chart4.data.datasets[2].data = cumGaps;
chart4.update('none');
}
['salA','salB','pct','yrs'].forEach(id =>
document.getElementById(id).addEventListener('input', update)
);
update();