finflow-pro / index.html
ziggylott's picture
Upload index.html with huggingface_hub
d46b500 verified
Raw
History Blame Contribute Delete
25 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>πŸ’° FinFlow Pro</title>
<style>
:root {
--bg: #0d1117;
--bg2: #161b22;
--card: #21262d;
--border: #30363d;
--accent: #58a6ff;
--green: #3fb950;
--red: #f85149;
--yellow: #d29922;
--text: #c9d1d9;
--muted: #8b949e;
--radius: 12px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; min-height: 100vh; }
.app { max-width: 960px; margin: 0 auto; padding: 20px; }
header { display: flex; align-items: center; justify-content: space-between; padding: 20px 0 24px; border-bottom: 1px solid var(--border); margin-bottom: 24px; }
.logo { display: flex; align-items: center; gap: 10px; }
.logo-icon { font-size: 2rem; }
.logo h1 { font-size: 1.4rem; color: var(--accent); }
.logo p { font-size: 0.75rem; color: var(--muted); }
.balance-badge { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 18px; text-align: right; }
.balance-badge .label { font-size: 0.7rem; color: var(--muted); }
.balance-badge .amount { font-size: 1.5rem; font-weight: bold; color: var(--green); }
nav { display: flex; gap: 6px; margin-bottom: 24px; flex-wrap: wrap; }
.nav-btn { background: none; border: 1px solid var(--border); color: var(--muted); padding: 8px 14px; border-radius: 8px; cursor: pointer; font-size: 0.85rem; font-family: inherit; transition: all 0.15s; }
.nav-btn.active, .nav-btn:hover { border-color: var(--accent); color: var(--accent); background: rgba(88,166,255,0.08); }
.panel { display: none; }
.panel.active { display: block; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 600px) { .grid-2 { grid-template-columns: 1fr; } }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 700px) { .grid-3 { grid-template-columns: 1fr 1fr; } }
.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; }
.card h3 { font-size: 0.85rem; color: var(--muted); margin-bottom: 8px; }
.card .value { font-size: 1.8rem; font-weight: bold; }
.card .change { font-size: 0.75rem; margin-top: 4px; }
.up { color: var(--green); }
.down { color: var(--red); }
.neutral { color: var(--yellow); }
.section-title { font-size: 1rem; font-weight: bold; margin-bottom: 14px; color: var(--text); }
.full-card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 16px; }
/* Budget */
.budget-row { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.budget-row .label { flex: 0 0 130px; font-size: 0.85rem; }
.progress { flex: 1; background: var(--bg2); border-radius: 6px; height: 10px; overflow: hidden; }
.progress-bar { height: 100%; border-radius: 6px; transition: width 0.5s; }
.budget-row .pct { flex: 0 0 40px; font-size: 0.8rem; text-align: right; }
/* Transactions */
.tx-row { display: flex; align-items: center; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--border); }
.tx-row:last-child { border-bottom: none; }
.tx-icon { font-size: 1.4rem; }
.tx-info { flex: 1; }
.tx-name { font-size: 0.9rem; }
.tx-cat { font-size: 0.75rem; color: var(--muted); }
.tx-amount { font-weight: bold; font-size: 0.95rem; }
/* Forms */
.form-row { display: flex; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; }
.form-row input, .form-row select {
flex: 1; min-width: 120px; background: var(--bg2); border: 1px solid var(--border); color: var(--text);
padding: 9px 12px; border-radius: 8px; font-size: 0.9rem; font-family: inherit;
}
.form-row input:focus, .form-row select:focus { outline: none; border-color: var(--accent); }
select option { background: var(--bg2); }
.btn { background: var(--accent); color: #0d1117; padding: 9px 18px; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 0.9rem; font-family: inherit; transition: opacity 0.15s; }
.btn:hover { opacity: 0.85; }
.btn.danger { background: var(--red); color: white; }
.btn.ghost { background: none; border: 1px solid var(--border); color: var(--text); }
.btn.ghost:hover { border-color: var(--accent); color: var(--accent); }
/* Goals */
.goal-card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; margin-bottom: 12px; }
.goal-top { display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px; }
.goal-name { font-weight: bold; }
.goal-pct { font-size: 0.75rem; color: var(--muted); }
.goal-progress { background: var(--bg2); border-radius: 6px; height: 8px; overflow: hidden; margin-bottom: 8px; }
.goal-bar { height: 100%; background: var(--accent); border-radius: 6px; transition: width 0.5s; }
.goal-amounts { font-size: 0.8rem; color: var(--muted); }
/* AI Chat */
.chat-box { background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; height: 300px; overflow-y: auto; margin-bottom: 12px; }
.msg { margin-bottom: 12px; }
.msg.user { text-align: right; }
.msg.user .bubble { background: var(--accent); color: #0d1117; display: inline-block; padding: 8px 14px; border-radius: 16px 16px 4px 16px; font-size: 0.9rem; max-width: 75%; }
.msg.ai .bubble { background: var(--card); color: var(--text); display: inline-block; padding: 8px 14px; border-radius: 16px 16px 16px 4px; font-size: 0.9rem; max-width: 85%; white-space: pre-line; }
.msg .name { font-size: 0.7rem; color: var(--muted); margin-bottom: 3px; }
.chat-input-row { display: flex; gap: 8px; }
.chat-input-row input { flex: 1; background: var(--bg2); border: 1px solid var(--border); color: var(--text); padding: 10px 14px; border-radius: 8px; font-family: inherit; font-size: 0.9rem; }
.chat-input-row input:focus { outline: none; border-color: var(--accent); }
.tag { display: inline-block; padding: 2px 8px; border-radius: 6px; font-size: 0.7rem; font-weight: bold; }
</style>
</head>
<body>
<div class="app">
<header>
<div class="logo">
<div class="logo-icon">πŸ’°</div>
<div>
<h1>FinFlow Pro</h1>
<p>Personal Finance Dashboard</p>
</div>
</div>
<div class="balance-badge">
<div class="label">Net Worth</div>
<div class="amount" id="netWorthDisplay">$0</div>
</div>
</header>
<nav>
<button class="nav-btn active" onclick="showPanel('dashboard')">πŸ“Š Dashboard</button>
<button class="nav-btn" onclick="showPanel('transactions')">πŸ’³ Transactions</button>
<button class="nav-btn" onclick="showPanel('budget')">πŸ“‹ Budget</button>
<button class="nav-btn" onclick="showPanel('goals')">🎯 Goals</button>
<button class="nav-btn" onclick="showPanel('ai')">πŸ€– AI Advisor</button>
</nav>
<!-- DASHBOARD -->
<div id="panel-dashboard" class="panel active">
<div class="grid-3" style="margin-bottom:20px">
<div class="card">
<h3>Monthly Income</h3>
<div class="value up" id="incomeDisplay">$0</div>
<div class="change up">↑ This month</div>
</div>
<div class="card">
<h3>Monthly Expenses</h3>
<div class="value down" id="expenseDisplay">$0</div>
<div class="change down">↓ Spending</div>
</div>
<div class="card">
<h3>Monthly Savings</h3>
<div class="value" id="savingsDisplay" style="color:var(--accent)">$0</div>
<div class="change neutral">Income - Expenses</div>
</div>
</div>
<div class="full-card">
<div class="section-title">πŸ’³ Recent Transactions</div>
<div id="recentTxList"><p style="color:var(--muted);font-size:0.85rem">No transactions yet. Add some in the Transactions tab!</p></div>
</div>
<div class="full-card">
<div class="section-title">πŸ“‹ Budget Overview</div>
<div id="dashBudget"><p style="color:var(--muted);font-size:0.85rem">No budget set. Configure in the Budget tab!</p></div>
</div>
</div>
<!-- TRANSACTIONS -->
<div id="panel-transactions" class="panel">
<div class="full-card" style="margin-bottom:16px">
<div class="section-title">βž• Add Transaction</div>
<div class="form-row">
<input type="text" id="txName" placeholder="Description (e.g. Grocery run)">
<input type="number" id="txAmount" placeholder="Amount (e.g. 85.50)" step="0.01">
<select id="txType">
<option value="expense">πŸ’Έ Expense</option>
<option value="income">πŸ’΅ Income</option>
</select>
<select id="txCat">
<option value="Food">πŸ” Food</option>
<option value="Transport">πŸš— Transport</option>
<option value="Housing">🏠 Housing</option>
<option value="Entertainment">🎬 Entertainment</option>
<option value="Health">πŸ’Š Health</option>
<option value="Shopping">πŸ›οΈ Shopping</option>
<option value="Salary">πŸ’Ό Salary</option>
<option value="Other">πŸ“¦ Other</option>
</select>
<button class="btn" onclick="addTransaction()">Add</button>
</div>
</div>
<div class="full-card">
<div class="section-title">πŸ“œ All Transactions</div>
<div id="txList"><p style="color:var(--muted);font-size:0.85rem">No transactions yet.</p></div>
</div>
</div>
<!-- BUDGET -->
<div id="panel-budget" class="panel">
<div class="full-card" style="margin-bottom:16px">
<div class="section-title">βš™οΈ Set Monthly Budget</div>
<div class="form-row">
<select id="budgetCat">
<option value="Food">πŸ” Food</option>
<option value="Transport">πŸš— Transport</option>
<option value="Housing">🏠 Housing</option>
<option value="Entertainment">🎬 Entertainment</option>
<option value="Health">πŸ’Š Health</option>
<option value="Shopping">πŸ›οΈ Shopping</option>
<option value="Other">πŸ“¦ Other</option>
</select>
<input type="number" id="budgetAmount" placeholder="Monthly budget ($)" step="1">
<button class="btn" onclick="setBudget()">Set Budget</button>
</div>
</div>
<div class="full-card">
<div class="section-title">πŸ“Š Budget vs. Actual</div>
<div id="budgetList"><p style="color:var(--muted);font-size:0.85rem">Set a budget above to get started!</p></div>
</div>
</div>
<!-- GOALS -->
<div id="panel-goals" class="panel">
<div class="full-card" style="margin-bottom:16px">
<div class="section-title">πŸ†• Add Savings Goal</div>
<div class="form-row">
<input type="text" id="goalName" placeholder="Goal name (e.g. Emergency Fund)">
<input type="number" id="goalTarget" placeholder="Target amount ($)" step="1">
<input type="number" id="goalCurrent" placeholder="Current amount ($)" step="1">
<button class="btn" onclick="addGoal()">Add Goal</button>
</div>
</div>
<div id="goalsList"><p style="color:var(--muted);font-size:0.85rem">No goals set yet.</p></div>
</div>
<!-- AI ADVISOR -->
<div id="panel-ai" class="panel">
<div class="full-card">
<div class="section-title">πŸ€– AI Financial Advisor</div>
<p style="color:var(--muted);font-size:0.85rem;margin-bottom:12px">Powered by Grok xAI. Add your API key to unlock personalized advice. Get yours free at <a href="https://x.ai" target="_blank" style="color:var(--accent)">x.ai</a>.</p>
<div class="form-row" style="margin-bottom:12px">
<input type="password" id="aiKey" placeholder="xai-... (optional)" oninput="saveAiKey(this.value)">
</div>
<div class="chat-box" id="chatBox"></div>
<div class="chat-input-row">
<input type="text" id="chatInput" placeholder="Ask about your finances..." onkeydown="if(event.key==='Enter')sendAiMsg()">
<button class="btn" onclick="sendAiMsg()">Send</button>
</div>
<div style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap">
<button class="btn ghost" onclick="quickQ('How should I allocate my income?')">πŸ’‘ Income allocation</button>
<button class="btn ghost" onclick="quickQ('Tips to reduce my expenses')">βœ‚οΈ Cut expenses</button>
<button class="btn ghost" onclick="quickQ('Best strategies to build an emergency fund')">🚨 Emergency fund</button>
</div>
</div>
</div>
</div>
<script>
const CAT_ICONS = { Food:'πŸ”', Transport:'πŸš—', Housing:'🏠', Entertainment:'🎬', Health:'πŸ’Š', Shopping:'πŸ›οΈ', Salary:'πŸ’Ό', Other:'πŸ“¦' };
let data = { transactions: [], budgets: {}, goals: [] };
function save() { localStorage.setItem('ff-data', JSON.stringify(data)); }
function load() { try { data = JSON.parse(localStorage.getItem('ff-data')) || { transactions: [], budgets: {}, goals: [] }; } catch { } }
function saveAiKey(v) { if(v) localStorage.setItem('ff-ai-key', v); }
function showPanel(name) {
document.querySelectorAll('.nav-btn').forEach(b => b.classList.toggle('active', b.getAttribute('onclick').includes(name)));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
document.getElementById('panel-' + name).classList.add('active');
if (name === 'dashboard') renderDashboard();
if (name === 'transactions') renderTransactions();
if (name === 'budget') renderBudget();
if (name === 'goals') renderGoals();
}
function fmt(n) { return '$' + Math.abs(n).toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2}); }
function addTransaction() {
const name = document.getElementById('txName').value.trim();
const amount = parseFloat(document.getElementById('txAmount').value);
const type = document.getElementById('txType').value;
const cat = document.getElementById('txCat').value;
if (!name || !amount) return alert('Please fill in description and amount.');
data.transactions.unshift({ id: Date.now(), name, amount, type, cat, date: new Date().toLocaleDateString() });
save();
document.getElementById('txName').value = '';
document.getElementById('txAmount').value = '';
renderTransactions();
renderDashboard();
}
function renderTransactions() {
const el = document.getElementById('txList');
if (!data.transactions.length) { el.innerHTML = '<p style="color:var(--muted);font-size:0.85rem">No transactions yet.</p>'; return; }
el.innerHTML = data.transactions.map(t => `
<div class="tx-row">
<div class="tx-icon">${CAT_ICONS[t.cat]||'πŸ“¦'}</div>
<div class="tx-info"><div class="tx-name">${t.name}</div><div class="tx-cat">${t.cat} Β· ${t.date}</div></div>
<div class="tx-amount ${t.type==='income'?'up':'down'}">${t.type==='income'?'+':'-'}${fmt(t.amount)}</div>
<button class="btn danger" style="padding:4px 10px;font-size:0.75rem" onclick="deleteTx(${t.id})">βœ•</button>
</div>`).join('');
}
function deleteTx(id) {
data.transactions = data.transactions.filter(t => t.id !== id);
save(); renderTransactions(); renderDashboard();
}
function getMonthlyTotals() {
const now = new Date();
const month = now.getMonth(); const year = now.getFullYear();
const monthly = data.transactions.filter(t => {
const d = new Date(t.date); return d.getMonth() === month && d.getFullYear() === year;
});
const income = monthly.filter(t => t.type === 'income').reduce((s, t) => s + t.amount, 0);
const expenses = monthly.filter(t => t.type === 'expense').reduce((s, t) => s + t.amount, 0);
const byCategory = {};
monthly.filter(t => t.type === 'expense').forEach(t => { byCategory[t.cat] = (byCategory[t.cat]||0) + t.amount; });
return { income, expenses, savings: income - expenses, byCategory };
}
function renderDashboard() {
const { income, expenses, savings, byCategory } = getMonthlyTotals();
document.getElementById('incomeDisplay').textContent = fmt(income);
document.getElementById('expenseDisplay').textContent = fmt(expenses);
document.getElementById('savingsDisplay').textContent = fmt(savings);
document.getElementById('savingsDisplay').className = 'value ' + (savings >= 0 ? 'up' : 'down');
const netWorth = data.goals.reduce((s, g) => s + g.current, 0) + (income - expenses) * 3;
document.getElementById('netWorthDisplay').textContent = fmt(netWorth);
const recent = data.transactions.slice(0, 5);
document.getElementById('recentTxList').innerHTML = recent.length
? recent.map(t => `<div class="tx-row"><div class="tx-icon">${CAT_ICONS[t.cat]||'πŸ“¦'}</div><div class="tx-info"><div class="tx-name">${t.name}</div><div class="tx-cat">${t.cat}</div></div><div class="tx-amount ${t.type==='income'?'up':'down'}">${t.type==='income'?'+':'-'}${fmt(t.amount)}</div></div>`).join('')
: '<p style="color:var(--muted);font-size:0.85rem">No recent transactions.</p>';
const budgetHTML = Object.entries(data.budgets).map(([cat, budget]) => {
const spent = byCategory[cat] || 0;
const pct = Math.min(100, Math.round((spent / budget) * 100));
const color = pct > 90 ? 'var(--red)' : pct > 70 ? 'var(--yellow)' : 'var(--green)';
return `<div class="budget-row"><div class="label">${CAT_ICONS[cat]||'πŸ“¦'} ${cat}</div><div class="progress"><div class="progress-bar" style="width:${pct}%;background:${color}"></div></div><div class="pct" style="color:${color}">${pct}%</div></div>`;
}).join('');
document.getElementById('dashBudget').innerHTML = budgetHTML || '<p style="color:var(--muted);font-size:0.85rem">Set a budget to see overview.</p>';
}
function setBudget() {
const cat = document.getElementById('budgetCat').value;
const amount = parseFloat(document.getElementById('budgetAmount').value);
if (!amount) return alert('Enter a valid budget amount.');
data.budgets[cat] = amount;
save();
document.getElementById('budgetAmount').value = '';
renderBudget();
}
function renderBudget() {
const el = document.getElementById('budgetList');
if (!Object.keys(data.budgets).length) { el.innerHTML = '<p style="color:var(--muted);font-size:0.85rem">Set a budget above to get started!</p>'; return; }
const { byCategory } = getMonthlyTotals();
el.innerHTML = Object.entries(data.budgets).map(([cat, budget]) => {
const spent = byCategory[cat] || 0;
const pct = Math.min(100, Math.round((spent / budget) * 100));
const color = pct > 90 ? 'var(--red)' : pct > 70 ? 'var(--yellow)' : 'var(--green)';
return `<div class="budget-row" style="margin-bottom:16px">
<div class="label" style="flex:0 0 120px">${CAT_ICONS[cat]||'πŸ“¦'} ${cat}</div>
<div class="progress"><div class="progress-bar" style="width:${pct}%;background:${color}"></div></div>
<div class="pct" style="color:${color};flex:0 0 50px">${pct}%</div>
<div style="font-size:0.75rem;color:var(--muted);flex:0 0 120px">${fmt(spent)} / ${fmt(budget)}</div>
<button class="btn danger" style="padding:4px 8px;font-size:0.75rem" onclick="deleteBudget('${cat}')">βœ•</button>
</div>`;
}).join('');
}
function deleteBudget(cat) { delete data.budgets[cat]; save(); renderBudget(); renderDashboard(); }
function addGoal() {
const name = document.getElementById('goalName').value.trim();
const target = parseFloat(document.getElementById('goalTarget').value);
const current = parseFloat(document.getElementById('goalCurrent').value) || 0;
if (!name || !target) return alert('Please fill in goal name and target amount.');
data.goals.push({ id: Date.now(), name, target, current });
save();
document.getElementById('goalName').value = '';
document.getElementById('goalTarget').value = '';
document.getElementById('goalCurrent').value = '';
renderGoals();
}
function renderGoals() {
const el = document.getElementById('goalsList');
if (!data.goals.length) { el.innerHTML = '<p style="color:var(--muted);font-size:0.85rem">No goals set yet.</p>'; return; }
el.innerHTML = data.goals.map(g => {
const pct = Math.min(100, Math.round((g.current / g.target) * 100));
return `<div class="goal-card">
<div class="goal-top"><div class="goal-name">🎯 ${g.name}</div><div class="goal-pct">${pct}% complete</div></div>
<div class="goal-progress"><div class="goal-bar" style="width:${pct}%"></div></div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px">
<div class="goal-amounts">${fmt(g.current)} saved of ${fmt(g.target)} goal</div>
<div style="display:flex;gap:8px">
<button class="btn ghost" style="padding:4px 10px;font-size:0.75rem" onclick="contributeGoal(${g.id})">+ Add</button>
<button class="btn danger" style="padding:4px 10px;font-size:0.75rem" onclick="deleteGoal(${g.id})">βœ•</button>
</div>
</div>
</div>`;
}).join('');
}
function contributeGoal(id) {
const amt = parseFloat(prompt('How much to add to this goal?'));
if (!amt) return;
const g = data.goals.find(g => g.id === id);
if (g) { g.current = Math.min(g.target, g.current + amt); save(); renderGoals(); }
}
function deleteGoal(id) { data.goals = data.goals.filter(g => g.id !== id); save(); renderGoals(); }
// AI Chat
function quickQ(q) { document.getElementById('chatInput').value = q; sendAiMsg(); }
async function sendAiMsg() {
const input = document.getElementById('chatInput');
const msg = input.value.trim();
if (!msg) return;
input.value = '';
const chatBox = document.getElementById('chatBox');
chatBox.innerHTML += `<div class="msg user"><div class="name">You</div><div class="bubble">${msg}</div></div>`;
chatBox.scrollTop = chatBox.scrollHeight;
const apiKey = document.getElementById('aiKey').value.trim() || localStorage.getItem('ff-ai-key') || '';
const { income, expenses, savings, byCategory } = getMonthlyTotals();
const context = `User's financial data: Monthly income: ${fmt(income)}, expenses: ${fmt(expenses)}, savings: ${fmt(savings)}. Expense breakdown: ${JSON.stringify(byCategory)}. Active goals: ${data.goals.map(g => `${g.name}: ${fmt(g.current)}/${fmt(g.target)}`).join(', ') || 'none'}.`;
if (!apiKey) {
const tips = [
"Based on your data, try the 50/30/20 rule: 50% needs, 30% wants, 20% savings.",
"Building an emergency fund of 3-6 months of expenses is a great first goal!",
"Track every expense for 30 days to find hidden spending patterns.",
"Automate your savings by setting up automatic transfers on payday.",
"Review subscriptions monthly β€” most people have 2-3 forgotten ones.",
];
const tip = tips[Math.floor(Math.random() * tips.length)];
chatBox.innerHTML += `<div class="msg ai"><div class="name">πŸ’° FinFlow AI</div><div class="bubble">πŸ’‘ ${tip}\n\n(Add your xAI API key above for personalized advice based on your actual data!)</div></div>`;
chatBox.scrollTop = chatBox.scrollHeight;
return;
}
chatBox.innerHTML += `<div class="msg ai" id="typing"><div class="name">πŸ’° FinFlow AI</div><div class="bubble">Thinking...</div></div>`;
chatBox.scrollTop = chatBox.scrollHeight;
try {
const res = await fetch('https://api.x.ai/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
body: JSON.stringify({
model: 'grok-3-latest',
messages: [
{ role: 'system', content: 'You are a helpful personal finance advisor. Be concise, practical, and encouraging. ' + context },
{ role: 'user', content: msg }
],
max_tokens: 400
})
});
const d = await res.json();
const reply = d.choices?.[0]?.message?.content || 'Could not get a response.';
document.getElementById('typing')?.remove();
chatBox.innerHTML += `<div class="msg ai"><div class="name">πŸ’° FinFlow AI</div><div class="bubble">${reply}</div></div>`;
} catch {
document.getElementById('typing')?.remove();
chatBox.innerHTML += `<div class="msg ai"><div class="name">πŸ’° FinFlow AI</div><div class="bubble">πŸ’‘ Unable to connect to AI. Try the 50/30/20 rule: 50% needs, 30% wants, 20% savings!</div></div>`;
}
chatBox.scrollTop = chatBox.scrollHeight;
}
// Init
load();
renderDashboard();
const savedKey = localStorage.getItem('ff-ai-key');
if (savedKey) document.getElementById('aiKey').value = savedKey;
// Add welcome message
document.getElementById('chatBox').innerHTML = '<div class="msg ai"><div class="name">πŸ’° FinFlow AI</div><div class="bubble">πŸ‘‹ Welcome to FinFlow Pro! Add your transactions, set a budget, and create savings goals to get started.\n\nAsk me anything about personal finance, or add your xAI API key above for personalized advice!</div></div>';
</script>
</body>
</html>