wealth-planner-v1 / index.html
JayStormX8's picture
Add 3 files
d7e49c2 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Wealth Management Planner</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/[email protected]"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
@keyframes float {
0% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
100% { transform: translateY(0px); }
}
.floating {
animation: float 3s ease-in-out infinite;
}
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.report-card {
transition: all 0.3s ease;
}
.report-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.loading-dots:after {
content: '.';
animation: dots 1.5s steps(5, end) infinite;
}
@keyframes dots {
0%, 20% { content: '.'; }
40% { content: '..'; }
60% { content: '...'; }
80%, 100% { content: ''; }
}
.gradient-bg {
background: linear-gradient(135deg, #6b46c1 0%, #4299e1 50%, #38b2ac 100%);
}
.pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.progress-bar {
height: 4px;
background: linear-gradient(90deg, #4f46e5 0%, #10b981 50%, #f59e0b 100%);
animation: progress 2s ease-in-out infinite;
background-size: 200% 100%;
}
@keyframes progress {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.chart-container {
position: relative;
height: 300px;
width: 100%;
}
.glow {
box-shadow: 0 0 20px rgba(99, 102, 241, 0.5);
}
.financial-freedom-card {
background: linear-gradient(135deg, #10b981 0%, #3b82f6 100%);
color: white;
}
.milestone-marker {
width: 12px;
height: 12px;
border-radius: 50%;
position: absolute;
top: -6px;
left: 50%;
transform: translateX(-50%);
}
.milestone-line {
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 2px;
transform: translateX(-50%);
}
</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-transparent bg-clip-text bg-gradient-to-r from-indigo-600 to-blue-500 mb-2">AI Wealth Management Planner</h1>
<p class="text-lg text-gray-600">Get a personalized financial roadmap to achieve financial freedom</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Input Form -->
<div class="bg-white rounded-xl shadow-md p-6 lg:col-span-1 h-fit sticky top-8 glow">
<h2 class="text-2xl font-semibold text-gray-800 mb-6">Your Financial Profile</h2>
<form id="financialForm" class="space-y-4">
<div>
<label for="age" class="block text-sm font-medium text-gray-700 mb-1">Age</label>
<input type="number" id="age" min="18" max="100" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" required>
</div>
<div>
<label for="income" class="block text-sm font-medium text-gray-700 mb-1">Annual Income ($)</label>
<input type="number" id="income" min="0" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" required>
</div>
<div>
<label for="savings" class="block text-sm font-medium text-gray-700 mb-1">Current Savings ($)</label>
<input type="number" id="savings" min="0" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" required>
</div>
<div>
<label for="debt" class="block text-sm font-medium text-gray-700 mb-1">Current Debt ($)</label>
<input type="number" id="debt" min="0" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" value="0">
</div>
<div>
<label for="monthlySpending" class="block text-sm font-medium text-gray-700 mb-1">Monthly Spending ($)</label>
<input type="number" id="monthlySpending" min="0" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" value="3000">
</div>
<div>
<label for="riskTolerance" class="block text-sm font-medium text-gray-700 mb-1">Risk Tolerance</label>
<select id="riskTolerance" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
<option value="conservative">Conservative</option>
<option value="moderate" selected>Moderate</option>
<option value="aggressive">Aggressive</option>
</select>
</div>
<div>
<label for="retirementAge" class="block text-sm font-medium text-gray-700 mb-1">Desired Retirement Age</label>
<input type="number" id="retirementAge" min="18" max="100" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" value="65">
</div>
<div class="pt-2">
<button type="submit" id="generateBtn" class="w-full gradient-bg hover:opacity-90 text-white font-medium py-3 px-4 rounded-lg transition duration-200 flex items-center justify-center shadow-lg">
<span>Generate Wealth Plan</span>
<i class="fas fa-arrow-right ml-2"></i>
</button>
</div>
</form>
</div>
<!-- Results Section -->
<div class="bg-white rounded-xl shadow-md p-6 lg:col-span-2">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-semibold text-gray-800">Your Wealth Management Plan</h2>
<button id="saveReportBtn" class="bg-indigo-100 hover:bg-indigo-200 text-indigo-800 font-medium py-2 px-4 rounded-lg transition duration-200 hidden">
<i class="fas fa-save mr-2"></i>Save Report
</button>
</div>
<div id="resultsContainer">
<div class="bg-blue-50 border-l-4 border-blue-500 p-4 rounded-lg">
<div class="flex">
<div class="flex-shrink-0">
<i class="fas fa-info-circle text-blue-500 text-xl"></i>
</div>
<div class="ml-3">
<p class="text-sm text-blue-700">
Enter your financial details and click "Generate Wealth Plan" to get personalized recommendations.
</p>
</div>
</div>
</div>
</div>
<!-- Loading Animation -->
<div id="loadingContainer" class="hidden mt-8">
<div class="text-center">
<div class="w-24 h-24 mx-auto mb-6 relative">
<div class="w-full h-full rounded-full bg-indigo-100 flex items-center justify-center floating">
<i class="fas fa-piggy-bank text-indigo-600 text-4xl"></i>
</div>
<div class="absolute inset-0 rounded-full border-4 border-indigo-200 border-t-indigo-600 animate-spin"></div>
</div>
<h3 class="text-xl font-semibold text-gray-800 mb-2">Crunching your numbers</h3>
<p class="text-gray-600 mb-4">We're analyzing your financial profile and building a personalized roadmap...</p>
<div class="w-full bg-gray-200 rounded-full h-2.5 max-w-md mx-auto">
<div class="progress-bar h-2.5 rounded-full"></div>
</div>
<div class="mt-6 grid grid-cols-3 gap-4 max-w-md mx-auto">
<div class="bg-purple-50 p-3 rounded-lg pulse">
<i class="fas fa-chart-line text-purple-600 text-xl mb-2"></i>
<p class="text-xs text-purple-800">Projecting growth</p>
</div>
<div class="bg-green-50 p-3 rounded-lg pulse" style="animation-delay: 0.2s">
<i class="fas fa-money-bill-wave text-green-600 text-xl mb-2"></i>
<p class="text-xs text-green-800">Optimizing savings</p>
</div>
<div class="bg-blue-50 p-3 rounded-lg pulse" style="animation-delay: 0.4s">
<i class="fas fa-calendar-check text-blue-600 text-xl mb-2"></i>
<p class="text-xs text-blue-800">Planning milestones</p>
</div>
</div>
</div>
</div>
<!-- Saved Reports Section -->
<div id="savedReportsContainer" class="mt-12">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-semibold text-gray-800">Saved Reports</h3>
<button id="clearReportsBtn" class="text-red-500 hover:text-red-700 text-sm font-medium">
<i class="fas fa-trash-alt mr-1"></i>Clear All
</button>
</div>
<div id="savedReportsList" class="grid grid-cols-1 gap-4">
<!-- Reports will be added here dynamically -->
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const financialForm = document.getElementById('financialForm');
const generateBtn = document.getElementById('generateBtn');
const saveReportBtn = document.getElementById('saveReportBtn');
const clearReportsBtn = document.getElementById('clearReportsBtn');
const resultsContainer = document.getElementById('resultsContainer');
const loadingContainer = document.getElementById('loadingContainer');
const savedReportsList = document.getElementById('savedReportsList');
let currentReport = null;
let charts = [];
// Load saved reports from localStorage
loadSavedReports();
financialForm.addEventListener('submit', async function(e) {
e.preventDefault();
const age = parseInt(document.getElementById('age').value);
const income = parseInt(document.getElementById('income').value);
const savings = parseInt(document.getElementById('savings').value);
const debt = parseInt(document.getElementById('debt').value);
const monthlySpending = parseInt(document.getElementById('monthlySpending').value);
const riskTolerance = document.getElementById('riskTolerance').value;
const retirementAge = parseInt(document.getElementById('retirementAge').value);
// Show loading state
generateBtn.disabled = true;
generateBtn.innerHTML = '<span>Generating</span><span class="loading-dots"></span>';
resultsContainer.classList.add('hidden');
loadingContainer.classList.remove('hidden');
try {
// Generate AI response
const aiResponse = await generateWealthPlan(age, income, savings, debt, monthlySpending, riskTolerance, retirementAge);
// Calculate financial freedom projections
const projections = calculateProjections(age, income, savings, debt, monthlySpending, riskTolerance, retirementAge);
// Display results
displayResults(aiResponse, projections, { age, income, savings, debt, monthlySpending, riskTolerance, retirementAge });
// Store current report for potential saving
currentReport = {
data: { age, income, savings, debt, monthlySpending, riskTolerance, retirementAge },
plan: aiResponse,
projections: projections,
timestamp: new Date().toISOString()
};
// Show save button
saveReportBtn.classList.remove('hidden');
} catch (error) {
console.error('Error:', error);
resultsContainer.innerHTML = `
<div class="bg-red-50 border-l-4 border-red-500 p-4 rounded-lg fade-in">
<div class="flex">
<div class="flex-shrink-0">
<i class="fas fa-exclamation-circle text-red-500 text-xl"></i>
</div>
<div class="ml-3">
<p class="text-sm text-red-700">
An error occurred while generating your wealth plan. Please try again later.
</p>
</div>
</div>
</div>
`;
} finally {
// Reset button
generateBtn.disabled = false;
generateBtn.innerHTML = '<span>Generate Wealth Plan</span><i class="fas fa-arrow-right ml-2"></i>';
loadingContainer.classList.add('hidden');
resultsContainer.classList.remove('hidden');
}
});
saveReportBtn.addEventListener('click', function() {
if (currentReport) {
saveReport(currentReport);
currentReport = null;
saveReportBtn.classList.add('hidden');
// Show success message
const successMsg = document.createElement('div');
successMsg.className = 'bg-green-50 border-l-4 border-green-500 p-4 rounded-lg fade-in mb-4';
successMsg.innerHTML = `
<div class="flex">
<div class="flex-shrink-0">
<i class="fas fa-check-circle text-green-500 text-xl"></i>
</div>
<div class="ml-3">
<p class="text-sm text-green-700">
Report saved successfully!
</p>
</div>
</div>
`;
resultsContainer.insertBefore(successMsg, resultsContainer.firstChild);
// Remove message after 3 seconds
setTimeout(() => {
successMsg.classList.add('opacity-0', 'transition-opacity', 'duration-500');
setTimeout(() => successMsg.remove(), 500);
}, 3000);
}
});
clearReportsBtn.addEventListener('click', function() {
if (confirm('Are you sure you want to clear all saved reports?')) {
localStorage.removeItem('wealthReports');
savedReportsList.innerHTML = '';
}
});
async function generateWealthPlan(age, income, savings, debt, monthlySpending, riskTolerance, retirementAge) {
const yearsToRetirement = retirementAge - age;
const netWorth = savings - debt;
const annualSpending = monthlySpending * 12;
const financialFreedomNumber = annualSpending * 25; // 4% rule
// Simulate some processing time for a better UX
await new Promise(resolve => setTimeout(resolve, 2000));
// Construct the prompt for the AI
const prompt = `Create a comprehensive wealth management plan for a ${age}-year-old individual with the following financial profile:
- Annual Income: $${income.toLocaleString()}
- Current Savings: $${savings.toLocaleString()}
- Current Debt: $${debt.toLocaleString()}
- Net Worth: $${netWorth.toLocaleString()}
- Monthly Spending: $${monthlySpending.toLocaleString()}
- Annual Spending: $${annualSpending.toLocaleString()}
- Financial Freedom Target: $${financialFreedomNumber.toLocaleString()}
- Risk Tolerance: ${riskTolerance}
- Years Until Desired Retirement: ${yearsToRetirement}
Please provide detailed recommendations covering:
1. Emergency fund target
2. Debt repayment strategy
3. Retirement savings goals and investment allocation
4. Tax optimization strategies
5. Insurance needs assessment
6. Projected path to financial freedom
7. Investment strategy based on risk tolerance
8. Any other relevant financial planning considerations
Structure the response with clear headings and bullet points for easy reading. Tailor the advice to the specified risk tolerance and time horizon. Include specific numbers and percentages where applicable.`;
// Call the DeepSeek API
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer sk-0cce68e321854d11b01ee9227147a12d'
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: [
{
role: 'user',
content: prompt
}
],
temperature: 0.7,
max_tokens: 1500
})
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to generate wealth plan');
}
return data.choices[0].message.content;
}
function calculateProjections(age, income, savings, debt, monthlySpending, riskTolerance, retirementAge) {
const yearsToRetirement = retirementAge - age;
const annualSpending = monthlySpending * 12;
const financialFreedomNumber = annualSpending * 25; // 4% rule
// Determine expected returns based on risk tolerance
let expectedReturn;
switch(riskTolerance) {
case 'conservative':
expectedReturn = 0.05; // 5%
break;
case 'moderate':
expectedReturn = 0.07; // 7%
break;
case 'aggressive':
expectedReturn = 0.09; // 9%
break;
default:
expectedReturn = 0.07;
}
// Calculate savings rate (simplified)
const savingsRate = 0.2; // Assume 20% savings rate for this example
const annualSavings = income * savingsRate;
// Project growth over years
const projections = [];
let currentNetWorth = savings - debt;
for (let i = 0; i <= yearsToRetirement; i++) {
const currentAge = age + i;
const investmentGrowth = currentNetWorth * expectedReturn;
currentNetWorth += annualSavings + investmentGrowth;
projections.push({
age: currentAge,
year: new Date().getFullYear() + i,
netWorth: Math.round(currentNetWorth),
savings: Math.round(annualSavings),
investmentGrowth: Math.round(investmentGrowth)
});
// Check if we've reached financial freedom
if (currentNetWorth >= financialFreedomNumber) {
break;
}
}
// Calculate financial freedom age
let financialFreedomAge = age;
for (let i = 0; i < projections.length; i++) {
if (projections[i].netWorth >= financialFreedomNumber) {
financialFreedomAge = projections[i].age;
break;
}
}
return {
projections: projections,
financialFreedomAge: financialFreedomAge,
financialFreedomNumber: financialFreedomNumber,
annualSpending: annualSpending,
expectedReturn: expectedReturn,
savingsRate: savingsRate
};
}
function displayResults(aiResponse, projections, userData) {
// Destroy any existing charts
charts.forEach(chart => chart.destroy());
charts = [];
const yearsToRetirement = userData.retirementAge - userData.age;
const netWorth = userData.savings - userData.debt;
const annualSpending = userData.monthlySpending * 12;
// Prepare data for charts
const projectionYears = projections.projections.map(p => p.year);
const projectionNetWorth = projections.projections.map(p => p.netWorth);
const projectionSavings = projections.projections.map(p => p.savings);
const projectionGrowth = projections.projections.map(p => p.investmentGrowth);
// Create allocation chart data
const allocationData = {
conservative: [60, 30, 10],
moderate: [40, 50, 10],
aggressive: [20, 60, 20]
};
resultsContainer.innerHTML = `
<div class="space-y-8 fade-in">
<!-- Financial Snapshot -->
<div class="bg-white rounded-lg shadow-sm p-6 border border-gray-100 glow">
<h3 class="text-xl font-semibold text-gray-800 mb-4">Financial Snapshot</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
<div class="bg-gradient-to-br from-indigo-100 to-blue-100 p-4 rounded-lg">
<p class="text-sm text-indigo-600 font-medium">Age</p>
<p class="text-2xl font-bold text-indigo-800">${userData.age}</p>
</div>
<div class="bg-gradient-to-br from-green-100 to-teal-100 p-4 rounded-lg">
<p class="text-sm text-green-600 font-medium">Annual Income</p>
<p class="text-2xl font-bold text-green-800">$${userData.income.toLocaleString()}</p>
</div>
<div class="bg-gradient-to-br from-purple-100 to-pink-100 p-4 rounded-lg">
<p class="text-sm text-purple-600 font-medium">Net Worth</p>
<p class="text-2xl font-bold text-purple-800">$${netWorth.toLocaleString()}</p>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-gradient-to-br from-amber-100 to-yellow-100 p-4 rounded-lg">
<p class="text-sm text-amber-600 font-medium">Years to Retirement</p>
<p class="text-2xl font-bold text-amber-800">${yearsToRetirement}</p>
</div>
<div class="bg-gradient-to-br from-red-100 to-orange-100 p-4 rounded-lg">
<p class="text-sm text-red-600 font-medium">Risk Tolerance</p>
<p class="text-2xl font-bold text-red-800 capitalize">${userData.riskTolerance}</p>
</div>
</div>
</div>
<!-- Financial Freedom Projection -->
<div class="financial-freedom-card rounded-lg shadow-sm p-6 text-white">
<div class="flex flex-col md:flex-row justify-between items-start md:items-center">
<div>
<h3 class="text-xl font-semibold mb-2">Financial Freedom Projection</h3>
<p class="text-lg font-medium">$${projections.financialFreedomNumber.toLocaleString()} needed</p>
<p class="text-sm opacity-90">Based on annual spending of $${annualSpending.toLocaleString()}</p>
</div>
<div class="mt-4 md:mt-0 text-center">
<div class="text-4xl font-bold">${projections.financialFreedomAge}</div>
<div class="text-sm">Years old</div>
</div>
</div>
<div class="mt-6 chart-container">
<canvas id="netWorthChart"></canvas>
</div>
<div class="mt-4 grid grid-cols-3 gap-2 text-center">
<div class="bg-white bg-opacity-20 p-2 rounded">
<p class="text-xs opacity-80">Expected Return</p>
<p class="font-bold">${(projections.expectedReturn * 100).toFixed(1)}%</p>
</div>
<div class="bg-white bg-opacity-20 p-2 rounded">
<p class="text-xs opacity-80">Savings Rate</p>
<p class="font-bold">${(projections.savingsRate * 100).toFixed(0)}%</p>
</div>
<div class="bg-white bg-opacity-20 p-2 rounded">
<p class="text-xs opacity-80">Years to FI</p>
<p class="font-bold">${projections.financialFreedomAge - userData.age}</p>
</div>
</div>
</div>
<!-- Net Worth Growth Chart -->
<div class="bg-white rounded-lg shadow-sm p-6 border border-gray-100">
<h3 class="text-xl font-semibold text-gray-800 mb-4">Net Worth Growth Projection</h3>
<div class="chart-container">
<canvas id="growthChart"></canvas>
</div>
</div>
<!-- Investment Allocation -->
<div class="bg-white rounded-lg shadow-sm p-6 border border-gray-100">
<h3 class="text-xl font-semibold text-gray-800 mb-4">Recommended Investment Allocation</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="chart-container">
<canvas id="allocationChart"></canvas>
</div>
<div class="flex flex-col justify-center">
<div class="space-y-3">
<div class="flex items-center">
<div class="w-4 h-4 rounded-full bg-blue-500 mr-2"></div>
<span class="text-sm">Stocks</span>
</div>
<div class="flex items-center">
<div class="w-4 h-4 rounded-full bg-green-500 mr-2"></div>
<span class="text-sm">Bonds</span>
</div>
<div class="flex items-center">
<div class="w-4 h-4 rounded-full bg-yellow-500 mr-2"></div>
<span class="text-sm">Alternative Investments</span>
</div>
</div>
<div class="mt-6 bg-${userData.riskTolerance === 'conservative' ? 'blue' : userData.riskTolerance === 'moderate' ? 'purple' : 'red'}-50 p-4 rounded-lg">
<p class="text-sm text-${userData.riskTolerance === 'conservative' ? 'blue' : userData.riskTolerance === 'moderate' ? 'purple' : 'red'}-800">
<span class="font-medium">${userData.riskTolerance.charAt(0).toUpperCase() + userData.riskTolerance.slice(1)} Portfolio:</span>
${userData.riskTolerance === 'conservative' ?
'Lower risk with stable returns' :
userData.riskTolerance === 'moderate' ?
'Balanced approach for growth and stability' :
'Higher growth potential with increased volatility'}
</p>
</div>
</div>
</div>
</div>
<!-- Milestones Timeline -->
<div class="bg-white rounded-lg shadow-sm p-6 border border-gray-100">
<h3 class="text-xl font-semibold text-gray-800 mb-6">Financial Milestones</h3>
<div class="relative">
<!-- Timeline line -->
<div class="milestone-line bg-gray-200"></div>
<!-- Milestones -->
${createMilestones(userData.age, projections.financialFreedomAge, userData.retirementAge, netWorth, projections.financialFreedomNumber)}
</div>
</div>
<!-- AI Recommendations -->
<div class="bg-white rounded-lg shadow-sm p-6 border border-gray-100">
<h3 class="text-xl font-semibold text-gray-800 mb-4">Your Personalized Wealth Management Plan</h3>
<div class="prose max-w-none text-gray-700">
${formatAIResponse(aiResponse)}
</div>
</div>
</div>
`;
// Create charts after DOM is rendered
setTimeout(() => {
createNetWorthChart(projectionYears, projectionNetWorth, projections.financialFreedomNumber);
createGrowthChart(projectionYears, projectionNetWorth, projectionSavings, projectionGrowth);
createAllocationChart(allocationData[userData.riskTolerance]);
}, 100);
}
function createMilestones(currentAge, fiAge, retirementAge, currentNetWorth, fiNumber) {
const milestones = [
{
age: currentAge,
title: "Current Status",
description: `Net Worth: $${currentNetWorth.toLocaleString()}`,
color: "bg-indigo-500",
position: "left"
},
{
age: fiAge,
title: "Financial Freedom",
description: `Target: $${fiNumber.toLocaleString()}`,
color: "bg-green-500",
position: "right"
},
{
age: retirementAge,
title: "Retirement Age",
description: "Traditional retirement",
color: "bg-blue-500",
position: "left"
}
];
// Add a milestone at 50% of FI number if it's between current age and FI age
const halfWayAge = Math.floor(currentAge + (fiAge - currentAge) * 0.5);
if (halfWayAge > currentAge && halfWayAge < fiAge) {
milestones.splice(1, 0, {
age: halfWayAge,
title: "Halfway Point",
description: "50% to financial freedom",
color: "bg-yellow-500",
position: "right"
});
}
// Sort by age
milestones.sort((a, b) => a.age - b.age);
let html = '';
milestones.forEach(milestone => {
const isCurrent = milestone.age === currentAge;
const isFI = milestone.age === fiAge;
html += `
<div class="relative mb-8 ${milestone.position === 'left' ? 'pr-8 md:pr-16' : 'pl-8 md:pl-16'}">
<div class="milestone-marker ${milestone.color}"></div>
<div class="bg-white p-4 rounded-lg shadow-sm border border-gray-200 ${isFI ? 'financial-freedom-card text-white' : ''}">
<h4 class="font-semibold ${isFI ? '' : 'text-gray-800'}">${milestone.title}</h4>
<p class="text-sm ${isFI ? 'opacity-90' : 'text-gray-600'}">Age ${milestone.age}${milestone.description}</p>
${isCurrent ? '<div class="mt-2"><span class="inline-block px-2 py-1 text-xs font-medium bg-indigo-100 text-indigo-800 rounded-full">Current</span></div>' : ''}
${isFI ? '<div class="mt-2"><span class="inline-block px-2 py-1 text-xs font-medium bg-white bg-opacity-20 rounded-full">Goal</span></div>' : ''}
</div>
</div>
`;
});
return html;
}
function createNetWorthChart(years, netWorth, fiNumber) {
const ctx = document.getElementById('netWorthChart').getContext('2d');
const fiIndex = netWorth.findIndex(nw => nw >= fiNumber);
const fiYear = fiIndex !== -1 ? years[fiIndex] : null;
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: years,
datasets: [
{
label: 'Projected Net Worth',
data: netWorth,
borderColor: 'rgba(255, 255, 255, 0.8)',
backgroundColor: 'rgba(255, 255, 255, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.4
},
{
label: 'Financial Freedom Target',
data: Array(years.length).fill(fiNumber),
borderColor: 'rgba(255, 255, 255, 0.5)',
borderWidth: 1,
borderDash: [5, 5],
fill: false
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: {
color: 'white',
font: {
size: 12
}
}
},
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += '$' + context.parsed.y.toLocaleString();
}
return label;
}
}
}
},
scales: {
x: {
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: 'rgba(255, 255, 255, 0.8)'
}
},
y: {
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: 'rgba(255, 255, 255, 0.8)',
callback: function(value) {
return '$' + (value / 1000) + 'k';
}
}
}
},
annotation: fiYear ? {
annotations: {
line1: {
type: 'line',
xMin: fiYear,
xMax: fiYear,
borderColor: 'rgba(255, 255, 255, 0.7)',
borderWidth: 1,
label: {
content: 'FI Reached',
enabled: true,
position: 'top',
backgroundColor: 'rgba(16, 185, 129, 0.7)',
color: 'white',
font: {
weight: 'bold'
}
}
}
}
} : {}
}
});
charts.push(chart);
}
function createGrowthChart(years, netWorth, savings, growth) {
const ctx = document.getElementById('growthChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: years,
datasets: [
{
label: 'New Savings',
data: savings,
backgroundColor: 'rgba(59, 130, 246, 0.7)',
stack: 'stack_1'
},
{
label: 'Investment Growth',
data: growth,
backgroundColor: 'rgba(16, 185, 129, 0.7)',
stack: 'stack_1'
},
{
label: 'Net Worth',
data: netWorth,
type: 'line',
borderColor: 'rgba(139, 92, 246, 1)',
backgroundColor: 'rgba(0, 0, 0, 0)',
borderWidth: 3,
pointBackgroundColor: 'rgba(139, 92, 246, 1)',
pointRadius: 4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
labels: {
font: {
size: 12
}
}
},
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += '$' + context.parsed.y.toLocaleString();
}
return label;
}
}
}
},
scales: {
x: {
stacked: true,
grid: {
display: false
}
},
y: {
stacked: false,
ticks: {
callback: function(value) {
return '$' + (value / 1000) + 'k';
}
}
}
}
}
});
charts.push(chart);
}
function createAllocationChart(allocationData) {
const ctx = document.getElementById('allocationChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Stocks', 'Bonds', 'Alternatives'],
datasets: [{
data: allocationData,
backgroundColor: [
'rgba(59, 130, 246, 0.8)',
'rgba(16, 185, 129, 0.8)',
'rgba(234, 179, 8, 0.8)'
],
borderColor: [
'rgba(59, 130, 246, 1)',
'rgba(16, 185, 129, 1)',
'rgba(234, 179, 8, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right',
labels: {
boxWidth: 12,
padding: 20,
font: {
size: 12
}
}
},
datalabels: {
formatter: (value) => {
return value + '%';
},
color: '#fff',
font: {
weight: 'bold'
}
},
tooltip: {
callbacks: {
label: function(context) {
return context.label + ': ' + context.raw + '%';
}
}
}
},
cutout: '65%'
},
plugins: [ChartDataLabels]
});
charts.push(chart);
}
function formatAIResponse(text) {
// Convert markdown-like formatting to HTML
let html = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
html = html.replace(/^### (.*$)/gm, '<h3 class="text-lg font-semibold text-gray-800 mt-6 mb-3">$1</h3>');
html = html.replace(/^## (.*$)/gm, '<h2 class="text-xl font-semibold text-gray-800 mt-8 mb-4">$1</h2>');
html = html.replace(/^# (.*$)/gm, '<h1 class="text-2xl font-semibold text-gray-800 mt-10 mb-6">$1</h1>');
html = html.replace(/^- (.*$)/gm, '<li class="mb-2">$1</li>');
html = html.replace(/\n/g, '<br>');
// Wrap lists in ul tags
html = html.replace(/(<li class="mb-2">.*?<\/li>)+/g, function(match) {
return '<ul class="list-disc pl-5 space-y-1 my-3">' + match + '</ul>';
});
// Highlight numbers and percentages
html = html.replace(/(\$[\d,]+|\d+%)/g, '<span class="font-bold text-indigo-600">$1</span>');
return html;
}
function saveReport(report) {
// Get existing reports from localStorage
const savedReports = JSON.parse(localStorage.getItem('wealthReports')) || [];
// Add new report
savedReports.push(report);
// Save back to localStorage
localStorage.setItem('wealthReports', JSON.stringify(savedReports));
// Reload the saved reports list
loadSavedReports();
}
function loadSavedReports() {
const savedReports = JSON.parse(localStorage.getItem('wealthReports')) || [];
if (savedReports.length === 0) {
savedReportsList.innerHTML = `
<div class="bg-gray-50 rounded-lg p-4 text-center text-gray-500">
<i class="fas fa-folder-open text-2xl mb-2"></i>
<p>No saved reports yet</p>
</div>
`;
return;
}
savedReportsList.innerHTML = '';
// Display reports in reverse chronological order
savedReports.reverse().forEach((report, index) => {
const reportDate = new Date(report.timestamp);
const formattedDate = reportDate.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
const fiAge = report.projections?.financialFreedomAge || 'N/A';
const netWorth = report.data.savings - report.data.debt;
const reportCard = document.createElement('div');
reportCard.className = 'report-card bg-white rounded-lg shadow-sm p-4 border border-gray-100 hover:border-indigo-200 cursor-pointer';
reportCard.innerHTML = `
<div class="flex justify-between items-start">
<div>
<h4 class="font-medium text-gray-800">Wealth Plan - ${formattedDate}</h4>
<p class="text-sm text-gray-500 mt-1">
Age ${report.data.age}, Income: $${report.data.income.toLocaleString()}, Net Worth: $${netWorth.toLocaleString()}
</p>
<div class="mt-2 flex items-center">
<span class="inline-block px-2 py-1 text-xs font-medium ${fiAge <= report.data.retirementAge ? 'bg-green-100 text-green-800' : 'bg-amber-100 text-amber-800'} rounded-full">
FI Age: ${fiAge}
</span>
</div>
</div>
<button class="text-gray-400 hover:text-red-500 delete-report" data-index="${savedReports.length - 1 - index}">
<i class="fas fa-times"></i>
</button>
</div>
`;
reportCard.addEventListener('click', () => {
displayResults(report.plan, report.projections, report.data);
saveReportBtn.classList.add('hidden');
window.scrollTo({ top: 0, behavior: 'smooth' });
});
savedReportsList.appendChild(reportCard);
});
// Add event listeners to delete buttons
document.querySelectorAll('.delete-report').forEach(button => {
button.addEventListener('click', function(e) {
e.stopPropagation();
const index = parseInt(this.getAttribute('data-index'));
deleteReport(index);
});
});
}
function deleteReport(index) {
const savedReports = JSON.parse(localStorage.getItem('wealthReports')) || [];
if (index >= 0 && index < savedReports.length) {
if (confirm('Are you sure you want to delete this report?')) {
savedReports.splice(index, 1);
localStorage.setItem('wealthReports', JSON.stringify(savedReports));
loadSavedReports();
}
}
}
});
</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=JayStormX8/wealth-planner-v1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>