Spaces:
Running
Running
| <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> |