misc-stuff / index.html
Jausing's picture
Add 2 files
2477bbc verified
<!DOCTYPE html>
<html lang="sv">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Thomanders Pensionsoptimering</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/slider-ui/dist/slider-ui.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.slider-thumb::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 24px;
height: 24px;
border-radius: 50%;
background: #3b82f6;
cursor: pointer;
}
.slider-thumb::-moz-range-thumb {
width: 24px;
height: 24px;
border-radius: 50%;
background: #3b82f6;
cursor: pointer;
}
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 200px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 8px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -100px;
opacity: 0;
transition: opacity 0.3s;
font-size: 14px;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
.animate-grow {
animation: grow 1.5s ease-out forwards;
}
@keyframes grow {
from { transform: scaleY(0); }
to { transform: scaleY(1); }
}
.fade-in {
animation: fadeIn 1s ease-in forwards;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.progress-bar {
height: 8px;
border-radius: 4px;
background-color: #e5e7eb;
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background-color: #3b82f6;
transition: width 0.3s ease;
}
.function-graph {
height: 200px;
width: 100%;
background-color: #f8fafc;
border-radius: 8px;
position: relative;
overflow: hidden;
}
.graph-line {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background-color: #3b82f6;
}
.tax-bracket {
position: absolute;
bottom: 0;
height: 100%;
background-color: rgba(59, 130, 246, 0.1);
border-left: 1px dashed #3b82f6;
border-right: 1px dashed #3b82f6;
}
.tax-bracket-label {
position: absolute;
top: 5px;
transform: translateX(-50%);
font-size: 10px;
color: #3b82f6;
white-space: nowrap;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="text-center mb-12">
<h1 class="text-4xl font-bold text-blue-800 mb-4">Thomanders Pensionsoptimering - Livscykelvärdering</h1>
<p class="text-lg text-gray-600 max-w-3xl mx-auto">
Vad är en finansiell rådgivare från Max värd? Ungefär en prompt....En komplett modell för att maximera din upplevda livskvalitet genom optimal pensionsplanering.
Tar hänsyn till skiktgränser, subjektiv tidspreferens och osäkerhet i livslängd.
</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Input Controls -->
<div class="bg-white rounded-xl shadow-lg p-6 lg:col-span-1">
<h2 class="text-2xl font-semibold text-blue-700 mb-6">Hårda Parametrar</h2>
<div class="space-y-6">
<div>
<label for="salary" class="block text-sm font-medium text-gray-700 mb-2">
Nuvarande månadslön (brutto)
<span class="tooltip ml-1">
<i class="fas fa-info-circle text-blue-500"></i>
<span class="tooltiptext">Din nuvarande bruttolön innan skatt och avdrag</span>
</span>
</label>
<div class="flex items-center">
<span class="mr-2 text-gray-500">kr</span>
<input type="range" id="salary" min="20000" max="150000" step="1000" value="45000" class="w-full h-2 bg-gray-200 rounded-lg slider-thumb">
<span id="salaryValue" class="ml-4 font-medium">45 000</span>
</div>
</div>
<div>
<label for="age" class="block text-sm font-medium text-gray-700 mb-2">
Nuvarande ålder
<span class="tooltip ml-1">
<i class="fas fa-info-circle text-blue-500"></i>
<span class="tooltiptext">Din nuvarande ålder för att beräkna tills pension</span>
</span>
</label>
<div class="flex items-center">
<input type="range" id="age" min="20" max="65" step="1" value="35" class="w-full h-2 bg-gray-200 rounded-lg slider-thumb">
<span id="ageValue" class="ml-4 font-medium">35</span>
</div>
</div>
</div>
<h2 class="text-2xl font-semibold text-blue-700 mt-8 mb-6">Semi-flexibla Parametrar</h2>
<div class="space-y-6">
<div>
<label for="salaryGrowth" class="block text-sm font-medium text-gray-700 mb-2">
Förväntad årlig löneutveckling (%)
<span class="tooltip ml-1">
<i class="fas fa-info-circle text-blue-500"></i>
<span class="tooltiptext">Genomsnittlig årlig ökning av din lön fram till pension</span>
</span>
</label>
<div class="flex items-center">
<input type="range" id="salaryGrowth" min="-2" max="10" step="0.5" value="2.5" class="w-full h-2 bg-gray-200 rounded-lg slider-thumb">
<span id="salaryGrowthValue" class="ml-4 font-medium">2.5%</span>
</div>
</div>
<div>
<label for="retirementAge" class="block text-sm font-medium text-gray-700 mb-2">
Planerad pensionsålder
<span class="tooltip ml-1">
<i class="fas fa-info-circle text-blue-500"></i>
<span class="tooltiptext">Ålder då du planerar att gå i pension</span>
</span>
</label>
<div class="flex items-center">
<input type="range" id="retirementAge" min="55" max="75" step="1" value="65" class="w-full h-2 bg-gray-200 rounded-lg slider-thumb">
<span id="retirementAgeValue" class="ml-4 font-medium">65</span>
</div>
</div>
<div>
<label for="lifeExpectancy" class="block text-sm font-medium text-gray-700 mb-2">
Förväntad dödsålder
<span class="tooltip ml-1">
<i class="fas fa-info-circle text-blue-500"></i>
<span class="tooltiptext">Ålder då du förväntar dig att dö (baserat på släkt och hälsa)</span>
</span>
</label>
<div class="flex items-center">
<input type="range" id="lifeExpectancy" min="65" max="100" step="1" value="85" class="w-full h-2 bg-gray-200 rounded-lg slider-thumb">
<span id="lifeExpectancyValue" class="ml-4 font-medium">85</span>
</div>
</div>
</div>
<h2 class="text-2xl font-semibold text-blue-700 mt-8 mb-6">Subjektiva Värderingar</h2>
<div class="space-y-6">
<div>
<label for="currentValueMultiplier" class="block text-sm font-medium text-gray-700 mb-2">
Nuvarande värdefaktor (300% betyder 3x mer värde idag)
<span class="tooltip ml-1">
<i class="fas fa-info-circle text-blue-500"></i>
<span class="tooltiptext">Hur mycket mer värderar du pengar idag jämfört med efter pension?</span>
</span>
</label>
<div class="flex items-center">
<input type="range" id="currentValueMultiplier" min="100" max="500" step="10" value="300" class="w-full h-2 bg-gray-200 rounded-lg slider-thumb">
<span id="currentValueMultiplierValue" class="ml-4 font-medium">300%</span>
</div>
<div class="function-graph mt-2" id="currentValueGraph">
<div class="graph-line"></div>
</div>
</div>
<div>
<label for="retirementValueMultiplier" class="block text-sm font-medium text-gray-700 mb-2">
Pensionsvärdefaktor (100% = neutral)
<span class="tooltip ml-1">
<i class="fas fa-info-circle text-blue-500"></i>
<span class="tooltiptext">Hur mycket värderar du pengar under pensionsåren jämfört med vid pensionsstart?</span>
</span>
</label>
<div class="flex items-center">
<input type="range" id="retirementValueMultiplier" min="0" max="200" step="5" value="100" class="w-full h-2 bg-gray-200 rounded-lg slider-thumb">
<span id="retirementValueMultiplierValue" class="ml-4 font-medium">100%</span>
</div>
<div class="function-graph mt-2" id="retirementValueGraph">
<div class="graph-line"></div>
</div>
</div>
<div>
<label for="endOfLifeReserve" class="block text-sm font-medium text-gray-700 mb-2">
Reserv vid dödsålder (% av totalt sparande)
<span class="tooltip ml-1">
<i class="fas fa-info-circle text-blue-500"></i>
<span class="tooltiptext">Hur stor del av ditt sparande vill du ha kvar vid förväntad dödsålder?</span>
</span>
</label>
<div class="flex items-center">
<input type="range" id="endOfLifeReserve" min="0" max="100" step="5" value="20" class="w-full h-2 bg-gray-200 rounded-lg slider-thumb">
<span id="endOfLifeReserveValue" class="ml-4 font-medium">20%</span>
</div>
</div>
</div>
<h2 class="text-2xl font-semibold text-blue-700 mt-8 mb-6">Pensionsstrategi</h2>
<div class="space-y-6">
<div>
<label for="pensionContribution" class="block text-sm font-medium text-gray-700 mb-2">
Pensionsavsättning (% av lön)
<span class="tooltip ml-1">
<i class="fas fa-info-circle text-blue-500"></i>
<span class="tooltiptext">Den procent av din lön som går till pensionssparande</span>
</span>
</label>
<div class="flex items-center">
<input type="range" id="pensionContribution" min="2" max="20" step="0.5" value="4.5" class="w-full h-2 bg-gray-200 rounded-lg slider-thumb">
<span id="pensionContributionValue" class="ml-4 font-medium">4.5%</span>
</div>
</div>
<div>
<label for="salaryExchange" class="block text-sm font-medium text-gray-700 mb-2">
Löneväxlingsandel (%)
<span class="tooltip ml-1">
<i class="fas fa-info-circle text-blue-500"></i>
<span class="tooltiptext">Andel av din pensionsavsättning som löneväxlas</span>
</span>
</label>
<div class="flex items-center">
<input type="range" id="salaryExchange" min="0" max="100" step="5" value="0" class="w-full h-2 bg-gray-200 rounded-lg slider-thumb">
<span id="salaryExchangeValue" class="ml-4 font-medium">0%</span>
</div>
</div>
<div>
<label for="expectedReturn" class="block text-sm font-medium text-gray-700 mb-2">
Förväntad årlig avkastning (%)
<span class="tooltip ml-1">
<i class="fas fa-info-circle text-blue-500"></i>
<span class="tooltiptext">Genomsnittlig årlig avkastning på ditt pensionssparande</span>
</span>
</label>
<div class="flex items-center">
<input type="range" id="expectedReturn" min="1" max="10" step="0.5" value="5" class="w-full h-2 bg-gray-200 rounded-lg slider-thumb">
<span id="expectedReturnValue" class="ml-4 font-medium">5%</span>
</div>
</div>
<div class="pt-4">
<button id="calculateBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200 flex items-center justify-center">
<i class="fas fa-calculator mr-2"></i> Beräkna Optimal Strategi
</button>
</div>
</div>
</div>
<!-- Results Visualization -->
<div class="bg-white rounded-xl shadow-lg p-6 lg:col-span-2">
<h2 class="text-2xl font-semibold text-blue-700 mb-6">Optimeringsresultat</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
<div class="bg-blue-50 rounded-lg p-4 border border-blue-100">
<h3 class="text-lg font-medium text-blue-800 mb-2">Optimal löneväxling</h3>
<p class="text-2xl font-bold text-blue-600" id="optimalExchange">0%</p>
</div>
<div class="bg-purple-50 rounded-lg p-4 border border-purple-100">
<h3 class="text-lg font-medium text-purple-800 mb-2">Maximerat livsvärde</h3>
<p class="text-2xl font-bold text-purple-600" id="maxValue">0</p>
</div>
<div class="bg-green-50 rounded-lg p-4 border border-green-100">
<h3 class="text-lg font-medium text-green-800 mb-2">Pensionspot vid pension</h3>
<p class="text-2xl font-bold text-green-600" id="totalPension">0 kr</p>
</div>
</div>
<div class="mb-6">
<h3 class="text-xl font-semibold text-gray-800 mb-3">Skatteskikt och Marginaleffekt</h3>
<div class="bg-white border border-gray-200 rounded-lg p-4">
<div class="function-graph" id="taxBracketGraph">
<div class="graph-line"></div>
<!-- Tax brackets will be added dynamically -->
</div>
<div class="mt-2 text-sm text-gray-600" id="marginalTaxText">
Din marginaleffekt vid löneväxling beräknas baserat på dina parametrar.
</div>
</div>
</div>
<div class="mb-8">
<h3 class="text-xl font-semibold text-gray-800 mb-4">Livscykelvärde över tid</h3>
<div class="h-64">
<canvas id="lifecycleChart"></canvas>
</div>
</div>
<div class="mb-8">
<h3 class="text-xl font-semibold text-gray-800 mb-4">Pensionsutveckling med optimal strategi</h3>
<div class="h-64">
<canvas id="pensionChart"></canvas>
</div>
</div>
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-6">
<div class="flex">
<div class="flex-shrink-0">
<i class="fas fa-lightbulb text-yellow-500 text-xl"></i>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-yellow-800">Optimeringsstrategi</h3>
<div class="mt-2 text-sm text-yellow-700" id="strategyTips">
<p>Systemet beräknar den optimala löneväxlingsnivån baserat på dina parametrar och subjektiva värderingar.</p>
</div>
</div>
</div>
</div>
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
<h3 class="text-lg font-medium text-gray-800 mb-3">Livscykelanalys</h3>
<div class="space-y-3 text-sm text-gray-700" id="lifecycleAnalysis">
<p>Denna optimering tar hänsyn till:</p>
<ul class="list-disc pl-5 space-y-1">
<li>Skatteskikt och marginaleffekter av löneväxling</li>
<li>Din subjektiva värdering av pengar över tid</li>
<li>Förväntad löneutveckling och livslängd</li>
<li>Avkastning på pensionssparande</li>
<li>Önskad reserv vid förväntad dödsålder</li>
</ul>
<p>Resultatet visar den strategi som maximerar din upplevda livskvalitet över hela livscykeln.</p>
</div>
</div>
</div>
</div>
</div>
<script>
// Swedish tax brackets for 2023 (simplified)
const TAX_BRACKETS = [
{ min: 0, max: 509300, rate: 0.0, name: "Ingen skatt" },
{ min: 509300, max: 673400, rate: 0.2, name: "Kommunal skatt" },
{ min: 673400, max: 1018600, rate: 0.5, name: "Statlig skatt" },
{ min: 1018600, max: Infinity, rate: 0.55, name: "Toppskatt" }
];
// Format numbers with spaces as thousand separators
function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
}
// Calculate marginal tax rate for a given salary
function calculateMarginalTax(salary) {
for (let i = TAX_BRACKETS.length - 1; i >= 0; i--) {
if (salary > TAX_BRACKETS[i].min) {
return TAX_BRACKETS[i].rate;
}
}
return 0;
}
// Calculate total tax for a given salary
function calculateTotalTax(salary) {
let tax = 0;
for (const bracket of TAX_BRACKETS) {
if (salary > bracket.min) {
const taxableAmount = Math.min(salary, bracket.max) - bracket.min;
tax += taxableAmount * bracket.rate;
}
}
return tax;
}
// Calculate employer contributions (simplified)
function calculateEmployerContributions(salary) {
return salary * 0.3142; // Approximate employer contributions in Sweden
}
// Calculate compound interest with growing contributions
function calculateCompoundInterest(principal, annualRate, years, initialMonthlyContribution, contributionGrowthRate) {
let amount = principal;
const monthlyRate = annualRate / 12 / 100;
const months = years * 12;
let currentContribution = initialMonthlyContribution;
for (let i = 0; i < months; i++) {
// Apply annual contribution growth (every 12 months)
if (i > 0 && i % 12 === 0) {
currentContribution = currentContribution * (1 + contributionGrowthRate / 100);
}
amount = amount * (1 + monthlyRate) + currentContribution;
}
return amount;
}
// Calculate projected salary with growth
function calculateProjectedSalary(currentSalary, growthRate, years) {
return currentSalary * Math.pow(1 + growthRate / 100, years);
}
// Calculate time value multiplier (current to retirement)
function calculateCurrentValueMultiplier(yearsToRetirement, currentMultiplier) {
// Linear interpolation from currentMultiplier to 100% at retirement
return 100 + (currentMultiplier - 100) * (1 - yearsToRetirement / (retirementAge - age));
}
// Calculate time value multiplier (retirement to death)
function calculateRetirementValueMultiplier(yearsFromRetirement, retirementMultiplier) {
// Linear interpolation from 100% to retirementMultiplier at death
return 100 + (retirementMultiplier - 100) * (yearsFromRetirement / (lifeExpectancy - retirementAge));
}
// Update all displayed values when inputs change
function updateDisplayValues() {
document.getElementById('salaryValue').textContent = formatNumber(document.getElementById('salary').value);
document.getElementById('ageValue').textContent = document.getElementById('age').value;
document.getElementById('salaryGrowthValue').textContent = document.getElementById('salaryGrowth').value + '%';
document.getElementById('retirementAgeValue').textContent = document.getElementById('retirementAge').value;
document.getElementById('lifeExpectancyValue').textContent = document.getElementById('lifeExpectancy').value;
document.getElementById('currentValueMultiplierValue').textContent = document.getElementById('currentValueMultiplier').value + '%';
document.getElementById('retirementValueMultiplierValue').textContent = document.getElementById('retirementValueMultiplier').value + '%';
document.getElementById('endOfLifeReserveValue').textContent = document.getElementById('endOfLifeReserve').value + '%';
document.getElementById('pensionContributionValue').textContent = document.getElementById('pensionContribution').value + '%';
document.getElementById('salaryExchangeValue').textContent = document.getElementById('salaryExchange').value + '%';
document.getElementById('expectedReturnValue').textContent = document.getElementById('expectedReturn').value + '%';
updateValueGraphs();
updateTaxBracketGraph();
}
// Update the value function graphs
function updateValueGraphs() {
const currentMultiplier = parseInt(document.getElementById('currentValueMultiplier').value);
const retirementMultiplier = parseInt(document.getElementById('retirementValueMultiplier').value);
const age = parseInt(document.getElementById('age').value);
const retirementAge = parseInt(document.getElementById('retirementAge').value);
const lifeExpectancy = parseInt(document.getElementById('lifeExpectancy').value);
// Current value graph (from now to retirement)
const currentGraph = document.getElementById('currentValueGraph');
currentGraph.innerHTML = '<div class="graph-line"></div>';
// Add points to show the value decay
for (let y = age; y <= retirementAge; y++) {
const yearsToRetirement = retirementAge - y;
const valuePercent = calculateCurrentValueMultiplier(yearsToRetirement, currentMultiplier);
const point = document.createElement('div');
point.style.position = 'absolute';
point.style.bottom = '0';
point.style.left = `${((y - age) / (retirementAge - age)) * 100}%`;
point.style.width = '2px';
point.style.height = `${Math.min(100, valuePercent)}%`;
point.style.backgroundColor = '#3b82f6';
currentGraph.appendChild(point);
}
// Retirement value graph (from retirement to death)
const retirementGraph = document.getElementById('retirementValueGraph');
retirementGraph.innerHTML = '<div class="graph-line"></div>';
// Add points to show the value decay
for (let y = retirementAge; y <= lifeExpectancy; y++) {
const yearsFromRetirement = y - retirementAge;
const valuePercent = calculateRetirementValueMultiplier(yearsFromRetirement, retirementMultiplier);
const point = document.createElement('div');
point.style.position = 'absolute';
point.style.bottom = '0';
point.style.left = `${((y - retirementAge) / (lifeExpectancy - retirementAge)) * 100}%`;
point.style.width = '2px';
point.style.height = `${Math.min(100, valuePercent)}%`;
point.style.backgroundColor = '#3b82f6';
retirementGraph.appendChild(point);
}
}
// Update the tax bracket graph
function updateTaxBracketGraph() {
const salary = parseInt(document.getElementById('salary').value) * 12; // Annual salary
const graph = document.getElementById('taxBracketGraph');
graph.innerHTML = '<div class="graph-line"></div>';
// Find the maximum bracket that applies to our salary
let maxBracket = 0;
for (const bracket of TAX_BRACKETS) {
if (salary > bracket.min) {
maxBracket = Math.max(maxBracket, bracket.max);
}
}
// Add tax brackets to the graph
for (const bracket of TAX_BRACKETS) {
if (bracket.max > maxBracket * 1.5) continue; // Don't show brackets far beyond our salary
const bracketDiv = document.createElement('div');
bracketDiv.className = 'tax-bracket';
bracketDiv.style.left = `${(bracket.min / (maxBracket * 1.5)) * 100}%`;
bracketDiv.style.width = `${((bracket.max - bracket.min) / (maxBracket * 1.5)) * 100}%`;
graph.appendChild(bracketDiv);
const label = document.createElement('div');
label.className = 'tax-bracket-label';
label.textContent = `${bracket.name} (${bracket.rate * 100}%)`;
label.style.left = `${(bracket.min / (maxBracket * 1.5)) * 100}%`;
graph.appendChild(label);
}
// Add current salary marker
const marker = document.createElement('div');
marker.style.position = 'absolute';
marker.style.bottom = '0';
marker.style.left = `${(salary / (maxBracket * 1.5)) * 100}%`;
marker.style.width = '2px';
marker.style.height = '100%';
marker.style.backgroundColor = '#ef4444';
graph.appendChild(marker);
const markerLabel = document.createElement('div');
markerLabel.className = 'tax-bracket-label';
markerLabel.textContent = `Din lön: ${formatNumber(salary)} kr`;
markerLabel.style.left = `${(salary / (maxBracket * 1.5)) * 100}%`;
markerLabel.style.color = '#ef4444';
graph.appendChild(markerLabel);
// Update marginal tax text
const marginalTax = calculateMarginalTax(salary) * 100;
document.getElementById('marginalTaxText').innerHTML = `
Din nuvarande årslön: <strong>${formatNumber(salary)} kr</strong><br>
Marginaltaxesats: <strong>${marginalTax}%</strong><br>
Löneväxling kan spara <strong>${marginalTax + 31.42}%</strong> (skatt + arbetsgivaravgifter)
`;
}
// Initialize charts
let lifecycleChart;
let pensionChart;
function initCharts() {
const lifecycleCtx = document.getElementById('lifecycleChart').getContext('2d');
lifecycleChart = new Chart(lifecycleCtx, {
type: 'line',
data: {
labels: [],
datasets: [
{
label: 'Upplevt värde över tid',
data: [],
borderColor: '#8b5cf6',
backgroundColor: 'rgba(139, 92, 246, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.3
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: {
callbacks: {
label: function(context) {
return context.dataset.label + ': ' + context.raw.toFixed(2);
}
}
}
},
scales: {
y: {
title: {
display: true,
text: 'Relativt värde'
}
},
x: {
title: {
display: true,
text: 'Ålder'
}
}
}
}
});
const pensionCtx = document.getElementById('pensionChart').getContext('2d');
pensionChart = new Chart(pensionCtx, {
type: 'bar',
data: {
labels: ['Arbetsgivaravgifter sparade', 'Skatt sparad', 'Pensionsavsättning'],
datasets: [{
data: [0, 0, 0],
backgroundColor: [
'#3b82f6',
'#10b981',
'#f59e0b'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return context.label + ': ' + formatNumber(context.raw) + ' kr';
}
}
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return formatNumber(value) + ' kr';
}
}
}
}
}
});
}
// Calculate the optimal salary exchange percentage
function calculateOptimalStrategy() {
// Get input values
const salary = parseInt(document.getElementById('salary').value);
const salaryGrowth = parseFloat(document.getElementById('salaryGrowth').value);
const age = parseInt(document.getElementById('age').value);
const retirementAge = parseInt(document.getElementById('retirementAge').value);
const lifeExpectancy = parseInt(document.getElementById('lifeExpectancy').value);
const currentMultiplier = parseInt(document.getElementById('currentValueMultiplier').value) / 100;
const retirementMultiplier = parseInt(document.getElementById('retirementValueMultiplier').value) / 100;
const endOfLifeReserve = parseInt(document.getElementById('endOfLifeReserve').value) / 100;
const pensionContribution = parseFloat(document.getElementById('pensionContribution').value) / 100;
const expectedReturn = parseFloat(document.getElementById('expectedReturn').value);
// Calculate years to retirement and retirement duration
const yearsToRetirement = retirementAge - age;
const retirementDuration = lifeExpectancy - retirementAge;
// Test different salary exchange percentages to find the optimal one
let maxValue = -Infinity;
let optimalExchange = 0;
let optimalPension = 0;
let optimalValues = [];
for (let exchange = 0; exchange <= 100; exchange += 5) {
// Calculate pension growth with this exchange percentage
const totalPensionContribution = salary * pensionContribution;
const exchangedAmount = totalPensionContribution * (exchange / 100);
const regularPensionContribution = totalPensionContribution - exchangedAmount;
// Calculate tax savings from salary exchange
const taxRate = calculateTotalTax(salary) / salary;
const marginalTaxRate = calculateMarginalTax(salary * 12) / 100;
const employerContributionRate = 0.3142;
const taxSaved = exchangedAmount * marginalTaxRate;
const employerContributionsSaved = exchangedAmount * employerContributionRate;
const totalSavings = taxSaved + employerContributionsSaved;
// Calculate net salary
const taxWithoutExchange = calculateTotalTax(salary);
const taxWithExchange = calculateTotalTax(salary - exchangedAmount);
const netSalaryWithoutExchange = salary - taxWithoutExchange;
const netSalaryWithExchange = (salary - exchangedAmount) - taxWithExchange;
// Calculate pension growth with salary growth
const monthlyContributionWithExchange = (regularPensionContribution + exchangedAmount + totalSavings) / 12;
const monthlyContributionWithoutExchange = (regularPensionContribution + (exchangedAmount * (1 - marginalTaxRate))) / 12;
const pensionWithExchange = calculateCompoundInterest(0, expectedReturn, yearsToRetirement, monthlyContributionWithExchange, salaryGrowth);
const pensionWithoutExchange = calculateCompoundInterest(0, expectedReturn, yearsToRetirement, monthlyContributionWithoutExchange, salaryGrowth);
// Calculate perceived value over lifecycle
let totalValue = 0;
const yearlyValues = [];
// Working years (current to retirement)
for (let year = 0; year < yearsToRetirement; year++) {
const currentAge = age + year;
const yearsRemaining = yearsToRetirement - year;
// Calculate salary at this year
const yearSalary = calculateProjectedSalary(salary, salaryGrowth, year);
const yearExchangedAmount = yearSalary * pensionContribution * (exchange / 100);
const yearTaxWithExchange = calculateTotalTax(yearSalary - yearExchangedAmount);
const yearNetSalary = (yearSalary - yearExchangedAmount) - yearTaxWithExchange;
// Calculate value multiplier for this year
const valueMultiplier = 1 + (currentMultiplier - 1) * (yearsRemaining / yearsToRetirement);
// Add to total value
const yearValue = yearNetSalary * valueMultiplier;
totalValue += yearValue;
yearlyValues.push({
age: currentAge,
value: yearValue,
type: 'working'
});
}
// Retirement years (retirement to death)
const annualPensionWithdrawal = (pensionWithExchange * (1 - endOfLifeReserve)) / retirementDuration;
let remainingPension = pensionWithExchange;
for (let year = 0; year < retirementDuration; year++) {
const currentAge = retirementAge + year;
// Calculate pension withdrawal for this year
const withdrawal = Math.min(annualPensionWithdrawal, remainingPension);
remainingPension -= withdrawal;
// Calculate value multiplier for this year
const valueMultiplier = 1 + (retirementMultiplier - 1) * (year / retirementDuration);
// Add to total value
const yearValue = withdrawal * valueMultiplier;
totalValue += yearValue;
yearlyValues.push({
age: currentAge,
value: yearValue,
type: 'retirement'
});
}
// Add remaining pension at death (if any)
if (remainingPension > 0) {
totalValue += remainingPension * retirementMultiplier;
yearlyValues.push({
age: lifeExpectancy,
value: remainingPension * retirementMultiplier,
type: 'reserve'
});
}
// Check if this is the best strategy so far
if (totalValue > maxValue) {
maxValue = totalValue;
optimalExchange = exchange;
optimalPension = pensionWithExchange;
optimalValues = yearlyValues;
}
}
// Update results display
document.getElementById('optimalExchange').textContent = optimalExchange + '%';
document.getElementById('maxValue').textContent = formatNumber(Math.round(maxValue / 1000)) + 'k';
document.getElementById('totalPension').textContent = formatNumber(Math.round(optimalPension)) + ' kr';
// Update lifecycle chart
const ages = optimalValues.map(v => v.age);
const values = optimalValues.map(v => v.value / 1000); // Scale down for chart
lifecycleChart.data.labels = ages;
lifecycleChart.data.datasets[0].data = values;
lifecycleChart.update();
// Update pension chart with optimal exchange details
const salaryInput = parseInt(document.getElementById('salary').value);
const pensionContributionInput = parseFloat(document.getElementById('pensionContribution').value) / 100;
const exchangedAmount = salaryInput * pensionContributionInput * (optimalExchange / 100);
const taxSaved = exchangedAmount * calculateMarginalTax(salaryInput * 12) / 100;
const employerContributionsSaved = exchangedAmount * 0.3142;
pensionChart.data.datasets[0].data = [
Math.round(employerContributionsSaved * 12),
Math.round(taxSaved * 12),
Math.round((salaryInput * pensionContributionInput - exchangedAmount + exchangedAmount + taxSaved + employerContributionsSaved) * 12)
];
pensionChart.update();
// Update strategy tips
let tips = '';
if (optimalExchange === 0) {
tips = 'Baserat på dina parametrar är ingen löneväxling optimal. Detta kan bero på:';
tips += '<ul class="list-disc pl-5 mt-1">';
tips += '<li>Låg marginaltaxesats</li>';
tips += '<li>Hög värdering av nuvarande inkomst</li>';
tips += '<li>Lång tid till pension</li>';
tips += '</ul>';
} else if (optimalExchange < 50) {
tips = 'En del löneväxling (' + optimalExchange + '%) är optimal. Detta balanserar:';
tips += '<ul class="list-disc pl-5 mt-1">';
tips += '<li>Skattesparande nu</li>';
tips += '<li>Framtida pensionsinkomst</li>';
tips += '<li>Din subjektiva värdering av pengar över tid</li>';
tips += '</ul>';
} else if (optimalExchange < 100) {
tips = 'Hög löneväxling (' + optimalExchange + '%) är optimal. Detta tyder på:';
tips += '<ul class="list-disc pl-5 mt-1">';
tips += '<li>Hög marginaltaxesats</li>';
tips += '<li>Relativt låg värdering av nuvarande inkomst</li>';
tips += '<li>Fördelaktig pensionsavkastning</li>';
tips += '</ul>';
} else {
tips = 'Full löneväxling (100%) är optimal. Detta är typiskt när:';
tips += '<ul class="list-disc pl-5 mt-1">';
tips += '<li>Mycket hög marginaltaxesats</li>';
tips += '<li>Låg värdering av nuvarande inkomst</li>';
tips += '<li>Kort tid till pension</li>';
tips += '</ul>';
}
// Add specific recommendations based on parameters
if (yearsToRetirement < 10) {
tips += '<p class="mt-2">Med mindre än 10 år till pension kan löneväxling vara särskilt fördelaktigt.</p>';
}
if (calculateMarginalTax(salary * 12) > 0.5) {
tips += '<p class="mt-2">Din höga marginaltaxesats gör löneväxling extra attraktiv.</p>';
}
if (currentMultiplier < 2) {
tips += '<p class="mt-2">Din relativt låga värdering av nuvarande inkomst gör pensionssparande mer attraktivt.</p>';
}
document.getElementById('strategyTips').innerHTML = tips;
// Add animation classes
document.getElementById('optimalExchange').classList.add('animate-grow');
document.getElementById('maxValue').classList.add('animate-grow');
document.getElementById('totalPension').classList.add('animate-grow');
// Remove animation classes after animation completes
setTimeout(() => {
document.getElementById('optimalExchange').classList.remove('animate-grow');
document.getElementById('maxValue').classList.remove('animate-grow');
document.getElementById('totalPension').classList.remove('animate-grow');
}, 1500);
}
// Initialize the page
document.addEventListener('DOMContentLoaded', function() {
// Set up event listeners for all input changes
document.querySelectorAll('input[type="range"]').forEach(input => {
input.addEventListener('input', function() {
updateDisplayValues();
});
});
// Set up calculate button
document.getElementById('calculateBtn').addEventListener('click', function() {
calculateOptimalStrategy();
});
// Initialize display values and charts
updateDisplayValues();
initCharts();
// Calculate initial results
calculateOptimalStrategy();
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Jausing/misc-stuff" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>