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