Spaces:
Running
Running
Upload 9 files
Browse files- README.md +74 -0
- calculators.html +905 -0
- index.html +409 -0
- insights.html +860 -0
- portfolio.html +673 -0
- risk.html +618 -0
- shared.css +772 -0
- shared.js +172 -0
- tracker.html +551 -0
README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: FinWise — AI Personal Finance Platform
|
| 3 |
+
emoji: 📈
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: static
|
| 7 |
+
pinned: false
|
| 8 |
+
license: mit
|
| 9 |
+
short_description: Beginner-friendly stock & portfolio management web app
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# 📈 FinWise — AI-Powered Personal Finance Platform
|
| 13 |
+
|
| 14 |
+
A **comprehensive, beginner-friendly** stock and mutual fund portfolio management platform built for clarity, interactivity, and smart financial guidance.
|
| 15 |
+
|
| 16 |
+
## 🚀 Features
|
| 17 |
+
|
| 18 |
+
| Module | Description |
|
| 19 |
+
|--------|-------------|
|
| 20 |
+
| 🏠 **Dashboard** | Portfolio overview, performance chart, market snapshot |
|
| 21 |
+
| 🏗️ **Portfolio Builder** | 3-step guided portfolio creation with live preview |
|
| 22 |
+
| 🎯 **Risk Analyzer** | Risk quiz, gauge, scenario simulation, rebalancing suggestions |
|
| 23 |
+
| 📈 **Investment Tracker** | Holdings table, editable cost basis, P&L, allocation chart |
|
| 24 |
+
| 🧮 **Calculators** | Growth, Retirement, SIP/Recurring, Inflation Impact |
|
| 25 |
+
| 💡 **AI Insights** | Claude-powered chat advisor, tips, concepts, glossary |
|
| 26 |
+
|
| 27 |
+
## 🛠 Tech Stack
|
| 28 |
+
|
| 29 |
+
- **Pure HTML/CSS/JavaScript** — no build step required
|
| 30 |
+
- **Chart.js** — all charts and visualizations
|
| 31 |
+
- **Claude API** — AI advisor in the Insights tab
|
| 32 |
+
- **localStorage** — portfolio persistence across sessions
|
| 33 |
+
- **Google Fonts** — Syne + DM Sans typography
|
| 34 |
+
|
| 35 |
+
## 📁 File Structure
|
| 36 |
+
|
| 37 |
+
```
|
| 38 |
+
finwise/
|
| 39 |
+
├── index.html # Dashboard
|
| 40 |
+
├── portfolio.html # Portfolio Builder
|
| 41 |
+
├── risk.html # Risk Analyzer
|
| 42 |
+
├── tracker.html # Investment Tracker
|
| 43 |
+
├── calculators.html # Financial Calculators
|
| 44 |
+
├── insights.html # AI Insights
|
| 45 |
+
├── shared.css # Design system & styles
|
| 46 |
+
├── shared.js # Utilities, data, helpers
|
| 47 |
+
└── README.md
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
## 🎨 Design
|
| 51 |
+
|
| 52 |
+
- **Dark luxury fintech** aesthetic
|
| 53 |
+
- **Syne** (headings) + **DM Sans** (body) typography
|
| 54 |
+
- Cyan & Emerald accent palette on deep navy
|
| 55 |
+
- Mobile-first responsive design with bottom navigation
|
| 56 |
+
- Animated stats, hover effects, smooth transitions
|
| 57 |
+
|
| 58 |
+
## 🤖 AI Features
|
| 59 |
+
|
| 60 |
+
The **AI Advisor** in the Insights tab uses Claude claude-sonnet-4-20250514 to provide:
|
| 61 |
+
- Personalized portfolio analysis based on your actual holdings
|
| 62 |
+
- Plain-language explanations of investing concepts
|
| 63 |
+
- Context-aware rebalancing suggestions
|
| 64 |
+
- Smart financial Q&A
|
| 65 |
+
|
| 66 |
+
## 📱 Responsive
|
| 67 |
+
|
| 68 |
+
- Full sidebar navigation on desktop
|
| 69 |
+
- Bottom tab bar on mobile
|
| 70 |
+
- All charts resize gracefully
|
| 71 |
+
|
| 72 |
+
---
|
| 73 |
+
|
| 74 |
+
*FinWise is an educational tool. It does not constitute licensed financial advice.*
|
calculators.html
ADDED
|
@@ -0,0 +1,905 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>FinWise — Calculators</title>
|
| 7 |
+
<link rel="stylesheet" href="shared.css">
|
| 8 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
| 9 |
+
<style>
|
| 10 |
+
.calc-tabs {
|
| 11 |
+
display: flex;
|
| 12 |
+
gap: 8px;
|
| 13 |
+
flex-wrap: wrap;
|
| 14 |
+
margin-bottom: 28px;
|
| 15 |
+
}
|
| 16 |
+
.calc-tab {
|
| 17 |
+
display: flex;
|
| 18 |
+
align-items: center;
|
| 19 |
+
gap: 8px;
|
| 20 |
+
padding: 12px 20px;
|
| 21 |
+
border-radius: var(--r);
|
| 22 |
+
border: 1px solid var(--border);
|
| 23 |
+
background: var(--card);
|
| 24 |
+
color: var(--text2);
|
| 25 |
+
font-family: var(--font-body);
|
| 26 |
+
font-size: 14px;
|
| 27 |
+
font-weight: 600;
|
| 28 |
+
cursor: pointer;
|
| 29 |
+
transition: all var(--transition);
|
| 30 |
+
white-space: nowrap;
|
| 31 |
+
}
|
| 32 |
+
.calc-tab:hover { border-color: var(--border2); color: var(--text); transform: translateY(-1px); }
|
| 33 |
+
.calc-tab.active {
|
| 34 |
+
border-color: var(--cyan);
|
| 35 |
+
background: rgba(34,211,238,0.08);
|
| 36 |
+
color: var(--cyan);
|
| 37 |
+
box-shadow: var(--glow-c);
|
| 38 |
+
}
|
| 39 |
+
.calc-tab-icon { font-size: 20px; }
|
| 40 |
+
|
| 41 |
+
.calc-panel { display: none; }
|
| 42 |
+
.calc-panel.active { display: block; }
|
| 43 |
+
|
| 44 |
+
.result-hero {
|
| 45 |
+
text-align: center;
|
| 46 |
+
padding: 32px 20px;
|
| 47 |
+
background: linear-gradient(135deg, rgba(34,211,238,0.06), rgba(16,185,129,0.04));
|
| 48 |
+
border: 1px solid var(--border2);
|
| 49 |
+
border-radius: var(--r-lg);
|
| 50 |
+
margin-bottom: 24px;
|
| 51 |
+
position: relative;
|
| 52 |
+
overflow: hidden;
|
| 53 |
+
}
|
| 54 |
+
.result-hero::before {
|
| 55 |
+
content: '';
|
| 56 |
+
position: absolute;
|
| 57 |
+
width: 300px; height: 300px;
|
| 58 |
+
border-radius: 50%;
|
| 59 |
+
background: radial-gradient(circle, rgba(34,211,238,0.06) 0%, transparent 70%);
|
| 60 |
+
top: -100px; left: 50%;
|
| 61 |
+
transform: translateX(-50%);
|
| 62 |
+
}
|
| 63 |
+
.result-label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.12em; color: var(--text2); font-weight: 700; }
|
| 64 |
+
.result-value { font-family: var(--font-head); font-size: 52px; font-weight: 800; line-height: 1; margin: 12px 0; }
|
| 65 |
+
.result-value.positive { color: var(--emerald); }
|
| 66 |
+
.result-sub { font-size: 14px; color: var(--text2); }
|
| 67 |
+
|
| 68 |
+
.result-breakdown {
|
| 69 |
+
display: grid;
|
| 70 |
+
grid-template-columns: repeat(3,1fr);
|
| 71 |
+
gap: 12px;
|
| 72 |
+
margin-bottom: 24px;
|
| 73 |
+
}
|
| 74 |
+
.rb-box {
|
| 75 |
+
background: var(--bg3);
|
| 76 |
+
border-radius: var(--r-sm);
|
| 77 |
+
padding: 14px;
|
| 78 |
+
text-align: center;
|
| 79 |
+
}
|
| 80 |
+
.rb-box-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text2); margin-bottom: 6px; font-weight: 700; }
|
| 81 |
+
.rb-box-val { font-family: var(--font-head); font-size: 20px; font-weight: 800; }
|
| 82 |
+
|
| 83 |
+
.chart-panel { height: 260px; position: relative; }
|
| 84 |
+
|
| 85 |
+
.input-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
| 86 |
+
@media (max-width: 600px) { .input-row { grid-template-columns: 1fr; } .result-breakdown { grid-template-columns: 1fr 1fr; } }
|
| 87 |
+
|
| 88 |
+
.preset-pills {
|
| 89 |
+
display: flex;
|
| 90 |
+
gap: 6px;
|
| 91 |
+
flex-wrap: wrap;
|
| 92 |
+
margin-bottom: 8px;
|
| 93 |
+
}
|
| 94 |
+
.preset-pill {
|
| 95 |
+
padding: 4px 10px;
|
| 96 |
+
border-radius: 100px;
|
| 97 |
+
border: 1px solid var(--border2);
|
| 98 |
+
background: transparent;
|
| 99 |
+
color: var(--text2);
|
| 100 |
+
font-size: 12px;
|
| 101 |
+
font-weight: 600;
|
| 102 |
+
cursor: pointer;
|
| 103 |
+
font-family: var(--font-body);
|
| 104 |
+
transition: all var(--transition);
|
| 105 |
+
}
|
| 106 |
+
.preset-pill:hover { border-color: var(--cyan); color: var(--cyan); }
|
| 107 |
+
.preset-pill.active { background: rgba(34,211,238,0.1); border-color: var(--cyan); color: var(--cyan); }
|
| 108 |
+
|
| 109 |
+
.milestone-grid {
|
| 110 |
+
display: grid;
|
| 111 |
+
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
| 112 |
+
gap: 10px;
|
| 113 |
+
margin-top: 16px;
|
| 114 |
+
}
|
| 115 |
+
.milestone-card {
|
| 116 |
+
background: var(--bg3);
|
| 117 |
+
border: 1px solid var(--border);
|
| 118 |
+
border-radius: var(--r-sm);
|
| 119 |
+
padding: 14px;
|
| 120 |
+
text-align: center;
|
| 121 |
+
transition: all var(--transition);
|
| 122 |
+
}
|
| 123 |
+
.milestone-card.reached {
|
| 124 |
+
border-color: var(--emerald);
|
| 125 |
+
background: rgba(16,185,129,0.07);
|
| 126 |
+
}
|
| 127 |
+
.milestone-card.reached .ms-icon { filter: none; }
|
| 128 |
+
.milestone-icon { font-size: 28px; margin-bottom: 6px; filter: grayscale(0.7); }
|
| 129 |
+
.milestone-val { font-family: var(--font-head); font-size: 18px; font-weight: 800; margin-bottom: 4px; }
|
| 130 |
+
.milestone-lbl { font-size: 11px; color: var(--text2); }
|
| 131 |
+
.milestone-yr { font-size: 10px; color: var(--emerald); margin-top: 4px; font-weight: 700; }
|
| 132 |
+
|
| 133 |
+
.compare-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top:16px; }
|
| 134 |
+
.compare-box {
|
| 135 |
+
background: var(--bg3);
|
| 136 |
+
border-radius: var(--r-sm);
|
| 137 |
+
padding: 16px;
|
| 138 |
+
text-align: center;
|
| 139 |
+
border: 1px solid var(--border);
|
| 140 |
+
}
|
| 141 |
+
.cb-label { font-size: 12px; color: var(--text2); margin-bottom: 8px; font-weight: 600; }
|
| 142 |
+
.cb-val { font-family: var(--font-head); font-size: 24px; font-weight: 800; }
|
| 143 |
+
|
| 144 |
+
.sip-frequency {
|
| 145 |
+
display: flex;
|
| 146 |
+
gap: 8px;
|
| 147 |
+
margin-bottom: 16px;
|
| 148 |
+
}
|
| 149 |
+
.freq-btn {
|
| 150 |
+
flex: 1;
|
| 151 |
+
padding: 10px;
|
| 152 |
+
border-radius: var(--r-sm);
|
| 153 |
+
border: 1px solid var(--border);
|
| 154 |
+
background: transparent;
|
| 155 |
+
color: var(--text2);
|
| 156 |
+
font-family: var(--font-body);
|
| 157 |
+
font-size: 13px;
|
| 158 |
+
font-weight: 600;
|
| 159 |
+
cursor: pointer;
|
| 160 |
+
transition: all var(--transition);
|
| 161 |
+
}
|
| 162 |
+
.freq-btn.active { border-color: var(--cyan); background: rgba(34,211,238,0.1); color: var(--cyan); }
|
| 163 |
+
|
| 164 |
+
.inflation-impact {
|
| 165 |
+
display: flex;
|
| 166 |
+
align-items: center;
|
| 167 |
+
gap: 16px;
|
| 168 |
+
padding: 16px;
|
| 169 |
+
background: var(--bg3);
|
| 170 |
+
border-radius: var(--r-sm);
|
| 171 |
+
border-left: 3px solid var(--rose);
|
| 172 |
+
margin-bottom: 16px;
|
| 173 |
+
}
|
| 174 |
+
.inf-icon { font-size: 32px; }
|
| 175 |
+
.inf-text { flex: 1; }
|
| 176 |
+
.inf-title { font-weight: 700; margin-bottom: 4px; }
|
| 177 |
+
.inf-desc { font-size: 13px; color: var(--text2); }
|
| 178 |
+
</style>
|
| 179 |
+
</head>
|
| 180 |
+
<body>
|
| 181 |
+
<div class="app-shell">
|
| 182 |
+
<nav class="sidebar">
|
| 183 |
+
<div class="sidebar-logo">
|
| 184 |
+
<div class="logo-mark">
|
| 185 |
+
<div class="logo-icon">📈</div>
|
| 186 |
+
<div><div class="logo-text">FinWise</div><div class="logo-sub">Smart Investing</div></div>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
<div class="nav-section">
|
| 190 |
+
<div class="nav-label">Main</div>
|
| 191 |
+
<a href="index.html" class="nav-item"><span class="nav-icon">🏠</span> Dashboard</a>
|
| 192 |
+
<a href="portfolio.html" class="nav-item"><span class="nav-icon">📊</span> Portfolio Builder</a>
|
| 193 |
+
<a href="risk.html" class="nav-item"><span class="nav-icon">🎯</span> Risk Analyzer</a>
|
| 194 |
+
<a href="tracker.html" class="nav-item"><span class="nav-icon">📈</span> Tracker</a>
|
| 195 |
+
<div class="nav-label">Tools</div>
|
| 196 |
+
<a href="calculators.html" class="nav-item"><span class="nav-icon">🧮</span> Calculators</a>
|
| 197 |
+
<a href="insights.html" class="nav-item"><span class="nav-icon">💡</span> Insights</a>
|
| 198 |
+
</div>
|
| 199 |
+
<div class="sidebar-footer">
|
| 200 |
+
<div class="market-ticker">Live Market</div>
|
| 201 |
+
<div id="sidebar-tickers"></div>
|
| 202 |
+
</div>
|
| 203 |
+
</nav>
|
| 204 |
+
|
| 205 |
+
<main class="main-content">
|
| 206 |
+
<div class="page-header fade-in">
|
| 207 |
+
<div class="page-title">Financial <span>Calculators</span></div>
|
| 208 |
+
<div class="page-subtitle">Interactive tools to plan, project, and optimize your wealth</div>
|
| 209 |
+
</div>
|
| 210 |
+
|
| 211 |
+
<!-- Calculator Tabs -->
|
| 212 |
+
<div class="calc-tabs fade-in">
|
| 213 |
+
<button class="calc-tab active" data-calc="growth">
|
| 214 |
+
<span class="calc-tab-icon">📈</span> Investment Growth
|
| 215 |
+
</button>
|
| 216 |
+
<button class="calc-tab" data-calc="retirement">
|
| 217 |
+
<span class="calc-tab-icon">🏖️</span> Retirement
|
| 218 |
+
</button>
|
| 219 |
+
<button class="calc-tab" data-calc="sip">
|
| 220 |
+
<span class="calc-tab-icon">🔄</span> SIP / Recurring
|
| 221 |
+
</button>
|
| 222 |
+
<button class="calc-tab" data-calc="inflation">
|
| 223 |
+
<span class="calc-tab-icon">🔥</span> Inflation Impact
|
| 224 |
+
</button>
|
| 225 |
+
</div>
|
| 226 |
+
|
| 227 |
+
<!-- ════════════════════════════════════════════════════════════
|
| 228 |
+
CALCULATOR 1: Investment Growth
|
| 229 |
+
════════════════════════════════════════════════════════════ -->
|
| 230 |
+
<div class="calc-panel active fade-in" id="calc-growth">
|
| 231 |
+
<div class="grid-60-40">
|
| 232 |
+
<div>
|
| 233 |
+
<div class="card">
|
| 234 |
+
<div class="section-title">📈 Investment Growth Calculator</div>
|
| 235 |
+
<div class="text-muted text-sm" style="margin-bottom:20px">See how your one-time investment grows over time with compound returns</div>
|
| 236 |
+
|
| 237 |
+
<div class="slider-wrap">
|
| 238 |
+
<div class="slider-header"><span class="slider-label">💵 Initial Investment</span><span class="slider-val" id="g-init-val">$10,000</span></div>
|
| 239 |
+
<div class="preset-pills">
|
| 240 |
+
<button class="preset-pill" onclick="setGrowthPreset('g-init',1000)">$1K</button>
|
| 241 |
+
<button class="preset-pill active" onclick="setGrowthPreset('g-init',10000)">$10K</button>
|
| 242 |
+
<button class="preset-pill" onclick="setGrowthPreset('g-init',25000)">$25K</button>
|
| 243 |
+
<button class="preset-pill" onclick="setGrowthPreset('g-init',50000)">$50K</button>
|
| 244 |
+
<button class="preset-pill" onclick="setGrowthPreset('g-init',100000)">$100K</button>
|
| 245 |
+
</div>
|
| 246 |
+
<input type="range" id="g-init" min="500" max="200000" value="10000" step="500" oninput="calcGrowth()">
|
| 247 |
+
</div>
|
| 248 |
+
|
| 249 |
+
<div class="slider-wrap">
|
| 250 |
+
<div class="slider-header"><span class="slider-label">📅 Time Horizon</span><span class="slider-val" id="g-years-val">20 years</span></div>
|
| 251 |
+
<div class="preset-pills">
|
| 252 |
+
<button class="preset-pill" onclick="setGrowthPreset('g-years',5)">5yr</button>
|
| 253 |
+
<button class="preset-pill" onclick="setGrowthPreset('g-years',10)">10yr</button>
|
| 254 |
+
<button class="preset-pill active" onclick="setGrowthPreset('g-years',20)">20yr</button>
|
| 255 |
+
<button class="preset-pill" onclick="setGrowthPreset('g-years',30)">30yr</button>
|
| 256 |
+
</div>
|
| 257 |
+
<input type="range" id="g-years" min="1" max="50" value="20" step="1" oninput="calcGrowth()">
|
| 258 |
+
</div>
|
| 259 |
+
|
| 260 |
+
<div class="slider-wrap">
|
| 261 |
+
<div class="slider-header"><span class="slider-label">📊 Annual Return Rate</span><span class="slider-val" id="g-rate-val">8%</span></div>
|
| 262 |
+
<div class="preset-pills">
|
| 263 |
+
<button class="preset-pill" onclick="setGrowthPreset('g-rate',4,'%')">Bonds 4%</button>
|
| 264 |
+
<button class="preset-pill" onclick="setGrowthPreset('g-rate',7,'%')">S&P Avg 7%</button>
|
| 265 |
+
<button class="preset-pill active" onclick="setGrowthPreset('g-rate',8,'%')">Moderate 8%</button>
|
| 266 |
+
<button class="preset-pill" onclick="setGrowthPreset('g-rate',12,'%')">Aggressive 12%</button>
|
| 267 |
+
</div>
|
| 268 |
+
<input type="range" id="g-rate" min="1" max="25" value="8" step="0.5" oninput="calcGrowth()">
|
| 269 |
+
</div>
|
| 270 |
+
|
| 271 |
+
<div class="slider-wrap">
|
| 272 |
+
<div class="slider-header"><span class="slider-label">📆 Compound Frequency</span><span class="slider-val" id="g-compound-val">Annually</span></div>
|
| 273 |
+
<input type="range" id="g-compound" min="1" max="12" value="1" step="1" oninput="calcGrowth()">
|
| 274 |
+
<div style="display:flex;justify-content:space-between;font-size:11px;color:var(--text3);margin-top:4px">
|
| 275 |
+
<span>Annually</span><span>Quarterly</span><span>Monthly</span>
|
| 276 |
+
</div>
|
| 277 |
+
</div>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
|
| 281 |
+
<div>
|
| 282 |
+
<div class="result-hero">
|
| 283 |
+
<div class="result-label">Future Value</div>
|
| 284 |
+
<div class="result-value positive" id="g-future">$0</div>
|
| 285 |
+
<div class="result-sub" id="g-sub">You'd earn <strong id="g-profit">$0</strong> in profit</div>
|
| 286 |
+
</div>
|
| 287 |
+
|
| 288 |
+
<div class="result-breakdown">
|
| 289 |
+
<div class="rb-box">
|
| 290 |
+
<div class="rb-box-label">Invested</div>
|
| 291 |
+
<div class="rb-box-val" id="g-rb-invested">$0</div>
|
| 292 |
+
</div>
|
| 293 |
+
<div class="rb-box">
|
| 294 |
+
<div class="rb-box-label">Earnings</div>
|
| 295 |
+
<div class="rb-box-val" style="color:var(--emerald)" id="g-rb-earn">$0</div>
|
| 296 |
+
</div>
|
| 297 |
+
<div class="rb-box">
|
| 298 |
+
<div class="rb-box-label">Total Return</div>
|
| 299 |
+
<div class="rb-box-val" style="color:var(--cyan)" id="g-rb-pct">0%</div>
|
| 300 |
+
</div>
|
| 301 |
+
</div>
|
| 302 |
+
|
| 303 |
+
<div class="card" style="padding:16px">
|
| 304 |
+
<div class="card-title">📊 Growth Over Time</div>
|
| 305 |
+
<div class="chart-panel">
|
| 306 |
+
<canvas id="growthChart"></canvas>
|
| 307 |
+
</div>
|
| 308 |
+
</div>
|
| 309 |
+
|
| 310 |
+
<!-- Milestones -->
|
| 311 |
+
<div class="card" style="margin-top:16px;padding:16px">
|
| 312 |
+
<div class="card-title">🏆 Milestones</div>
|
| 313 |
+
<div class="milestone-grid" id="milestones-grid"></div>
|
| 314 |
+
</div>
|
| 315 |
+
</div>
|
| 316 |
+
</div>
|
| 317 |
+
</div>
|
| 318 |
+
|
| 319 |
+
<!-- ════════════════════════════════════════════════════════════
|
| 320 |
+
CALCULATOR 2: Retirement
|
| 321 |
+
════════════════════════════════════════════════════════════ -->
|
| 322 |
+
<div class="calc-panel" id="calc-retirement">
|
| 323 |
+
<div class="grid-60-40">
|
| 324 |
+
<div>
|
| 325 |
+
<div class="card">
|
| 326 |
+
<div class="section-title">🏖️ Retirement Calculator</div>
|
| 327 |
+
<div class="text-muted text-sm" style="margin-bottom:20px">Plan your retirement savings and see if you're on track</div>
|
| 328 |
+
|
| 329 |
+
<div class="input-row" style="margin-bottom:16px">
|
| 330 |
+
<div class="field-group">
|
| 331 |
+
<label class="field-label">Current Age</label>
|
| 332 |
+
<input type="number" id="ret-age" value="30" min="18" max="80" oninput="calcRetirement()">
|
| 333 |
+
</div>
|
| 334 |
+
<div class="field-group">
|
| 335 |
+
<label class="field-label">Retirement Age</label>
|
| 336 |
+
<input type="number" id="ret-age2" value="65" min="40" max="85" oninput="calcRetirement()">
|
| 337 |
+
</div>
|
| 338 |
+
</div>
|
| 339 |
+
|
| 340 |
+
<div class="slider-wrap">
|
| 341 |
+
<div class="slider-header"><span class="slider-label">💰 Current Savings</span><span class="slider-val" id="ret-saved-val">$15,000</span></div>
|
| 342 |
+
<input type="range" id="ret-saved" min="0" max="500000" value="15000" step="1000" oninput="calcRetirement()">
|
| 343 |
+
</div>
|
| 344 |
+
|
| 345 |
+
<div class="slider-wrap">
|
| 346 |
+
<div class="slider-header"><span class="slider-label">📅 Monthly Contribution</span><span class="slider-val" id="ret-monthly-val">$500/mo</span></div>
|
| 347 |
+
<input type="range" id="ret-monthly" min="50" max="5000" value="500" step="50" oninput="calcRetirement()">
|
| 348 |
+
</div>
|
| 349 |
+
|
| 350 |
+
<div class="slider-wrap">
|
| 351 |
+
<div class="slider-header"><span class="slider-label">📈 Annual Return</span><span class="slider-val" id="ret-rate-val">7%</span></div>
|
| 352 |
+
<input type="range" id="ret-rate" min="2" max="15" value="7" step="0.5" oninput="calcRetirement()">
|
| 353 |
+
</div>
|
| 354 |
+
|
| 355 |
+
<div class="slider-wrap">
|
| 356 |
+
<div class="slider-header"><span class="slider-label">🏦 Desired Monthly Income in Retirement</span><span class="slider-val" id="ret-need-val">$4,000/mo</span></div>
|
| 357 |
+
<input type="range" id="ret-need" min="1000" max="20000" value="4000" step="500" oninput="calcRetirement()">
|
| 358 |
+
</div>
|
| 359 |
+
</div>
|
| 360 |
+
</div>
|
| 361 |
+
|
| 362 |
+
<div>
|
| 363 |
+
<div class="result-hero">
|
| 364 |
+
<div class="result-label">Retirement Nest Egg</div>
|
| 365 |
+
<div class="result-value positive" id="ret-future">$0</div>
|
| 366 |
+
<div class="result-sub" id="ret-status">—</div>
|
| 367 |
+
</div>
|
| 368 |
+
|
| 369 |
+
<div class="result-breakdown">
|
| 370 |
+
<div class="rb-box">
|
| 371 |
+
<div class="rb-box-label">Years to Retire</div>
|
| 372 |
+
<div class="rb-box-val" style="color:var(--amber)" id="ret-rb-years">0</div>
|
| 373 |
+
</div>
|
| 374 |
+
<div class="rb-box">
|
| 375 |
+
<div class="rb-box-label">Monthly Draw</div>
|
| 376 |
+
<div class="rb-box-val" style="color:var(--cyan)" id="ret-rb-draw">$0/mo</div>
|
| 377 |
+
</div>
|
| 378 |
+
<div class="rb-box">
|
| 379 |
+
<div class="rb-box-label">Fund Duration</div>
|
| 380 |
+
<div class="rb-box-val" style="color:var(--emerald)" id="ret-rb-dur">0 yrs</div>
|
| 381 |
+
</div>
|
| 382 |
+
</div>
|
| 383 |
+
|
| 384 |
+
<div class="card" style="padding:16px">
|
| 385 |
+
<div class="card-title">📊 Savings Trajectory</div>
|
| 386 |
+
<div class="chart-panel">
|
| 387 |
+
<canvas id="retChart"></canvas>
|
| 388 |
+
</div>
|
| 389 |
+
</div>
|
| 390 |
+
|
| 391 |
+
<div id="ret-on-track-box" style="margin-top:16px;padding:16px;border-radius:var(--r-sm);border:1px solid"></div>
|
| 392 |
+
</div>
|
| 393 |
+
</div>
|
| 394 |
+
</div>
|
| 395 |
+
|
| 396 |
+
<!-- ════════════════════════════════════════════════════════════
|
| 397 |
+
CALCULATOR 3: SIP / Recurring
|
| 398 |
+
════════════════════════════════════════════════════════════ -->
|
| 399 |
+
<div class="calc-panel" id="calc-sip">
|
| 400 |
+
<div class="grid-60-40">
|
| 401 |
+
<div>
|
| 402 |
+
<div class="card">
|
| 403 |
+
<div class="section-title">🔄 SIP / Recurring Investment</div>
|
| 404 |
+
<div class="text-muted text-sm" style="margin-bottom:16px">How regular investments compound over time</div>
|
| 405 |
+
|
| 406 |
+
<div style="margin-bottom:16px">
|
| 407 |
+
<div class="field-label" style="margin-bottom:8px">Contribution Frequency</div>
|
| 408 |
+
<div class="sip-frequency">
|
| 409 |
+
<button class="freq-btn" onclick="setSipFreq('weekly',this)">Weekly</button>
|
| 410 |
+
<button class="freq-btn active" onclick="setSipFreq('monthly',this)">Monthly</button>
|
| 411 |
+
<button class="freq-btn" onclick="setSipFreq('quarterly',this)">Quarterly</button>
|
| 412 |
+
<button class="freq-btn" onclick="setSipFreq('annually',this)">Annually</button>
|
| 413 |
+
</div>
|
| 414 |
+
</div>
|
| 415 |
+
|
| 416 |
+
<div class="slider-wrap">
|
| 417 |
+
<div class="slider-header"><span class="slider-label">💵 Contribution Amount</span><span class="slider-val" id="sip-amount-val">$300/mo</span></div>
|
| 418 |
+
<div class="preset-pills">
|
| 419 |
+
<button class="preset-pill" onclick="setSipPreset('sip-amount',100)">$100</button>
|
| 420 |
+
<button class="preset-pill active" onclick="setSipPreset('sip-amount',300)">$300</button>
|
| 421 |
+
<button class="preset-pill" onclick="setSipPreset('sip-amount',500)">$500</button>
|
| 422 |
+
<button class="preset-pill" onclick="setSipPreset('sip-amount',1000)">$1K</button>
|
| 423 |
+
</div>
|
| 424 |
+
<input type="range" id="sip-amount" min="10" max="5000" value="300" step="10" oninput="calcSIP()">
|
| 425 |
+
</div>
|
| 426 |
+
|
| 427 |
+
<div class="slider-wrap">
|
| 428 |
+
<div class="slider-header"><span class="slider-label">📅 Duration</span><span class="slider-val" id="sip-years-val">15 years</span></div>
|
| 429 |
+
<input type="range" id="sip-years" min="1" max="40" value="15" step="1" oninput="calcSIP()">
|
| 430 |
+
</div>
|
| 431 |
+
|
| 432 |
+
<div class="slider-wrap">
|
| 433 |
+
<div class="slider-header"><span class="slider-label">📊 Expected Return</span><span class="slider-val" id="sip-rate-val">9%</span></div>
|
| 434 |
+
<input type="range" id="sip-rate" min="2" max="20" value="9" step="0.5" oninput="calcSIP()">
|
| 435 |
+
</div>
|
| 436 |
+
|
| 437 |
+
<div class="slider-wrap">
|
| 438 |
+
<div class="slider-header"><span class="slider-label">📈 Annual Step-Up %</span><span class="slider-val" id="sip-stepup-val">0%</span></div>
|
| 439 |
+
<input type="range" id="sip-stepup" min="0" max="20" value="0" step="1" oninput="calcSIP()">
|
| 440 |
+
<div class="text-muted text-sm" style="margin-top:4px">Increase contribution amount each year by this %</div>
|
| 441 |
+
</div>
|
| 442 |
+
</div>
|
| 443 |
+
</div>
|
| 444 |
+
|
| 445 |
+
<div>
|
| 446 |
+
<div class="result-hero">
|
| 447 |
+
<div class="result-label">Total Wealth Created</div>
|
| 448 |
+
<div class="result-value positive" id="sip-future">$0</div>
|
| 449 |
+
<div class="result-sub">Total invested: <strong id="sip-invested">$0</strong></div>
|
| 450 |
+
</div>
|
| 451 |
+
|
| 452 |
+
<div class="result-breakdown">
|
| 453 |
+
<div class="rb-box">
|
| 454 |
+
<div class="rb-box-label">Invested</div>
|
| 455 |
+
<div class="rb-box-val" id="sip-rb-inv">$0</div>
|
| 456 |
+
</div>
|
| 457 |
+
<div class="rb-box">
|
| 458 |
+
<div class="rb-box-label">Gains</div>
|
| 459 |
+
<div class="rb-box-val" style="color:var(--emerald)" id="sip-rb-gain">$0</div>
|
| 460 |
+
</div>
|
| 461 |
+
<div class="rb-box">
|
| 462 |
+
<div class="rb-box-label">Gain %</div>
|
| 463 |
+
<div class="rb-box-val" style="color:var(--cyan)" id="sip-rb-pct">0%</div>
|
| 464 |
+
</div>
|
| 465 |
+
</div>
|
| 466 |
+
|
| 467 |
+
<div class="card" style="padding:16px">
|
| 468 |
+
<div class="card-title">📊 Contribution vs Growth</div>
|
| 469 |
+
<div class="chart-panel">
|
| 470 |
+
<canvas id="sipChart"></canvas>
|
| 471 |
+
</div>
|
| 472 |
+
</div>
|
| 473 |
+
|
| 474 |
+
<div class="compare-grid">
|
| 475 |
+
<div class="compare-box">
|
| 476 |
+
<div class="cb-label">💤 Without Investing<br>(under mattress)</div>
|
| 477 |
+
<div class="cb-val" id="sip-no-invest" style="color:var(--rose)">$0</div>
|
| 478 |
+
</div>
|
| 479 |
+
<div class="compare-box">
|
| 480 |
+
<div class="cb-label">📈 With Investing<br>(your strategy)</div>
|
| 481 |
+
<div class="cb-val" id="sip-with-invest" style="color:var(--emerald)">$0</div>
|
| 482 |
+
</div>
|
| 483 |
+
</div>
|
| 484 |
+
</div>
|
| 485 |
+
</div>
|
| 486 |
+
</div>
|
| 487 |
+
|
| 488 |
+
<!-- ════════════════════════════════════════════════════════════
|
| 489 |
+
CALCULATOR 4: Inflation Impact
|
| 490 |
+
════════════════════════════════════════════════════════════ -->
|
| 491 |
+
<div class="calc-panel" id="calc-inflation">
|
| 492 |
+
<div class="grid-60-40">
|
| 493 |
+
<div>
|
| 494 |
+
<div class="card">
|
| 495 |
+
<div class="section-title">🔥 Inflation Impact Calculator</div>
|
| 496 |
+
<div class="text-muted text-sm" style="margin-bottom:20px">Understand how inflation erodes your money's purchasing power</div>
|
| 497 |
+
|
| 498 |
+
<div class="slider-wrap">
|
| 499 |
+
<div class="slider-header"><span class="slider-label">💵 Amount Today</span><span class="slider-val" id="inf-amount-val">$10,000</span></div>
|
| 500 |
+
<div class="preset-pills">
|
| 501 |
+
<button class="preset-pill" onclick="setInfPreset('inf-amount',5000)">$5K</button>
|
| 502 |
+
<button class="preset-pill active" onclick="setInfPreset('inf-amount',10000)">$10K</button>
|
| 503 |
+
<button class="preset-pill" onclick="setInfPreset('inf-amount',50000)">$50K</button>
|
| 504 |
+
<button class="preset-pill" onclick="setInfPreset('inf-amount',100000)">$100K</button>
|
| 505 |
+
</div>
|
| 506 |
+
<input type="range" id="inf-amount" min="1000" max="500000" value="10000" step="1000" oninput="calcInflation()">
|
| 507 |
+
</div>
|
| 508 |
+
|
| 509 |
+
<div class="slider-wrap">
|
| 510 |
+
<div class="slider-header"><span class="slider-label">📅 Years Ahead</span><span class="slider-val" id="inf-years-val">20 years</span></div>
|
| 511 |
+
<input type="range" id="inf-years" min="1" max="50" value="20" step="1" oninput="calcInflation()">
|
| 512 |
+
</div>
|
| 513 |
+
|
| 514 |
+
<div class="slider-wrap">
|
| 515 |
+
<div class="slider-header"><span class="slider-label">🔥 Annual Inflation Rate</span><span class="slider-val" id="inf-rate-val">3.5%</span></div>
|
| 516 |
+
<div class="preset-pills">
|
| 517 |
+
<button class="preset-pill" onclick="setInfPreset('inf-rate',2)">Low 2%</button>
|
| 518 |
+
<button class="preset-pill active" onclick="setInfPreset('inf-rate',3.5)">Average 3.5%</button>
|
| 519 |
+
<button class="preset-pill" onclick="setInfPreset('inf-rate',6)">High 6%</button>
|
| 520 |
+
<button class="preset-pill" onclick="setInfPreset('inf-rate',10)">Crisis 10%</button>
|
| 521 |
+
</div>
|
| 522 |
+
<input type="range" id="inf-rate" min="0.5" max="15" value="3.5" step="0.5" oninput="calcInflation()">
|
| 523 |
+
</div>
|
| 524 |
+
|
| 525 |
+
<div class="slider-wrap">
|
| 526 |
+
<div class="slider-header"><span class="slider-label">📈 Investment Return Rate</span><span class="slider-val" id="inf-invest-val">7%</span></div>
|
| 527 |
+
<input type="range" id="inf-invest" min="0" max="20" value="7" step="0.5" oninput="calcInflation()">
|
| 528 |
+
<div class="text-muted text-sm" style="margin-top:4px">If you invest vs keep as cash</div>
|
| 529 |
+
</div>
|
| 530 |
+
</div>
|
| 531 |
+
</div>
|
| 532 |
+
|
| 533 |
+
<div>
|
| 534 |
+
<div class="inflation-impact">
|
| 535 |
+
<div class="inf-icon">💸</div>
|
| 536 |
+
<div class="inf-text">
|
| 537 |
+
<div class="inf-title">Purchasing Power Loss</div>
|
| 538 |
+
<div class="inf-desc">Your money <strong id="inf-today">$10,000</strong> today will only be worth <strong id="inf-future-worth" style="color:var(--rose)">—</strong> in real terms after <span id="inf-years-txt">20</span> years.</div>
|
| 539 |
+
</div>
|
| 540 |
+
</div>
|
| 541 |
+
|
| 542 |
+
<div class="result-hero">
|
| 543 |
+
<div class="result-label">Inflation-Adjusted Value</div>
|
| 544 |
+
<div class="result-value" id="inf-result" style="color:var(--rose)">$0</div>
|
| 545 |
+
<div class="result-sub">Real purchasing power loss: <strong id="inf-loss" style="color:var(--rose)">$0</strong></div>
|
| 546 |
+
</div>
|
| 547 |
+
|
| 548 |
+
<div class="result-breakdown">
|
| 549 |
+
<div class="rb-box">
|
| 550 |
+
<div class="rb-box-label">Nominal Value</div>
|
| 551 |
+
<div class="rb-box-val" id="inf-rb-nom">$0</div>
|
| 552 |
+
</div>
|
| 553 |
+
<div class="rb-box">
|
| 554 |
+
<div class="rb-box-label">Real Value</div>
|
| 555 |
+
<div class="rb-box-val" style="color:var(--rose)" id="inf-rb-real">$0</div>
|
| 556 |
+
</div>
|
| 557 |
+
<div class="rb-box">
|
| 558 |
+
<div class="rb-box-label">If Invested</div>
|
| 559 |
+
<div class="rb-box-val" style="color:var(--emerald)" id="inf-rb-inv">$0</div>
|
| 560 |
+
</div>
|
| 561 |
+
</div>
|
| 562 |
+
|
| 563 |
+
<div class="card" style="padding:16px">
|
| 564 |
+
<div class="card-title">📊 Cash vs Invested vs Inflation</div>
|
| 565 |
+
<div class="chart-panel">
|
| 566 |
+
<canvas id="infChart"></canvas>
|
| 567 |
+
</div>
|
| 568 |
+
</div>
|
| 569 |
+
</div>
|
| 570 |
+
</div>
|
| 571 |
+
</div>
|
| 572 |
+
|
| 573 |
+
</main>
|
| 574 |
+
</div>
|
| 575 |
+
|
| 576 |
+
<nav class="bottom-nav">
|
| 577 |
+
<div class="bottom-nav-inner">
|
| 578 |
+
<a href="index.html" class="bottom-nav-item"><span class="bnav-icon">🏠</span>Home</a>
|
| 579 |
+
<a href="portfolio.html" class="bottom-nav-item"><span class="bnav-icon">📊</span>Portfolio</a>
|
| 580 |
+
<a href="risk.html" class="bottom-nav-item"><span class="bnav-icon">🎯</span>Risk</a>
|
| 581 |
+
<a href="tracker.html" class="bottom-nav-item"><span class="bnav-icon">📈</span>Track</a>
|
| 582 |
+
<a href="calculators.html" class="bottom-nav-item"><span class="bnav-icon">🧮</span>Calc</a>
|
| 583 |
+
<a href="insights.html" class="bottom-nav-item"><span class="bnav-icon">💡</span>Insights</a>
|
| 584 |
+
</div>
|
| 585 |
+
</nav>
|
| 586 |
+
|
| 587 |
+
<script src="shared.js"></script>
|
| 588 |
+
<script>
|
| 589 |
+
// ══ Tab Switching ═════════════════════════════════════════════════
|
| 590 |
+
document.querySelectorAll('.calc-tab').forEach(btn => {
|
| 591 |
+
btn.addEventListener('click', () => {
|
| 592 |
+
document.querySelectorAll('.calc-tab,.calc-panel').forEach(el => el.classList.remove('active'));
|
| 593 |
+
btn.classList.add('active');
|
| 594 |
+
document.getElementById('calc-' + btn.dataset.calc).classList.add('active');
|
| 595 |
+
setTimeout(() => { calcGrowth(); calcRetirement(); calcSIP(); calcInflation(); }, 50);
|
| 596 |
+
});
|
| 597 |
+
});
|
| 598 |
+
|
| 599 |
+
// ══ Shared helpers ════════════════════════════════════════════════
|
| 600 |
+
let growthChart=null, retChart=null, sipChart=null, infChart=null;
|
| 601 |
+
let sipFreq = 'monthly'; // periods per year
|
| 602 |
+
const FREQ_MAP = { weekly:52, monthly:12, quarterly:4, annually:1 };
|
| 603 |
+
const FREQ_LABEL = { weekly:'wk', monthly:'mo', quarterly:'qtr', annually:'yr' };
|
| 604 |
+
|
| 605 |
+
function sliderSync(id, labelId, prefix='$', suffix='', scale=1, decimals=0) {
|
| 606 |
+
const el = document.getElementById(id);
|
| 607 |
+
const lbl = document.getElementById(labelId);
|
| 608 |
+
const update = () => {
|
| 609 |
+
const v = parseFloat(el.value) * scale;
|
| 610 |
+
lbl.textContent = prefix + (decimals ? v.toFixed(decimals) : Math.round(v).toLocaleString()) + suffix;
|
| 611 |
+
};
|
| 612 |
+
el.addEventListener('input', update);
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
// ══ CALCULATOR 1: Investment Growth ═══════════════════════════════
|
| 616 |
+
function setGrowthPreset(id, val, unit) {
|
| 617 |
+
document.getElementById(id).value = val;
|
| 618 |
+
calcGrowth();
|
| 619 |
+
}
|
| 620 |
+
function setSipPreset(id, val) { document.getElementById(id).value = val; calcSIP(); }
|
| 621 |
+
function setInfPreset(id, val) { document.getElementById(id).value = val; calcInflation(); }
|
| 622 |
+
|
| 623 |
+
function calcGrowth() {
|
| 624 |
+
const P = parseFloat(document.getElementById('g-init').value);
|
| 625 |
+
const n = parseInt(document.getElementById('g-years').value);
|
| 626 |
+
const r = parseFloat(document.getElementById('g-rate').value) / 100;
|
| 627 |
+
const c = parseInt(document.getElementById('g-compound').value);
|
| 628 |
+
|
| 629 |
+
document.getElementById('g-init-val').textContent = '$' + P.toLocaleString();
|
| 630 |
+
document.getElementById('g-years-val').textContent = n + ' year' + (n===1?'':'s');
|
| 631 |
+
const compoundLabels = {1:'Annually',2:'Semi-annually',3:'Tri-annually',4:'Quarterly',6:'Bi-monthly',12:'Monthly'};
|
| 632 |
+
document.getElementById('g-compound-val').textContent = compoundLabels[c] || c + 'x/yr';
|
| 633 |
+
document.getElementById('g-rate-val').textContent = r*100 + '%';
|
| 634 |
+
|
| 635 |
+
const FV = P * Math.pow(1 + r/c, c*n);
|
| 636 |
+
const profit = FV - P;
|
| 637 |
+
const pct = (profit/P)*100;
|
| 638 |
+
|
| 639 |
+
document.getElementById('g-future').textContent = fmt$(FV);
|
| 640 |
+
document.getElementById('g-profit').textContent = fmt$(profit);
|
| 641 |
+
document.getElementById('g-rb-invested').textContent = fmt$(P);
|
| 642 |
+
document.getElementById('g-rb-earn').textContent = fmt$(profit);
|
| 643 |
+
document.getElementById('g-rb-pct').textContent = '+' + pct.toFixed(1) + '%';
|
| 644 |
+
|
| 645 |
+
// Chart data
|
| 646 |
+
const years = Array.from({length:n+1},(_,i)=>i);
|
| 647 |
+
const vals = years.map(y => P * Math.pow(1+r/c, c*y));
|
| 648 |
+
const invested = years.map(() => P);
|
| 649 |
+
|
| 650 |
+
if (growthChart) growthChart.destroy();
|
| 651 |
+
const ctx = document.getElementById('growthChart').getContext('2d');
|
| 652 |
+
const grad = ctx.createLinearGradient(0,0,0,260);
|
| 653 |
+
grad.addColorStop(0,'rgba(16,185,129,0.35)'); grad.addColorStop(1,'rgba(16,185,129,0)');
|
| 654 |
+
|
| 655 |
+
growthChart = new Chart(ctx, {
|
| 656 |
+
type:'line',
|
| 657 |
+
data:{
|
| 658 |
+
labels: years.map(y => 'Yr '+y),
|
| 659 |
+
datasets:[
|
| 660 |
+
{ label:'Portfolio Value', data:vals, borderColor:'#10b981', backgroundColor:grad, borderWidth:2.5, fill:true, tension:0.4, pointRadius:0 },
|
| 661 |
+
{ label:'Amount Invested', data:invested, borderColor:'rgba(34,211,238,0.5)', borderDash:[5,5], borderWidth:1.5, fill:false, pointRadius:0, tension:0 }
|
| 662 |
+
]
|
| 663 |
+
},
|
| 664 |
+
options:{ responsive:true, maintainAspectRatio:false,
|
| 665 |
+
plugins:{ legend:{ labels:{font:{size:11}} },
|
| 666 |
+
tooltip:{ callbacks:{ label: ctx => ' '+fmt$(ctx.raw) } } },
|
| 667 |
+
scales:{
|
| 668 |
+
x:{ grid:{display:false}, ticks:{maxTicksLimit:8,font:{size:11}} },
|
| 669 |
+
y:{ grid:{color:'rgba(34,211,238,0.06)'}, ticks:{ callback:v=>'$'+(v/1000).toFixed(0)+'K', font:{size:11} } }
|
| 670 |
+
}
|
| 671 |
+
}
|
| 672 |
+
});
|
| 673 |
+
|
| 674 |
+
// Milestones
|
| 675 |
+
const milestones = [
|
| 676 |
+
{ icon:'💯', label:'2× Money', target:P*2 },
|
| 677 |
+
{ icon:'🚀', label:'5× Money', target:P*5 },
|
| 678 |
+
{ icon:'💎', label:'10× Money', target:P*10 },
|
| 679 |
+
{ icon:'🌟', label:'$100K', target:100000 },
|
| 680 |
+
{ icon:'🏆', label:'$250K', target:250000 },
|
| 681 |
+
{ icon:'👑', label:'$1M', target:1000000 },
|
| 682 |
+
];
|
| 683 |
+
document.getElementById('milestones-grid').innerHTML = milestones.map(m => {
|
| 684 |
+
const reached = FV >= m.target;
|
| 685 |
+
const yearsTo = reached ? Math.log(m.target/P) / (c * Math.log(1+r/c)) : null;
|
| 686 |
+
return `
|
| 687 |
+
<div class="milestone-card ${reached?'reached':''}">
|
| 688 |
+
<div class="milestone-icon">${m.icon}</div>
|
| 689 |
+
<div class="milestone-val">${m.label}</div>
|
| 690 |
+
<div class="milestone-lbl">${fmt$(m.target)}</div>
|
| 691 |
+
${reached ? `<div class="milestone-yr">✅ ~Yr ${Math.round(yearsTo)}</div>` : `<div class="milestone-yr" style="color:var(--text3)">Not reached</div>`}
|
| 692 |
+
</div>`;
|
| 693 |
+
}).join('');
|
| 694 |
+
}
|
| 695 |
+
|
| 696 |
+
// ══ CALCULATOR 2: Retirement ══════════════════════════════════════
|
| 697 |
+
function calcRetirement() {
|
| 698 |
+
const age = parseInt(document.getElementById('ret-age').value) || 30;
|
| 699 |
+
const retAge = parseInt(document.getElementById('ret-age2').value) || 65;
|
| 700 |
+
const saved = parseFloat(document.getElementById('ret-saved').value);
|
| 701 |
+
const mo = parseFloat(document.getElementById('ret-monthly').value);
|
| 702 |
+
const r = parseFloat(document.getElementById('ret-rate').value)/100;
|
| 703 |
+
const need = parseFloat(document.getElementById('ret-need').value);
|
| 704 |
+
|
| 705 |
+
document.getElementById('ret-saved-val').textContent = '$'+saved.toLocaleString();
|
| 706 |
+
document.getElementById('ret-monthly-val').textContent = '$'+mo.toLocaleString()+'/mo';
|
| 707 |
+
document.getElementById('ret-rate-val').textContent = r*100 + '%';
|
| 708 |
+
document.getElementById('ret-need-val').textContent = '$'+need.toLocaleString()+'/mo';
|
| 709 |
+
|
| 710 |
+
const years = Math.max(retAge - age, 1);
|
| 711 |
+
const rMo = r/12;
|
| 712 |
+
const FV_existing = saved * Math.pow(1+r, years);
|
| 713 |
+
const FV_contrib = mo * ((Math.pow(1+rMo, years*12)-1)/rMo);
|
| 714 |
+
const total = FV_existing + FV_contrib;
|
| 715 |
+
const annualy = need * 12;
|
| 716 |
+
const fundDur = total > 0 ? Math.log(1 - (annualy * (1-(1+r/4))) / (total * (r/4))) / Math.log(1+r/4) * (-1) : 0;
|
| 717 |
+
const safeDrawdown = total * 0.04 / 12;
|
| 718 |
+
const onTrack = total >= annualy * 25;
|
| 719 |
+
|
| 720 |
+
document.getElementById('ret-future').textContent = fmt$(total);
|
| 721 |
+
document.getElementById('ret-rb-years').textContent = years + ' yrs';
|
| 722 |
+
document.getElementById('ret-rb-draw').textContent = '$' + Math.round(safeDrawdown).toLocaleString() + '/mo';
|
| 723 |
+
document.getElementById('ret-rb-dur').textContent = Math.round(Math.max(fundDur,0)) + ' yrs';
|
| 724 |
+
|
| 725 |
+
const box = document.getElementById('ret-on-track-box');
|
| 726 |
+
if (onTrack) {
|
| 727 |
+
box.style.cssText = 'padding:16px;border-radius:var(--r-sm);border:1px solid rgba(16,185,129,0.4);background:rgba(16,185,129,0.08)';
|
| 728 |
+
box.innerHTML = `<div style="font-weight:700;color:var(--emerald);margin-bottom:6px">✅ You're on track!</div>
|
| 729 |
+
<div style="font-size:13px;color:var(--text2)">Your projected nest egg supports $${Math.round(safeDrawdown).toLocaleString()}/mo using the 4% rule. That's more than your $${need.toLocaleString()}/mo target.</div>`;
|
| 730 |
+
} else {
|
| 731 |
+
const gap = annualy*25 - total;
|
| 732 |
+
box.style.cssText = 'padding:16px;border-radius:var(--r-sm);border:1px solid rgba(245,158,11,0.4);background:rgba(245,158,11,0.08)';
|
| 733 |
+
box.innerHTML = `<div style="font-weight:700;color:var(--amber);margin-bottom:6px">⚠️ Savings Gap Detected</div>
|
| 734 |
+
<div style="font-size:13px;color:var(--text2)">You're projected to be <strong style="color:var(--amber)">${fmt$(gap)}</strong> short of your retirement goal. Consider increasing contributions by $${Math.round(gap/(years*12)).toLocaleString()}/mo.</div>`;
|
| 735 |
+
}
|
| 736 |
+
|
| 737 |
+
document.getElementById('ret-status').textContent = onTrack ? '🎉 On track for retirement!' : `Need ${fmt$(annualy*25)} total`;
|
| 738 |
+
|
| 739 |
+
// Chart
|
| 740 |
+
const yrs = Array.from({length:years+1},(_,i)=>i);
|
| 741 |
+
const trajectory = yrs.map(y => {
|
| 742 |
+
const fe = saved * Math.pow(1+r,y);
|
| 743 |
+
const fc = mo * ((Math.pow(1+r/12, y*12)-1)/(r/12));
|
| 744 |
+
return fe+fc;
|
| 745 |
+
});
|
| 746 |
+
|
| 747 |
+
if (retChart) retChart.destroy();
|
| 748 |
+
const ctx = document.getElementById('retChart').getContext('2d');
|
| 749 |
+
const grad = ctx.createLinearGradient(0,0,0,260);
|
| 750 |
+
grad.addColorStop(0,'rgba(34,211,238,0.3)'); grad.addColorStop(1,'rgba(34,211,238,0)');
|
| 751 |
+
|
| 752 |
+
retChart = new Chart(ctx, {
|
| 753 |
+
type:'line',
|
| 754 |
+
data:{
|
| 755 |
+
labels: yrs.map(y => age+y),
|
| 756 |
+
datasets:[
|
| 757 |
+
{ label:'Projected Savings', data:trajectory, borderColor:'#22d3ee', backgroundColor:grad, borderWidth:2.5, fill:true, tension:0.4, pointRadius:0 },
|
| 758 |
+
{ label:'Target', data:yrs.map(()=>annualy*25), borderColor:'rgba(245,158,11,0.6)', borderDash:[6,4], borderWidth:1.5, fill:false, pointRadius:0 }
|
| 759 |
+
]
|
| 760 |
+
},
|
| 761 |
+
options:{ responsive:true, maintainAspectRatio:false,
|
| 762 |
+
plugins:{ legend:{labels:{font:{size:11}}}, tooltip:{ callbacks:{ label:ctx=>' '+fmt$(ctx.raw) } } },
|
| 763 |
+
scales:{
|
| 764 |
+
x:{ grid:{display:false}, ticks:{maxTicksLimit:8,font:{size:11}} },
|
| 765 |
+
y:{ grid:{color:'rgba(34,211,238,0.06)'}, ticks:{ callback:v=>'$'+(v/1000).toFixed(0)+'K', font:{size:11} } }
|
| 766 |
+
}
|
| 767 |
+
}
|
| 768 |
+
});
|
| 769 |
+
}
|
| 770 |
+
|
| 771 |
+
// ══ CALCULATOR 3: SIP ═════════════════════════════════════════════
|
| 772 |
+
function setSipFreq(freq, btn) {
|
| 773 |
+
sipFreq = freq;
|
| 774 |
+
document.querySelectorAll('.freq-btn').forEach(b=>b.classList.remove('active'));
|
| 775 |
+
btn.classList.add('active');
|
| 776 |
+
calcSIP();
|
| 777 |
+
}
|
| 778 |
+
|
| 779 |
+
function calcSIP() {
|
| 780 |
+
const amount = parseFloat(document.getElementById('sip-amount').value);
|
| 781 |
+
const years = parseInt(document.getElementById('sip-years').value);
|
| 782 |
+
const rate = parseFloat(document.getElementById('sip-rate').value)/100;
|
| 783 |
+
const stepup = parseFloat(document.getElementById('sip-stepup').value)/100;
|
| 784 |
+
const n = FREQ_MAP[sipFreq];
|
| 785 |
+
const lbl = FREQ_LABEL[sipFreq];
|
| 786 |
+
|
| 787 |
+
document.getElementById('sip-amount-val').textContent = '$'+amount.toLocaleString()+'/'+lbl;
|
| 788 |
+
document.getElementById('sip-years-val').textContent = years + ' year' + (years===1?'':'s');
|
| 789 |
+
document.getElementById('sip-rate-val').textContent = rate*100 + '%';
|
| 790 |
+
document.getElementById('sip-stepup-val').textContent = (stepup*100).toFixed(0) + '%';
|
| 791 |
+
|
| 792 |
+
// Step-up SIP calculation
|
| 793 |
+
let FV = 0, totalInvested = 0;
|
| 794 |
+
let currentAmount = amount;
|
| 795 |
+
const yearlyVals = [], yearlyInv = [];
|
| 796 |
+
|
| 797 |
+
for (let y=1; y<=years; y++) {
|
| 798 |
+
if (y>1 && stepup>0) currentAmount *= (1+stepup);
|
| 799 |
+
for (let p=0; p<n; p++) {
|
| 800 |
+
FV = (FV + currentAmount) * (1 + rate/n);
|
| 801 |
+
totalInvested += currentAmount;
|
| 802 |
+
}
|
| 803 |
+
yearlyVals.push(FV);
|
| 804 |
+
yearlyInv.push(totalInvested);
|
| 805 |
+
}
|
| 806 |
+
|
| 807 |
+
const gain = FV - totalInvested;
|
| 808 |
+
const gainPct = (gain/totalInvested)*100;
|
| 809 |
+
|
| 810 |
+
document.getElementById('sip-future').textContent = fmt$(FV);
|
| 811 |
+
document.getElementById('sip-invested').textContent = fmt$(totalInvested);
|
| 812 |
+
document.getElementById('sip-rb-inv').textContent = fmt$(totalInvested);
|
| 813 |
+
document.getElementById('sip-rb-gain').textContent = fmt$(gain);
|
| 814 |
+
document.getElementById('sip-rb-pct').textContent = '+'+gainPct.toFixed(1)+'%';
|
| 815 |
+
document.getElementById('sip-no-invest').textContent = fmt$(totalInvested);
|
| 816 |
+
document.getElementById('sip-with-invest').textContent = fmt$(FV);
|
| 817 |
+
|
| 818 |
+
if (sipChart) sipChart.destroy();
|
| 819 |
+
const ctx = document.getElementById('sipChart').getContext('2d');
|
| 820 |
+
const labels = Array.from({length:years},(_,i)=>'Yr '+(i+1));
|
| 821 |
+
|
| 822 |
+
sipChart = new Chart(ctx, {
|
| 823 |
+
type:'bar',
|
| 824 |
+
data:{
|
| 825 |
+
labels,
|
| 826 |
+
datasets:[
|
| 827 |
+
{ label:'Total Invested', data:yearlyInv, backgroundColor:'rgba(34,211,238,0.4)', borderRadius:4, order:2 },
|
| 828 |
+
{ label:'Portfolio Value', data:yearlyVals, type:'line', borderColor:'#10b981', backgroundColor:'rgba(16,185,129,0.15)', borderWidth:2.5, fill:true, tension:0.4, pointRadius:0, order:1 }
|
| 829 |
+
]
|
| 830 |
+
},
|
| 831 |
+
options:{ responsive:true, maintainAspectRatio:false,
|
| 832 |
+
plugins:{ legend:{labels:{font:{size:11}}}, tooltip:{ callbacks:{ label:ctx=>' '+fmt$(ctx.raw) } } },
|
| 833 |
+
scales:{
|
| 834 |
+
x:{ grid:{display:false}, stacked:false, ticks:{maxTicksLimit:8,font:{size:11}} },
|
| 835 |
+
y:{ grid:{color:'rgba(34,211,238,0.06)'}, ticks:{ callback:v=>'$'+(v/1000).toFixed(0)+'K', font:{size:11} } }
|
| 836 |
+
}
|
| 837 |
+
}
|
| 838 |
+
});
|
| 839 |
+
}
|
| 840 |
+
|
| 841 |
+
// ══ CALCULATOR 4: Inflation ════���══════════════════════════════════
|
| 842 |
+
function calcInflation() {
|
| 843 |
+
const amount = parseFloat(document.getElementById('inf-amount').value);
|
| 844 |
+
const years = parseInt(document.getElementById('inf-years').value);
|
| 845 |
+
const infRate = parseFloat(document.getElementById('inf-rate').value)/100;
|
| 846 |
+
const invRate = parseFloat(document.getElementById('inf-invest').value)/100;
|
| 847 |
+
|
| 848 |
+
document.getElementById('inf-amount-val').textContent = '$'+amount.toLocaleString();
|
| 849 |
+
document.getElementById('inf-years-val').textContent = years+' year'+(years===1?'':'s');
|
| 850 |
+
document.getElementById('inf-rate-val').textContent = (infRate*100).toFixed(1)+'%';
|
| 851 |
+
document.getElementById('inf-invest-val').textContent = (invRate*100).toFixed(1)+'%';
|
| 852 |
+
|
| 853 |
+
const realValue = amount / Math.pow(1+infRate, years);
|
| 854 |
+
const nomValue = amount; // cash under mattress, nominal stays same
|
| 855 |
+
const invValue = amount * Math.pow(1+invRate, years);
|
| 856 |
+
const loss = amount - realValue;
|
| 857 |
+
|
| 858 |
+
document.getElementById('inf-today').textContent = fmt$(amount);
|
| 859 |
+
document.getElementById('inf-future-worth').textContent = fmt$(realValue);
|
| 860 |
+
document.getElementById('inf-years-txt').textContent = years;
|
| 861 |
+
document.getElementById('inf-result').textContent = fmt$(realValue);
|
| 862 |
+
document.getElementById('inf-loss').textContent = fmt$(loss);
|
| 863 |
+
document.getElementById('inf-rb-nom').textContent = fmt$(nomValue);
|
| 864 |
+
document.getElementById('inf-rb-real').textContent = fmt$(realValue);
|
| 865 |
+
document.getElementById('inf-rb-inv').textContent = fmt$(invValue);
|
| 866 |
+
|
| 867 |
+
// Chart
|
| 868 |
+
const yrs = Array.from({length:years+1},(_,i)=>i);
|
| 869 |
+
const cash = yrs.map(()=>amount);
|
| 870 |
+
const real = yrs.map(y => amount/Math.pow(1+infRate,y));
|
| 871 |
+
const inv = yrs.map(y => amount*Math.pow(1+invRate,y));
|
| 872 |
+
|
| 873 |
+
if (infChart) infChart.destroy();
|
| 874 |
+
const ctx = document.getElementById('infChart').getContext('2d');
|
| 875 |
+
|
| 876 |
+
infChart = new Chart(ctx, {
|
| 877 |
+
type:'line',
|
| 878 |
+
data:{
|
| 879 |
+
labels: yrs.map(y=>'Yr '+y),
|
| 880 |
+
datasets:[
|
| 881 |
+
{ label:'Invested', data:inv, borderColor:'#10b981', backgroundColor:'rgba(16,185,129,0.1)', borderWidth:2.5, fill:true, tension:0.4, pointRadius:0 },
|
| 882 |
+
{ label:'Cash (nominal)', data:cash, borderColor:'rgba(34,211,238,0.5)', borderDash:[5,5], borderWidth:1.5, fill:false, pointRadius:0 },
|
| 883 |
+
{ label:'Real Value (after inflation)', data:real, borderColor:'#f43f5e', backgroundColor:'rgba(244,63,94,0.1)', borderWidth:2, fill:true, tension:0.4, pointRadius:0 }
|
| 884 |
+
]
|
| 885 |
+
},
|
| 886 |
+
options:{ responsive:true, maintainAspectRatio:false,
|
| 887 |
+
plugins:{ legend:{labels:{font:{size:11}}}, tooltip:{ callbacks:{ label:ctx=>' '+fmt$(ctx.raw) } } },
|
| 888 |
+
scales:{
|
| 889 |
+
x:{ grid:{display:false}, ticks:{maxTicksLimit:8,font:{size:11}} },
|
| 890 |
+
y:{ grid:{color:'rgba(34,211,238,0.06)'}, ticks:{ callback:v=>'$'+(v/1000).toFixed(0)+'K', font:{size:11} } }
|
| 891 |
+
}
|
| 892 |
+
}
|
| 893 |
+
});
|
| 894 |
+
}
|
| 895 |
+
|
| 896 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 897 |
+
applyChartDefaults();
|
| 898 |
+
calcGrowth();
|
| 899 |
+
calcRetirement();
|
| 900 |
+
calcSIP();
|
| 901 |
+
calcInflation();
|
| 902 |
+
});
|
| 903 |
+
</script>
|
| 904 |
+
</body>
|
| 905 |
+
</html>
|
index.html
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>FinWise — Dashboard</title>
|
| 7 |
+
<link rel="stylesheet" href="shared.css">
|
| 8 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
| 9 |
+
<style>
|
| 10 |
+
.hero-banner {
|
| 11 |
+
background: linear-gradient(135deg, rgba(34,211,238,0.08) 0%, rgba(16,185,129,0.05) 100%);
|
| 12 |
+
border: 1px solid var(--border2);
|
| 13 |
+
border-radius: var(--r-lg);
|
| 14 |
+
padding: 28px 32px;
|
| 15 |
+
margin-bottom: 24px;
|
| 16 |
+
display: flex;
|
| 17 |
+
align-items: center;
|
| 18 |
+
justify-content: space-between;
|
| 19 |
+
gap: 20px;
|
| 20 |
+
position: relative;
|
| 21 |
+
overflow: hidden;
|
| 22 |
+
}
|
| 23 |
+
.hero-banner::before {
|
| 24 |
+
content: '';
|
| 25 |
+
position: absolute;
|
| 26 |
+
top: -40px; right: -40px;
|
| 27 |
+
width: 200px; height: 200px;
|
| 28 |
+
border-radius: 50%;
|
| 29 |
+
background: radial-gradient(circle, rgba(34,211,238,0.12) 0%, transparent 70%);
|
| 30 |
+
pointer-events: none;
|
| 31 |
+
}
|
| 32 |
+
.hero-greeting { font-family: var(--font-head); font-size: 13px; color: var(--text2); text-transform: uppercase; letter-spacing: 0.1em; }
|
| 33 |
+
.hero-amount { font-family: var(--font-head); font-size: 42px; font-weight: 800; line-height: 1; margin: 8px 0; }
|
| 34 |
+
.hero-sub { font-size: 14px; color: var(--text2); }
|
| 35 |
+
.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; }
|
| 36 |
+
.portfolio-value-change { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
|
| 37 |
+
|
| 38 |
+
.quick-actions {
|
| 39 |
+
display: grid;
|
| 40 |
+
grid-template-columns: repeat(4, 1fr);
|
| 41 |
+
gap: 12px;
|
| 42 |
+
margin-bottom: 24px;
|
| 43 |
+
}
|
| 44 |
+
.quick-action-card {
|
| 45 |
+
background: var(--card);
|
| 46 |
+
border: 1px solid var(--border);
|
| 47 |
+
border-radius: var(--r);
|
| 48 |
+
padding: 18px;
|
| 49 |
+
text-align: center;
|
| 50 |
+
cursor: pointer;
|
| 51 |
+
text-decoration: none;
|
| 52 |
+
transition: all var(--transition);
|
| 53 |
+
display: block;
|
| 54 |
+
}
|
| 55 |
+
.quick-action-card:hover {
|
| 56 |
+
border-color: var(--border2);
|
| 57 |
+
transform: translateY(-3px);
|
| 58 |
+
box-shadow: var(--glow-c);
|
| 59 |
+
}
|
| 60 |
+
.qa-icon { font-size: 28px; margin-bottom: 8px; }
|
| 61 |
+
.qa-label { font-size: 12px; font-weight: 700; color: var(--text2); text-transform: uppercase; letter-spacing: 0.06em; }
|
| 62 |
+
|
| 63 |
+
.chart-card { height: 100%; }
|
| 64 |
+
.chart-container { position: relative; height: 240px; }
|
| 65 |
+
|
| 66 |
+
.goal-item {
|
| 67 |
+
margin-bottom: 16px;
|
| 68 |
+
}
|
| 69 |
+
.goal-header {
|
| 70 |
+
display: flex;
|
| 71 |
+
justify-content: space-between;
|
| 72 |
+
margin-bottom: 6px;
|
| 73 |
+
font-size: 13px;
|
| 74 |
+
}
|
| 75 |
+
.goal-name { font-weight: 600; }
|
| 76 |
+
.goal-pct { color: var(--cyan); font-family: var(--font-mono); }
|
| 77 |
+
|
| 78 |
+
.activity-item {
|
| 79 |
+
display: flex;
|
| 80 |
+
align-items: center;
|
| 81 |
+
gap: 14px;
|
| 82 |
+
padding: 12px 0;
|
| 83 |
+
border-bottom: 1px solid rgba(34,211,238,0.05);
|
| 84 |
+
}
|
| 85 |
+
.activity-item:last-child { border-bottom: none; }
|
| 86 |
+
.activity-icon {
|
| 87 |
+
width: 38px; height: 38px;
|
| 88 |
+
border-radius: 10px;
|
| 89 |
+
display: flex; align-items: center; justify-content: center;
|
| 90 |
+
font-size: 16px;
|
| 91 |
+
flex-shrink: 0;
|
| 92 |
+
}
|
| 93 |
+
.activity-info { flex: 1; }
|
| 94 |
+
.activity-name { font-weight: 600; font-size: 14px; }
|
| 95 |
+
.activity-date { font-size: 12px; color: var(--text2); margin-top: 2px; }
|
| 96 |
+
.activity-amount { font-family: var(--font-mono); font-size: 14px; font-weight: 600; }
|
| 97 |
+
|
| 98 |
+
.market-overview {
|
| 99 |
+
display: grid;
|
| 100 |
+
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
|
| 101 |
+
gap: 10px;
|
| 102 |
+
}
|
| 103 |
+
.market-item {
|
| 104 |
+
background: var(--bg3);
|
| 105 |
+
border-radius: var(--r-sm);
|
| 106 |
+
padding: 12px;
|
| 107 |
+
text-align: center;
|
| 108 |
+
}
|
| 109 |
+
.market-sym { font-size: 11px; font-weight: 700; color: var(--text2); margin-bottom: 4px; }
|
| 110 |
+
.market-val { font-family: var(--font-mono); font-size: 14px; font-weight: 600; }
|
| 111 |
+
.market-chg { font-size: 11px; margin-top: 2px; font-weight: 600; }
|
| 112 |
+
|
| 113 |
+
.allocation-legend { margin-top: 12px; }
|
| 114 |
+
.legend-item {
|
| 115 |
+
display: flex; align-items: center; gap: 10px;
|
| 116 |
+
padding: 6px 0;
|
| 117 |
+
font-size: 13px;
|
| 118 |
+
}
|
| 119 |
+
.legend-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
| 120 |
+
.legend-name { flex: 1; color: var(--text2); }
|
| 121 |
+
.legend-pct { font-family: var(--font-mono); font-size: 12px; color: var(--text); font-weight: 600; }
|
| 122 |
+
</style>
|
| 123 |
+
</head>
|
| 124 |
+
<body>
|
| 125 |
+
<div class="app-shell">
|
| 126 |
+
|
| 127 |
+
<!-- Sidebar -->
|
| 128 |
+
<nav class="sidebar">
|
| 129 |
+
<div class="sidebar-logo">
|
| 130 |
+
<div class="logo-mark">
|
| 131 |
+
<div class="logo-icon">📈</div>
|
| 132 |
+
<div>
|
| 133 |
+
<div class="logo-text">FinWise</div>
|
| 134 |
+
<div class="logo-sub">Smart Investing</div>
|
| 135 |
+
</div>
|
| 136 |
+
</div>
|
| 137 |
+
</div>
|
| 138 |
+
<div class="nav-section">
|
| 139 |
+
<div class="nav-label">Main</div>
|
| 140 |
+
<a href="index.html" class="nav-item"><span class="nav-icon">🏠</span> Dashboard</a>
|
| 141 |
+
<a href="portfolio.html" class="nav-item"><span class="nav-icon">📊</span> Portfolio Builder</a>
|
| 142 |
+
<a href="risk.html" class="nav-item"><span class="nav-icon">🎯</span> Risk Analyzer</a>
|
| 143 |
+
<a href="tracker.html" class="nav-item"><span class="nav-icon">📈</span> Tracker</a>
|
| 144 |
+
<div class="nav-label">Tools</div>
|
| 145 |
+
<a href="calculators.html" class="nav-item"><span class="nav-icon">🧮</span> Calculators</a>
|
| 146 |
+
<a href="insights.html" class="nav-item"><span class="nav-icon">💡</span> Insights <span class="nav-badge">New</span></a>
|
| 147 |
+
</div>
|
| 148 |
+
<div class="sidebar-footer">
|
| 149 |
+
<div class="market-ticker">Live Market</div>
|
| 150 |
+
<div id="sidebar-tickers"></div>
|
| 151 |
+
</div>
|
| 152 |
+
</nav>
|
| 153 |
+
|
| 154 |
+
<!-- Main -->
|
| 155 |
+
<main class="main-content">
|
| 156 |
+
|
| 157 |
+
<!-- Hero Banner -->
|
| 158 |
+
<div class="hero-banner fade-in">
|
| 159 |
+
<div>
|
| 160 |
+
<div class="hero-greeting">👋 Good morning, Investor</div>
|
| 161 |
+
<div class="hero-amount" id="hero-amount">$0.00</div>
|
| 162 |
+
<div class="portfolio-value-change">
|
| 163 |
+
<span class="badge badge-emerald" id="hero-badge">▲ +$0.00 today</span>
|
| 164 |
+
<span class="text-muted text-sm">Total portfolio value</span>
|
| 165 |
+
</div>
|
| 166 |
+
<div class="hero-sub" style="margin-top:10px;">Your portfolio is performing <strong style="color:var(--cyan)">above average</strong> this month 🚀</div>
|
| 167 |
+
</div>
|
| 168 |
+
<div class="hero-actions">
|
| 169 |
+
<a href="portfolio.html" class="btn btn-primary">🔧 Build Portfolio</a>
|
| 170 |
+
<a href="tracker.html" class="btn btn-secondary">📊 View Tracker</a>
|
| 171 |
+
</div>
|
| 172 |
+
</div>
|
| 173 |
+
|
| 174 |
+
<!-- Stat Grid -->
|
| 175 |
+
<div class="stat-grid fade-in fade-in-1">
|
| 176 |
+
<div class="stat-card" style="--accent-color: var(--cyan)">
|
| 177 |
+
<div class="stat-label">Total Invested</div>
|
| 178 |
+
<div class="stat-value" id="stat-invested">$0</div>
|
| 179 |
+
<div class="stat-change up">📅 Since inception</div>
|
| 180 |
+
</div>
|
| 181 |
+
<div class="stat-card" style="--accent-color: var(--emerald)">
|
| 182 |
+
<div class="stat-label">Total Gain/Loss</div>
|
| 183 |
+
<div class="stat-value up" id="stat-gain">+$0</div>
|
| 184 |
+
<div class="stat-change up" id="stat-gain-pct">▲ 0.00%</div>
|
| 185 |
+
</div>
|
| 186 |
+
<div class="stat-card" style="--accent-color: var(--violet)">
|
| 187 |
+
<div class="stat-label">Risk Score</div>
|
| 188 |
+
<div class="stat-value" id="stat-risk">—</div>
|
| 189 |
+
<div class="stat-change" id="stat-risk-label">Moderate</div>
|
| 190 |
+
</div>
|
| 191 |
+
<div class="stat-card" style="--accent-color: var(--amber)">
|
| 192 |
+
<div class="stat-label">Diversification</div>
|
| 193 |
+
<div class="stat-value" id="stat-div">—</div>
|
| 194 |
+
<div class="stat-change up">▲ Well balanced</div>
|
| 195 |
+
</div>
|
| 196 |
+
</div>
|
| 197 |
+
|
| 198 |
+
<!-- Quick Actions -->
|
| 199 |
+
<div class="quick-actions fade-in fade-in-2">
|
| 200 |
+
<a href="portfolio.html" class="quick-action-card"><div class="qa-icon">🏗️</div><div class="qa-label">Build Portfolio</div></a>
|
| 201 |
+
<a href="risk.html" class="quick-action-card"><div class="qa-icon">🎯</div><div class="qa-label">Risk Check</div></a>
|
| 202 |
+
<a href="calculators.html" class="quick-action-card"><div class="qa-icon">🧮</div><div class="qa-label">Calculators</div></a>
|
| 203 |
+
<a href="insights.html" class="quick-action-card"><div class="qa-icon">💡</div><div class="qa-label">AI Insights</div></a>
|
| 204 |
+
</div>
|
| 205 |
+
|
| 206 |
+
<!-- Charts Row -->
|
| 207 |
+
<div class="grid-60-40 fade-in fade-in-3" style="margin-bottom:20px">
|
| 208 |
+
<div class="card chart-card">
|
| 209 |
+
<div class="card-title">📈 Portfolio Performance <span class="badge badge-emerald" style="margin-left:auto">+12.4% YTD</span></div>
|
| 210 |
+
<div class="chart-container">
|
| 211 |
+
<canvas id="performanceChart"></canvas>
|
| 212 |
+
</div>
|
| 213 |
+
</div>
|
| 214 |
+
<div class="card">
|
| 215 |
+
<div class="card-title">🥧 Allocation</div>
|
| 216 |
+
<div style="position:relative;height:180px">
|
| 217 |
+
<canvas id="allocChart"></canvas>
|
| 218 |
+
</div>
|
| 219 |
+
<div class="allocation-legend" id="alloc-legend"></div>
|
| 220 |
+
</div>
|
| 221 |
+
</div>
|
| 222 |
+
|
| 223 |
+
<!-- Bottom Row -->
|
| 224 |
+
<div class="grid-2 fade-in fade-in-4">
|
| 225 |
+
<!-- Goals -->
|
| 226 |
+
<div class="card">
|
| 227 |
+
<div class="card-title">🎯 Goal Progress</div>
|
| 228 |
+
<div id="goals-list">
|
| 229 |
+
<div class="goal-item">
|
| 230 |
+
<div class="goal-header"><span class="goal-name">🏖️ Retirement Fund</span><span class="goal-pct">34%</span></div>
|
| 231 |
+
<div class="progress-bar"><div class="progress-fill" style="width:34%;background:linear-gradient(90deg,var(--cyan),var(--emerald))"></div></div>
|
| 232 |
+
<div class="text-sm text-muted" style="margin-top:4px">$34,000 / $100,000 target by 2045</div>
|
| 233 |
+
</div>
|
| 234 |
+
<div class="goal-item">
|
| 235 |
+
<div class="goal-header"><span class="goal-name">🏠 Down Payment</span><span class="goal-pct">61%</span></div>
|
| 236 |
+
<div class="progress-bar"><div class="progress-fill" style="width:61%;background:linear-gradient(90deg,var(--violet),var(--cyan))"></div></div>
|
| 237 |
+
<div class="text-sm text-muted" style="margin-top:4px">$30,500 / $50,000 target by 2027</div>
|
| 238 |
+
</div>
|
| 239 |
+
<div class="goal-item">
|
| 240 |
+
<div class="goal-header"><span class="goal-name">📚 Education Fund</span><span class="goal-pct">18%</span></div>
|
| 241 |
+
<div class="progress-bar"><div class="progress-fill" style="width:18%;background:linear-gradient(90deg,var(--amber),var(--rose))"></div></div>
|
| 242 |
+
<div class="text-sm text-muted" style="margin-top:4px">$3,600 / $20,000 target by 2030</div>
|
| 243 |
+
</div>
|
| 244 |
+
</div>
|
| 245 |
+
<a href="calculators.html" class="btn btn-secondary btn-sm btn-full" style="margin-top:16px">Plan a new goal →</a>
|
| 246 |
+
</div>
|
| 247 |
+
|
| 248 |
+
<!-- Recent Activity -->
|
| 249 |
+
<div class="card">
|
| 250 |
+
<div class="card-title">⚡ Recent Activity</div>
|
| 251 |
+
<div id="activity-list">
|
| 252 |
+
<div class="activity-item">
|
| 253 |
+
<div class="activity-icon" style="background:rgba(16,185,129,0.15)">💰</div>
|
| 254 |
+
<div class="activity-info"><div class="activity-name">Bought VOO</div><div class="activity-date">May 1, 2026</div></div>
|
| 255 |
+
<div class="activity-amount up">+3 shares</div>
|
| 256 |
+
</div>
|
| 257 |
+
<div class="activity-item">
|
| 258 |
+
<div class="activity-icon" style="background:rgba(34,211,238,0.15)">📥</div>
|
| 259 |
+
<div class="activity-info"><div class="activity-name">Dividend — AAPL</div><div class="activity-date">Apr 28, 2026</div></div>
|
| 260 |
+
<div class="activity-amount up">+$12.40</div>
|
| 261 |
+
</div>
|
| 262 |
+
<div class="activity-item">
|
| 263 |
+
<div class="activity-icon" style="background:rgba(139,92,246,0.15)">🔄</div>
|
| 264 |
+
<div class="activity-info"><div class="activity-name">Rebalanced Portfolio</div><div class="activity-date">Apr 20, 2026</div></div>
|
| 265 |
+
<div class="activity-amount" style="color:var(--text2)">Optimized</div>
|
| 266 |
+
</div>
|
| 267 |
+
<div class="activity-item">
|
| 268 |
+
<div class="activity-icon" style="background:rgba(245,158,11,0.15)">💹</div>
|
| 269 |
+
<div class="activity-info"><div class="activity-name">Added NVDA</div><div class="activity-date">Apr 15, 2026</div></div>
|
| 270 |
+
<div class="activity-amount up">+1 share</div>
|
| 271 |
+
</div>
|
| 272 |
+
</div>
|
| 273 |
+
<a href="tracker.html" class="btn btn-secondary btn-sm btn-full" style="margin-top:16px">Full history →</a>
|
| 274 |
+
</div>
|
| 275 |
+
</div>
|
| 276 |
+
|
| 277 |
+
<!-- Market Overview -->
|
| 278 |
+
<div class="card fade-in" style="margin-top:20px">
|
| 279 |
+
<div class="card-title">🌐 Market Overview</div>
|
| 280 |
+
<div class="market-overview">
|
| 281 |
+
<div class="market-item"><div class="market-sym">S&P 500</div><div class="market-val">5,308</div><div class="market-chg up">▲ +0.26%</div></div>
|
| 282 |
+
<div class="market-item"><div class="market-sym">NASDAQ</div><div class="market-val">16,742</div><div class="market-chg down">▼ -0.46%</div></div>
|
| 283 |
+
<div class="market-item"><div class="market-sym">DOW</div><div class="market-val">39,512</div><div class="market-chg up">▲ +0.18%</div></div>
|
| 284 |
+
<div class="market-item"><div class="market-sym">BTC</div><div class="market-val">$68,420</div><div class="market-chg up">▲ +2.14%</div></div>
|
| 285 |
+
<div class="market-item"><div class="market-sym">GOLD</div><div class="market-val">$2,318</div><div class="market-chg up">▲ +1.49%</div></div>
|
| 286 |
+
<div class="market-item"><div class="market-sym">10Y BOND</div><div class="market-val">4.48%</div><div class="market-chg down">▼ -0.03%</div></div>
|
| 287 |
+
<div class="market-item"><div class="market-sym">USD/EUR</div><div class="market-val">0.924</div><div class="market-chg down">▼ -0.12%</div></div>
|
| 288 |
+
<div class="market-item"><div class="market-sym">VIX</div><div class="market-val">14.82</div><div class="market-chg up">▲ +3.2%</div></div>
|
| 289 |
+
</div>
|
| 290 |
+
</div>
|
| 291 |
+
|
| 292 |
+
</main>
|
| 293 |
+
</div>
|
| 294 |
+
|
| 295 |
+
<!-- Mobile Bottom Nav -->
|
| 296 |
+
<nav class="bottom-nav">
|
| 297 |
+
<div class="bottom-nav-inner">
|
| 298 |
+
<a href="index.html" class="bottom-nav-item"><span class="bnav-icon">🏠</span>Home</a>
|
| 299 |
+
<a href="portfolio.html" class="bottom-nav-item"><span class="bnav-icon">📊</span>Portfolio</a>
|
| 300 |
+
<a href="risk.html" class="bottom-nav-item"><span class="bnav-icon">🎯</span>Risk</a>
|
| 301 |
+
<a href="tracker.html" class="bottom-nav-item"><span class="bnav-icon">📈</span>Track</a>
|
| 302 |
+
<a href="calculators.html" class="bottom-nav-item"><span class="bnav-icon">🧮</span>Calc</a>
|
| 303 |
+
<a href="insights.html" class="bottom-nav-item"><span class="bnav-icon">💡</span>Insights</a>
|
| 304 |
+
</div>
|
| 305 |
+
</nav>
|
| 306 |
+
|
| 307 |
+
<script src="shared.js"></script>
|
| 308 |
+
<script>
|
| 309 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 310 |
+
const portfolio = getPortfolio();
|
| 311 |
+
const currentValue = portfolio.assets.reduce((s,a) => s + a.shares * a.price, 0);
|
| 312 |
+
const invested = portfolio.totalInvested;
|
| 313 |
+
const gain = currentValue - invested;
|
| 314 |
+
const gainPct = (gain / invested) * 100;
|
| 315 |
+
const todayChange = currentValue * 0.0026;
|
| 316 |
+
|
| 317 |
+
// Hero
|
| 318 |
+
animateCounter(document.getElementById('hero-amount'), currentValue, '$');
|
| 319 |
+
document.getElementById('hero-badge').textContent = `▲ +$${todayChange.toFixed(2)} today`;
|
| 320 |
+
|
| 321 |
+
// Stats
|
| 322 |
+
document.getElementById('stat-invested').textContent = fmtK(invested);
|
| 323 |
+
const gainEl = document.getElementById('stat-gain');
|
| 324 |
+
gainEl.textContent = (gain >= 0 ? '+' : '') + fmt$(gain);
|
| 325 |
+
gainEl.className = 'stat-value ' + (gain >= 0 ? 'up' : 'down');
|
| 326 |
+
document.getElementById('stat-gain-pct').textContent = (gainPct >= 0 ? '▲ +' : '▼ ') + gainPct.toFixed(2) + '%';
|
| 327 |
+
document.getElementById('stat-gain-pct').className = 'stat-change ' + (gain >= 0 ? 'up' : 'down');
|
| 328 |
+
|
| 329 |
+
const riskScore = calcRiskScore(portfolio);
|
| 330 |
+
const divScore = calcDiversification(portfolio);
|
| 331 |
+
document.getElementById('stat-risk').textContent = riskScore + '/100';
|
| 332 |
+
const riskLabels = ['Very Low','Low','Moderate','Moderate-High','High'];
|
| 333 |
+
const riskLabel = riskLabels[Math.floor(riskScore / 25)];
|
| 334 |
+
document.getElementById('stat-risk-label').textContent = '→ ' + riskLabel;
|
| 335 |
+
document.getElementById('stat-div').textContent = divScore + '/100';
|
| 336 |
+
|
| 337 |
+
// Performance Chart
|
| 338 |
+
const history = generateHistory(90, invested * 0.88);
|
| 339 |
+
const perfCtx = document.getElementById('performanceChart').getContext('2d');
|
| 340 |
+
const grad = perfCtx.createLinearGradient(0, 0, 0, 240);
|
| 341 |
+
grad.addColorStop(0, 'rgba(34,211,238,0.25)');
|
| 342 |
+
grad.addColorStop(1, 'rgba(34,211,238,0)');
|
| 343 |
+
|
| 344 |
+
new Chart(perfCtx, {
|
| 345 |
+
type: 'line',
|
| 346 |
+
data: {
|
| 347 |
+
labels: history.filter((_,i) => i % 6 === 0).map(d => d.date),
|
| 348 |
+
datasets: [{
|
| 349 |
+
label: 'Portfolio Value',
|
| 350 |
+
data: history.filter((_,i) => i % 6 === 0).map(d => d.value),
|
| 351 |
+
borderColor: '#22d3ee',
|
| 352 |
+
backgroundColor: grad,
|
| 353 |
+
borderWidth: 2.5,
|
| 354 |
+
fill: true,
|
| 355 |
+
tension: 0.45,
|
| 356 |
+
pointRadius: 0,
|
| 357 |
+
pointHoverRadius: 5,
|
| 358 |
+
pointHoverBackgroundColor: '#22d3ee',
|
| 359 |
+
}]
|
| 360 |
+
},
|
| 361 |
+
options: {
|
| 362 |
+
responsive: true, maintainAspectRatio: false,
|
| 363 |
+
plugins: { legend: { display: false }, tooltip: {
|
| 364 |
+
callbacks: { label: ctx => ' $' + ctx.raw.toLocaleString('en-US', {maximumFractionDigits:0}) }
|
| 365 |
+
}},
|
| 366 |
+
scales: {
|
| 367 |
+
x: { grid: { display: false }, ticks: { maxTicksLimit: 6, font: { size: 11 } } },
|
| 368 |
+
y: { grid: { color: 'rgba(34,211,238,0.06)' }, ticks: {
|
| 369 |
+
callback: v => '$' + (v/1000).toFixed(0) + 'K', font: { size: 11 }
|
| 370 |
+
}}
|
| 371 |
+
},
|
| 372 |
+
interaction: { intersect: false, mode: 'index' }
|
| 373 |
+
}
|
| 374 |
+
});
|
| 375 |
+
|
| 376 |
+
// Allocation Donut
|
| 377 |
+
const allocCtx = document.getElementById('allocChart').getContext('2d');
|
| 378 |
+
new Chart(allocCtx, {
|
| 379 |
+
type: 'doughnut',
|
| 380 |
+
data: {
|
| 381 |
+
labels: portfolio.assets.map(a => a.ticker),
|
| 382 |
+
datasets: [{
|
| 383 |
+
data: portfolio.assets.map(a => a.pct),
|
| 384 |
+
backgroundColor: portfolio.assets.map(a => a.color),
|
| 385 |
+
borderColor: 'rgba(5,13,26,0.8)',
|
| 386 |
+
borderWidth: 3,
|
| 387 |
+
hoverOffset: 8
|
| 388 |
+
}]
|
| 389 |
+
},
|
| 390 |
+
options: {
|
| 391 |
+
responsive: true, maintainAspectRatio: false,
|
| 392 |
+
cutout: '70%',
|
| 393 |
+
plugins: { legend: { display: false } }
|
| 394 |
+
}
|
| 395 |
+
});
|
| 396 |
+
|
| 397 |
+
// Legend
|
| 398 |
+
const legendEl = document.getElementById('alloc-legend');
|
| 399 |
+
legendEl.innerHTML = portfolio.assets.slice(0,5).map(a => `
|
| 400 |
+
<div class="legend-item">
|
| 401 |
+
<div class="legend-dot" style="background:${a.color}"></div>
|
| 402 |
+
<span class="legend-name">${a.ticker}</span>
|
| 403 |
+
<span class="legend-pct">${a.pct}%</span>
|
| 404 |
+
</div>
|
| 405 |
+
`).join('') + (portfolio.assets.length > 5 ? `<div class="legend-item"><span class="legend-name text-muted">+ ${portfolio.assets.length-5} more</span></div>` : '');
|
| 406 |
+
});
|
| 407 |
+
</script>
|
| 408 |
+
</body>
|
| 409 |
+
</html>
|
insights.html
ADDED
|
@@ -0,0 +1,860 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>FinWise — AI Insights</title>
|
| 7 |
+
<link rel="stylesheet" href="shared.css">
|
| 8 |
+
<style>
|
| 9 |
+
/* ── AI Chat Interface ──────────────────────────────────────────── */
|
| 10 |
+
.ai-chat-wrap {
|
| 11 |
+
display: flex;
|
| 12 |
+
flex-direction: column;
|
| 13 |
+
height: 420px;
|
| 14 |
+
background: var(--bg2);
|
| 15 |
+
border: 1px solid var(--border);
|
| 16 |
+
border-radius: var(--r);
|
| 17 |
+
overflow: hidden;
|
| 18 |
+
}
|
| 19 |
+
.chat-messages {
|
| 20 |
+
flex: 1;
|
| 21 |
+
overflow-y: auto;
|
| 22 |
+
padding: 20px;
|
| 23 |
+
display: flex;
|
| 24 |
+
flex-direction: column;
|
| 25 |
+
gap: 14px;
|
| 26 |
+
}
|
| 27 |
+
.chat-message {
|
| 28 |
+
display: flex;
|
| 29 |
+
gap: 10px;
|
| 30 |
+
max-width: 90%;
|
| 31 |
+
animation: fadeInUp 0.3s ease;
|
| 32 |
+
}
|
| 33 |
+
.chat-message.user {
|
| 34 |
+
margin-left: auto;
|
| 35 |
+
flex-direction: row-reverse;
|
| 36 |
+
}
|
| 37 |
+
.chat-avatar {
|
| 38 |
+
width: 32px; height: 32px;
|
| 39 |
+
border-radius: 50%;
|
| 40 |
+
display: flex; align-items: center; justify-content: center;
|
| 41 |
+
font-size: 16px;
|
| 42 |
+
flex-shrink: 0;
|
| 43 |
+
align-self: flex-end;
|
| 44 |
+
}
|
| 45 |
+
.chat-avatar.ai-av { background: linear-gradient(135deg, var(--cyan), var(--emerald)); }
|
| 46 |
+
.chat-avatar.user-av { background: linear-gradient(135deg, var(--violet), var(--indigo)); }
|
| 47 |
+
.chat-bubble {
|
| 48 |
+
padding: 12px 16px;
|
| 49 |
+
border-radius: 14px;
|
| 50 |
+
font-size: 14px;
|
| 51 |
+
line-height: 1.6;
|
| 52 |
+
max-width: 100%;
|
| 53 |
+
}
|
| 54 |
+
.chat-bubble.ai { background: var(--card); border: 1px solid var(--border); border-bottom-left-radius: 4px; }
|
| 55 |
+
.chat-bubble.user { background: linear-gradient(135deg,var(--cyan-d),var(--cyan)); color: var(--bg); border-bottom-right-radius: 4px; }
|
| 56 |
+
.chat-bubble.ai strong { color: var(--cyan); }
|
| 57 |
+
.chat-bubble.ai em { color: var(--emerald); font-style: normal; font-weight: 600; }
|
| 58 |
+
.chat-bubble ul { padding-left: 18px; margin-top: 6px; }
|
| 59 |
+
.chat-bubble li { margin-bottom: 4px; }
|
| 60 |
+
|
| 61 |
+
.chat-typing {
|
| 62 |
+
display: flex;
|
| 63 |
+
gap: 4px;
|
| 64 |
+
padding: 14px 16px;
|
| 65 |
+
background: var(--card);
|
| 66 |
+
border: 1px solid var(--border);
|
| 67 |
+
border-radius: 14px;
|
| 68 |
+
border-bottom-left-radius: 4px;
|
| 69 |
+
width: fit-content;
|
| 70 |
+
}
|
| 71 |
+
.chat-typing span {
|
| 72 |
+
width: 7px; height: 7px;
|
| 73 |
+
border-radius: 50%;
|
| 74 |
+
background: var(--cyan);
|
| 75 |
+
animation: typing 1.2s infinite;
|
| 76 |
+
}
|
| 77 |
+
.chat-typing span:nth-child(2) { animation-delay: 0.2s; }
|
| 78 |
+
.chat-typing span:nth-child(3) { animation-delay: 0.4s; }
|
| 79 |
+
@keyframes typing { 0%,100%{opacity:0.3;transform:scale(0.8)} 50%{opacity:1;transform:scale(1)} }
|
| 80 |
+
|
| 81 |
+
.chat-input-bar {
|
| 82 |
+
border-top: 1px solid var(--border);
|
| 83 |
+
padding: 12px 16px;
|
| 84 |
+
display: flex;
|
| 85 |
+
gap: 10px;
|
| 86 |
+
background: var(--bg2);
|
| 87 |
+
}
|
| 88 |
+
.chat-input {
|
| 89 |
+
flex: 1;
|
| 90 |
+
background: var(--bg3);
|
| 91 |
+
border: 1px solid var(--border);
|
| 92 |
+
border-radius: 100px;
|
| 93 |
+
padding: 10px 18px;
|
| 94 |
+
color: var(--text);
|
| 95 |
+
font-family: var(--font-body);
|
| 96 |
+
font-size: 14px;
|
| 97 |
+
outline: none;
|
| 98 |
+
transition: border-color var(--transition);
|
| 99 |
+
width: auto;
|
| 100 |
+
}
|
| 101 |
+
.chat-input:focus { border-color: var(--cyan); }
|
| 102 |
+
.chat-send-btn {
|
| 103 |
+
width: 42px; height: 42px;
|
| 104 |
+
border-radius: 50%;
|
| 105 |
+
background: linear-gradient(135deg, var(--cyan-d), var(--cyan));
|
| 106 |
+
border: none;
|
| 107 |
+
cursor: pointer;
|
| 108 |
+
display: flex; align-items: center; justify-content: center;
|
| 109 |
+
font-size: 18px;
|
| 110 |
+
transition: all var(--transition);
|
| 111 |
+
flex-shrink: 0;
|
| 112 |
+
}
|
| 113 |
+
.chat-send-btn:hover { box-shadow: 0 0 16px rgba(34,211,238,0.5); transform: scale(1.05); }
|
| 114 |
+
.chat-send-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
| 115 |
+
|
| 116 |
+
.quick-prompts {
|
| 117 |
+
display: flex;
|
| 118 |
+
gap: 8px;
|
| 119 |
+
flex-wrap: wrap;
|
| 120 |
+
padding: 10px 16px;
|
| 121 |
+
border-top: 1px solid var(--border);
|
| 122 |
+
background: var(--bg2);
|
| 123 |
+
}
|
| 124 |
+
.quick-prompt {
|
| 125 |
+
padding: 6px 12px;
|
| 126 |
+
border-radius: 100px;
|
| 127 |
+
border: 1px solid var(--border2);
|
| 128 |
+
background: transparent;
|
| 129 |
+
color: var(--text2);
|
| 130 |
+
font-size: 12px;
|
| 131 |
+
font-weight: 600;
|
| 132 |
+
cursor: pointer;
|
| 133 |
+
font-family: var(--font-body);
|
| 134 |
+
transition: all var(--transition);
|
| 135 |
+
white-space: nowrap;
|
| 136 |
+
}
|
| 137 |
+
.quick-prompt:hover { border-color: var(--cyan); color: var(--cyan); background: rgba(34,211,238,0.06); }
|
| 138 |
+
|
| 139 |
+
/* ── Concept Cards ──────────────────────────────────────────────── */
|
| 140 |
+
.concept-grid {
|
| 141 |
+
display: grid;
|
| 142 |
+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
| 143 |
+
gap: 16px;
|
| 144 |
+
}
|
| 145 |
+
.concept-card {
|
| 146 |
+
background: var(--card);
|
| 147 |
+
border: 1px solid var(--border);
|
| 148 |
+
border-radius: var(--r);
|
| 149 |
+
padding: 20px;
|
| 150 |
+
transition: all var(--transition);
|
| 151 |
+
cursor: pointer;
|
| 152 |
+
position: relative;
|
| 153 |
+
overflow: hidden;
|
| 154 |
+
}
|
| 155 |
+
.concept-card::before {
|
| 156 |
+
content: '';
|
| 157 |
+
position: absolute;
|
| 158 |
+
top: 0; left: 0; right: 0;
|
| 159 |
+
height: 2px;
|
| 160 |
+
background: var(--concept-color, var(--cyan));
|
| 161 |
+
}
|
| 162 |
+
.concept-card:hover {
|
| 163 |
+
border-color: var(--border2);
|
| 164 |
+
transform: translateY(-3px);
|
| 165 |
+
box-shadow: 0 8px 30px rgba(0,0,0,0.4);
|
| 166 |
+
}
|
| 167 |
+
.concept-icon { font-size: 32px; margin-bottom: 10px; }
|
| 168 |
+
.concept-title { font-family: var(--font-head); font-size: 16px; font-weight: 700; margin-bottom: 6px; }
|
| 169 |
+
.concept-desc { font-size: 13px; color: var(--text2); line-height: 1.5; }
|
| 170 |
+
.concept-tag { margin-top: 12px; }
|
| 171 |
+
|
| 172 |
+
/* ── Do's & Don'ts ──────────────────────────────────────────────── */
|
| 173 |
+
.dos-donts {
|
| 174 |
+
display: grid;
|
| 175 |
+
grid-template-columns: 1fr 1fr;
|
| 176 |
+
gap: 20px;
|
| 177 |
+
}
|
| 178 |
+
.do-card {
|
| 179 |
+
background: rgba(16,185,129,0.05);
|
| 180 |
+
border: 1px solid rgba(16,185,129,0.2);
|
| 181 |
+
border-radius: var(--r);
|
| 182 |
+
padding: 20px;
|
| 183 |
+
}
|
| 184 |
+
.dont-card {
|
| 185 |
+
background: rgba(244,63,94,0.05);
|
| 186 |
+
border: 1px solid rgba(244,63,94,0.2);
|
| 187 |
+
border-radius: var(--r);
|
| 188 |
+
padding: 20px;
|
| 189 |
+
}
|
| 190 |
+
.do-header { font-family: var(--font-head); font-size: 16px; font-weight: 800; color: var(--emerald); margin-bottom: 14px; }
|
| 191 |
+
.dont-header { font-family: var(--font-head); font-size: 16px; font-weight: 800; color: var(--rose); margin-bottom: 14px; }
|
| 192 |
+
.tip-item {
|
| 193 |
+
display: flex;
|
| 194 |
+
gap: 10px;
|
| 195 |
+
margin-bottom: 12px;
|
| 196 |
+
font-size: 13px;
|
| 197 |
+
line-height: 1.5;
|
| 198 |
+
}
|
| 199 |
+
.tip-icon { flex-shrink: 0; font-size: 16px; margin-top: 1px; }
|
| 200 |
+
|
| 201 |
+
/* ── Insight Cards ──────────────────────────────────────────────── */
|
| 202 |
+
.insight-feed { display: flex; flex-direction: column; gap: 14px; }
|
| 203 |
+
.insight-item {
|
| 204 |
+
display: flex;
|
| 205 |
+
gap: 14px;
|
| 206 |
+
padding: 16px;
|
| 207 |
+
background: var(--card);
|
| 208 |
+
border: 1px solid var(--border);
|
| 209 |
+
border-radius: var(--r-sm);
|
| 210 |
+
transition: all var(--transition);
|
| 211 |
+
}
|
| 212 |
+
.insight-item:hover { border-color: var(--border2); }
|
| 213 |
+
.insight-category {
|
| 214 |
+
display: inline-flex;
|
| 215 |
+
align-items: center;
|
| 216 |
+
gap: 5px;
|
| 217 |
+
font-size: 10px;
|
| 218 |
+
font-weight: 800;
|
| 219 |
+
text-transform: uppercase;
|
| 220 |
+
letter-spacing: 0.1em;
|
| 221 |
+
margin-bottom: 6px;
|
| 222 |
+
}
|
| 223 |
+
.insight-title { font-weight: 700; font-size: 14px; margin-bottom: 5px; }
|
| 224 |
+
.insight-body { font-size: 13px; color: var(--text2); line-height: 1.5; }
|
| 225 |
+
|
| 226 |
+
/* ── Glossary ───────────────────────────────────────────────────── */
|
| 227 |
+
.glossary-grid {
|
| 228 |
+
display: grid;
|
| 229 |
+
grid-template-columns: repeat(auto-fill, minmax(240px,1fr));
|
| 230 |
+
gap: 12px;
|
| 231 |
+
}
|
| 232 |
+
.glossary-item {
|
| 233 |
+
background: var(--bg3);
|
| 234 |
+
border-radius: var(--r-sm);
|
| 235 |
+
padding: 14px;
|
| 236 |
+
border: 1px solid var(--border);
|
| 237 |
+
}
|
| 238 |
+
.glos-term { font-weight: 700; color: var(--cyan); margin-bottom: 4px; font-size: 14px; }
|
| 239 |
+
.glos-def { font-size: 12px; color: var(--text2); line-height: 1.5; }
|
| 240 |
+
|
| 241 |
+
/* ── Tabs ───────────────────────────────────────────────────────── */
|
| 242 |
+
.insight-tabs {
|
| 243 |
+
display: flex;
|
| 244 |
+
gap: 6px;
|
| 245 |
+
margin-bottom: 24px;
|
| 246 |
+
flex-wrap: wrap;
|
| 247 |
+
}
|
| 248 |
+
.ins-tab {
|
| 249 |
+
padding: 9px 18px;
|
| 250 |
+
border-radius: 100px;
|
| 251 |
+
border: 1px solid var(--border);
|
| 252 |
+
background: transparent;
|
| 253 |
+
color: var(--text2);
|
| 254 |
+
font-family: var(--font-body);
|
| 255 |
+
font-size: 13px;
|
| 256 |
+
font-weight: 600;
|
| 257 |
+
cursor: pointer;
|
| 258 |
+
transition: all var(--transition);
|
| 259 |
+
}
|
| 260 |
+
.ins-tab:hover { border-color: var(--border2); color: var(--text); }
|
| 261 |
+
.ins-tab.active { border-color: var(--cyan); background: rgba(34,211,238,0.1); color: var(--cyan); }
|
| 262 |
+
|
| 263 |
+
.ins-panel { display: none; }
|
| 264 |
+
.ins-panel.active { display: block; }
|
| 265 |
+
|
| 266 |
+
.gamification-bar {
|
| 267 |
+
background: linear-gradient(135deg, rgba(139,92,246,0.1), rgba(34,211,238,0.05));
|
| 268 |
+
border: 1px solid rgba(139,92,246,0.2);
|
| 269 |
+
border-radius: var(--r);
|
| 270 |
+
padding: 20px 24px;
|
| 271 |
+
margin-bottom: 24px;
|
| 272 |
+
display: flex;
|
| 273 |
+
align-items: center;
|
| 274 |
+
gap: 20px;
|
| 275 |
+
}
|
| 276 |
+
.xp-badge {
|
| 277 |
+
background: linear-gradient(135deg, var(--violet), var(--indigo));
|
| 278 |
+
border-radius: var(--r-sm);
|
| 279 |
+
padding: 14px;
|
| 280 |
+
text-align: center;
|
| 281 |
+
min-width: 80px;
|
| 282 |
+
}
|
| 283 |
+
.xp-val { font-family: var(--font-head); font-size: 22px; font-weight: 800; }
|
| 284 |
+
.xp-lbl { font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em; opacity: 0.8; }
|
| 285 |
+
.level-info { flex: 1; }
|
| 286 |
+
.level-name { font-family: var(--font-head); font-size: 18px; font-weight: 700; margin-bottom: 4px; }
|
| 287 |
+
.achievements {
|
| 288 |
+
display: flex;
|
| 289 |
+
gap: 8px;
|
| 290 |
+
flex-wrap: wrap;
|
| 291 |
+
margin-top: 12px;
|
| 292 |
+
}
|
| 293 |
+
.achievement {
|
| 294 |
+
background: var(--bg3);
|
| 295 |
+
border-radius: var(--r-sm);
|
| 296 |
+
padding: 8px 12px;
|
| 297 |
+
font-size: 12px;
|
| 298 |
+
display: flex;
|
| 299 |
+
align-items: center;
|
| 300 |
+
gap: 6px;
|
| 301 |
+
border: 1px solid var(--border);
|
| 302 |
+
}
|
| 303 |
+
.achievement.earned { border-color: rgba(245,158,11,0.4); background: rgba(245,158,11,0.08); }
|
| 304 |
+
</style>
|
| 305 |
+
</head>
|
| 306 |
+
<body>
|
| 307 |
+
<div class="app-shell">
|
| 308 |
+
<nav class="sidebar">
|
| 309 |
+
<div class="sidebar-logo">
|
| 310 |
+
<div class="logo-mark">
|
| 311 |
+
<div class="logo-icon">📈</div>
|
| 312 |
+
<div><div class="logo-text">FinWise</div><div class="logo-sub">Smart Investing</div></div>
|
| 313 |
+
</div>
|
| 314 |
+
</div>
|
| 315 |
+
<div class="nav-section">
|
| 316 |
+
<div class="nav-label">Main</div>
|
| 317 |
+
<a href="index.html" class="nav-item"><span class="nav-icon">🏠</span> Dashboard</a>
|
| 318 |
+
<a href="portfolio.html" class="nav-item"><span class="nav-icon">📊</span> Portfolio Builder</a>
|
| 319 |
+
<a href="risk.html" class="nav-item"><span class="nav-icon">🎯</span> Risk Analyzer</a>
|
| 320 |
+
<a href="tracker.html" class="nav-item"><span class="nav-icon">📈</span> Tracker</a>
|
| 321 |
+
<div class="nav-label">Tools</div>
|
| 322 |
+
<a href="calculators.html" class="nav-item"><span class="nav-icon">🧮</span> Calculators</a>
|
| 323 |
+
<a href="insights.html" class="nav-item"><span class="nav-icon">💡</span> Insights <span class="nav-badge">New</span></a>
|
| 324 |
+
</div>
|
| 325 |
+
<div class="sidebar-footer">
|
| 326 |
+
<div class="market-ticker">Live Market</div>
|
| 327 |
+
<div id="sidebar-tickers"></div>
|
| 328 |
+
</div>
|
| 329 |
+
</nav>
|
| 330 |
+
|
| 331 |
+
<main class="main-content">
|
| 332 |
+
<div class="page-header fade-in">
|
| 333 |
+
<div class="page-title">AI <span>Insights</span></div>
|
| 334 |
+
<div class="page-subtitle">Personalized guidance, education, and smart financial tips powered by AI</div>
|
| 335 |
+
</div>
|
| 336 |
+
|
| 337 |
+
<!-- Gamification Bar -->
|
| 338 |
+
<div class="gamification-bar fade-in">
|
| 339 |
+
<div class="xp-badge">
|
| 340 |
+
<div class="xp-val">340</div>
|
| 341 |
+
<div class="xp-lbl">XP Points</div>
|
| 342 |
+
</div>
|
| 343 |
+
<div class="level-info">
|
| 344 |
+
<div class="level-name">🌱 Growing Investor — Level 4</div>
|
| 345 |
+
<div class="text-muted text-sm">160 XP until Level 5: Savvy Saver</div>
|
| 346 |
+
<div class="progress-bar" style="margin-top:8px">
|
| 347 |
+
<div class="progress-fill" style="width:68%;background:linear-gradient(90deg,var(--violet),var(--cyan))"></div>
|
| 348 |
+
</div>
|
| 349 |
+
<div class="achievements">
|
| 350 |
+
<div class="achievement earned">🏆 First Portfolio</div>
|
| 351 |
+
<div class="achievement earned">📊 Risk Assessed</div>
|
| 352 |
+
<div class="achievement earned">🧮 Calc Explorer</div>
|
| 353 |
+
<div class="achievement" style="opacity:0.5">🎯 Goal Setter</div>
|
| 354 |
+
<div class="achievement" style="opacity:0.5">💰 $10K Milestone</div>
|
| 355 |
+
</div>
|
| 356 |
+
</div>
|
| 357 |
+
</div>
|
| 358 |
+
|
| 359 |
+
<!-- Tab Nav -->
|
| 360 |
+
<div class="insight-tabs fade-in">
|
| 361 |
+
<button class="ins-tab active" data-ins="ai-chat">🤖 AI Advisor</button>
|
| 362 |
+
<button class="ins-tab" data-ins="daily">💡 Daily Insights</button>
|
| 363 |
+
<button class="ins-tab" data-ins="concepts">📚 Concepts</button>
|
| 364 |
+
<button class="ins-tab" data-ins="dos-donts">✅ Do's & Don'ts</button>
|
| 365 |
+
<button class="ins-tab" data-ins="glossary">📖 Glossary</button>
|
| 366 |
+
</div>
|
| 367 |
+
|
| 368 |
+
<!-- ════════════════════════════════════════════════════════════
|
| 369 |
+
AI ADVISOR TAB
|
| 370 |
+
════════════════════════════════════════════════════════════ -->
|
| 371 |
+
<div class="ins-panel active fade-in" id="ins-ai-chat">
|
| 372 |
+
<div class="grid-60-40">
|
| 373 |
+
<div>
|
| 374 |
+
<div class="card" style="padding:0;overflow:hidden">
|
| 375 |
+
<div style="padding:16px 20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px">
|
| 376 |
+
<div style="width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--cyan),var(--emerald));display:flex;align-items:center;justify-content:center;font-size:18px">🤖</div>
|
| 377 |
+
<div>
|
| 378 |
+
<div style="font-weight:700;font-size:15px">FinWise AI</div>
|
| 379 |
+
<div style="font-size:12px;color:var(--emerald)">● Online · Powered by Claude</div>
|
| 380 |
+
</div>
|
| 381 |
+
<div style="margin-left:auto">
|
| 382 |
+
<span class="badge badge-cyan">Beta</span>
|
| 383 |
+
</div>
|
| 384 |
+
</div>
|
| 385 |
+
<div class="ai-chat-wrap" style="border:none;border-radius:0;height:380px">
|
| 386 |
+
<div class="chat-messages" id="chat-messages">
|
| 387 |
+
<!-- Initial message -->
|
| 388 |
+
<div class="chat-message">
|
| 389 |
+
<div class="chat-avatar ai-av">🤖</div>
|
| 390 |
+
<div class="chat-bubble ai">
|
| 391 |
+
<strong>Hey there, investor! 👋</strong><br><br>
|
| 392 |
+
I'm your AI financial advisor. I can help you understand your portfolio, explain investing concepts, and give personalized guidance based on your goals.<br><br>
|
| 393 |
+
<em>What would you like to explore today?</em>
|
| 394 |
+
</div>
|
| 395 |
+
</div>
|
| 396 |
+
</div>
|
| 397 |
+
<div class="quick-prompts">
|
| 398 |
+
<button class="quick-prompt" onclick="sendQuickPrompt(this)">📊 Analyze my portfolio</button>
|
| 399 |
+
<button class="quick-prompt" onclick="sendQuickPrompt(this)">🎯 Am I taking too much risk?</button>
|
| 400 |
+
<button class="quick-prompt" onclick="sendQuickPrompt(this)">💡 How do ETFs work?</button>
|
| 401 |
+
<button class="quick-prompt" onclick="sendQuickPrompt(this)">🏖️ Retirement planning tips</button>
|
| 402 |
+
<button class="quick-prompt" onclick="sendQuickPrompt(this)">📈 What is dollar-cost averaging?</button>
|
| 403 |
+
<button class="quick-prompt" onclick="sendQuickPrompt(this)">⚡ Should I rebalance now?</button>
|
| 404 |
+
</div>
|
| 405 |
+
<div class="chat-input-bar">
|
| 406 |
+
<input type="text" class="chat-input" id="chat-input" placeholder="Ask me anything about investing…" onkeydown="if(event.key==='Enter')sendMessage()">
|
| 407 |
+
<button class="chat-send-btn" id="send-btn" onclick="sendMessage()">➤</button>
|
| 408 |
+
</div>
|
| 409 |
+
</div>
|
| 410 |
+
</div>
|
| 411 |
+
</div>
|
| 412 |
+
|
| 413 |
+
<!-- Sidebar topics -->
|
| 414 |
+
<div>
|
| 415 |
+
<div class="card">
|
| 416 |
+
<div class="card-title">📌 Suggested Topics</div>
|
| 417 |
+
<div class="insight-feed">
|
| 418 |
+
<div class="insight-item" style="cursor:pointer" onclick="sendText('What is compound interest and why is it important?')">
|
| 419 |
+
<div>
|
| 420 |
+
<div class="insight-category" style="color:var(--cyan)">🎓 Education</div>
|
| 421 |
+
<div class="insight-title">The Magic of Compound Interest</div>
|
| 422 |
+
<div class="insight-body">Why time in market beats timing the market</div>
|
| 423 |
+
</div>
|
| 424 |
+
</div>
|
| 425 |
+
<div class="insight-item" style="cursor:pointer" onclick="sendText('Explain the 4% rule for retirement withdrawals')">
|
| 426 |
+
<div>
|
| 427 |
+
<div class="insight-category" style="color:var(--emerald)">🏖️ Retirement</div>
|
| 428 |
+
<div class="insight-title">The 4% Withdrawal Rule</div>
|
| 429 |
+
<div class="insight-body">How much you can safely spend in retirement</div>
|
| 430 |
+
</div>
|
| 431 |
+
</div>
|
| 432 |
+
<div class="insight-item" style="cursor:pointer" onclick="sendText('What is portfolio rebalancing and when should I do it?')">
|
| 433 |
+
<div>
|
| 434 |
+
<div class="insight-category" style="color:var(--violet)">⚖️ Strategy</div>
|
| 435 |
+
<div class="insight-title">When to Rebalance</div>
|
| 436 |
+
<div class="insight-body">Keep your risk in check automatically</div>
|
| 437 |
+
</div>
|
| 438 |
+
</div>
|
| 439 |
+
<div class="insight-item" style="cursor:pointer" onclick="sendText('What is the difference between ETFs and mutual funds?')">
|
| 440 |
+
<div>
|
| 441 |
+
<div class="insight-category" style="color:var(--amber)">📊 Basics</div>
|
| 442 |
+
<div class="insight-title">ETFs vs Mutual Funds</div>
|
| 443 |
+
<div class="insight-body">Which is better for beginner investors?</div>
|
| 444 |
+
</div>
|
| 445 |
+
</div>
|
| 446 |
+
</div>
|
| 447 |
+
</div>
|
| 448 |
+
|
| 449 |
+
<!-- AI context card -->
|
| 450 |
+
<div class="card" style="margin-top:16px;background:linear-gradient(135deg,rgba(34,211,238,0.06),rgba(16,185,129,0.03))">
|
| 451 |
+
<div class="card-title">📊 Your Portfolio Context</div>
|
| 452 |
+
<div id="context-summary" style="font-size:13px;color:var(--text2);line-height:1.6"></div>
|
| 453 |
+
</div>
|
| 454 |
+
</div>
|
| 455 |
+
</div>
|
| 456 |
+
</div>
|
| 457 |
+
|
| 458 |
+
<!-- ════════════════════════════════════════════════════════════
|
| 459 |
+
DAILY INSIGHTS TAB
|
| 460 |
+
════════════════════════════════════════════════════════════ -->
|
| 461 |
+
<div class="ins-panel" id="ins-daily">
|
| 462 |
+
<div class="insight-feed">
|
| 463 |
+
<div class="insight-item">
|
| 464 |
+
<div style="font-size:36px">🌅</div>
|
| 465 |
+
<div>
|
| 466 |
+
<div class="insight-category badge-cyan">Daily Tip</div>
|
| 467 |
+
<div class="insight-title">Automate Your Investments</div>
|
| 468 |
+
<div class="insight-body">Set up automatic monthly contributions — even $100/month invested consistently can grow to over $200,000 in 30 years at a 10% return. Automation removes emotion from the equation and builds the habit effortlessly.</div>
|
| 469 |
+
</div>
|
| 470 |
+
</div>
|
| 471 |
+
<div class="insight-item">
|
| 472 |
+
<div style="font-size:36px">📰</div>
|
| 473 |
+
<div>
|
| 474 |
+
<div class="insight-category badge-amber">Market Insight</div>
|
| 475 |
+
<div class="insight-title">Fed Policy & Your Bonds</div>
|
| 476 |
+
<div class="insight-body">When interest rates rise, bond prices fall. If you're holding BND or similar ETFs in your portfolio, rising rates reduce their short-term value — but also mean higher yields going forward. Long-term investors should stay the course.</div>
|
| 477 |
+
</div>
|
| 478 |
+
</div>
|
| 479 |
+
<div class="insight-item">
|
| 480 |
+
<div style="font-size:36px">⚡</div>
|
| 481 |
+
<div>
|
| 482 |
+
<div class="insight-category badge-emerald">Action Item</div>
|
| 483 |
+
<div class="insight-title">Review Your Emergency Fund First</div>
|
| 484 |
+
<div class="insight-body">Before investing more, ensure you have 3-6 months of expenses in a high-yield savings account (HYSA). With rates above 4.5% APY at some banks, your emergency fund can earn real money while staying liquid.</div>
|
| 485 |
+
</div>
|
| 486 |
+
</div>
|
| 487 |
+
<div class="insight-item">
|
| 488 |
+
<div style="font-size:36px">🧠</div>
|
| 489 |
+
<div>
|
| 490 |
+
<div class="insight-category badge-violet">Behavioral Finance</div>
|
| 491 |
+
<div class="insight-title">Don't Check Your Portfolio Daily</div>
|
| 492 |
+
<div class="insight-body">Studies show that investors who check their portfolios more often make more emotional trades and achieve worse returns. Set a calendar reminder to review quarterly — not daily. Your future self will thank you.</div>
|
| 493 |
+
</div>
|
| 494 |
+
</div>
|
| 495 |
+
<div class="insight-item">
|
| 496 |
+
<div style="font-size:36px">💡</div>
|
| 497 |
+
<div>
|
| 498 |
+
<div class="insight-category badge-rose">Watch Out For</div>
|
| 499 |
+
<div class="insight-title">Expense Ratios Add Up</div>
|
| 500 |
+
<div class="insight-body">A fund with 1% expense ratio vs 0.03% (like VOO) costs you over $250,000 on a $100K investment over 30 years. Always compare expense ratios before buying mutual funds or ETFs.</div>
|
| 501 |
+
</div>
|
| 502 |
+
</div>
|
| 503 |
+
</div>
|
| 504 |
+
</div>
|
| 505 |
+
|
| 506 |
+
<!-- ════════════════════════════════════════════════════════════
|
| 507 |
+
CONCEPTS TAB
|
| 508 |
+
════════════════════════════════════════════════════════════ -->
|
| 509 |
+
<div class="ins-panel" id="ins-concepts">
|
| 510 |
+
<div class="concept-grid">
|
| 511 |
+
<div class="concept-card" style="--concept-color:var(--cyan)" onclick="sendText('Explain compound interest with a real example')">
|
| 512 |
+
<div class="concept-icon">⚡</div>
|
| 513 |
+
<div class="concept-title">Compound Interest</div>
|
| 514 |
+
<div class="concept-desc">The 8th wonder of the world — earning interest on your interest. Time is your greatest asset.</div>
|
| 515 |
+
<div class="concept-tag"><span class="badge badge-cyan">Beginner</span></div>
|
| 516 |
+
</div>
|
| 517 |
+
<div class="concept-card" style="--concept-color:var(--emerald)" onclick="sendText('What is dollar-cost averaging and how do I use it?')">
|
| 518 |
+
<div class="concept-icon">📅</div>
|
| 519 |
+
<div class="concept-title">Dollar-Cost Averaging</div>
|
| 520 |
+
<div class="concept-desc">Invest a fixed amount regularly regardless of price. Reduces the impact of volatility automatically.</div>
|
| 521 |
+
<div class="concept-tag"><span class="badge badge-emerald">Strategy</span></div>
|
| 522 |
+
</div>
|
| 523 |
+
<div class="concept-card" style="--concept-color:var(--violet)" onclick="sendText('Explain diversification and why it matters')">
|
| 524 |
+
<div class="concept-icon">🌍</div>
|
| 525 |
+
<div class="concept-title">Diversification</div>
|
| 526 |
+
<div class="concept-desc">Don't put all your eggs in one basket. Spreading across assets reduces risk without sacrificing returns.</div>
|
| 527 |
+
<div class="concept-tag"><span class="badge badge-violet">Risk Management</span></div>
|
| 528 |
+
</div>
|
| 529 |
+
<div class="concept-card" style="--concept-color:var(--amber)" onclick="sendText('What is asset allocation and how should I allocate my portfolio?')">
|
| 530 |
+
<div class="concept-icon">🥧</div>
|
| 531 |
+
<div class="concept-title">Asset Allocation</div>
|
| 532 |
+
<div class="concept-desc">How you divide money between stocks, bonds, and other assets. The most important investment decision you'll make.</div>
|
| 533 |
+
<div class="concept-tag"><span class="badge badge-amber">Portfolio</span></div>
|
| 534 |
+
</div>
|
| 535 |
+
<div class="concept-card" style="--concept-color:var(--rose)" onclick="sendText('What is beta in investing and what does it tell me?')">
|
| 536 |
+
<div class="concept-icon">β</div>
|
| 537 |
+
<div class="concept-title">Beta & Volatility</div>
|
| 538 |
+
<div class="concept-desc">Beta measures how much an asset moves relative to the market. A beta of 1.5 means 50% more volatile than S&P 500.</div>
|
| 539 |
+
<div class="concept-tag"><span class="badge badge-rose">Metrics</span></div>
|
| 540 |
+
</div>
|
| 541 |
+
<div class="concept-card" style="--concept-color:var(--indigo)" onclick="sendText('What is an index fund and why do most pros recommend it?')">
|
| 542 |
+
<div class="concept-icon">📊</div>
|
| 543 |
+
<div class="concept-title">Index Funds</div>
|
| 544 |
+
<div class="concept-desc">Passively track a market index like the S&P 500. Low cost, diversified, and statistically outperform most active funds.</div>
|
| 545 |
+
<div class="concept-tag"><span class="badge badge-cyan">Beginner</span></div>
|
| 546 |
+
</div>
|
| 547 |
+
<div class="concept-card" style="--concept-color:var(--emerald)" onclick="sendText('Explain the 4% rule for retirement')">
|
| 548 |
+
<div class="concept-icon">🏖️</div>
|
| 549 |
+
<div class="concept-title">The 4% Rule</div>
|
| 550 |
+
<div class="concept-desc">Withdraw 4% of your nest egg annually in retirement. Based on 95% survival rate across 30-year historical periods.</div>
|
| 551 |
+
<div class="concept-tag"><span class="badge badge-emerald">Retirement</span></div>
|
| 552 |
+
</div>
|
| 553 |
+
<div class="concept-card" style="--concept-color:var(--cyan)" onclick="sendText('What is rebalancing and when should I rebalance my portfolio?')">
|
| 554 |
+
<div class="concept-icon">⚖️</div>
|
| 555 |
+
<div class="concept-title">Rebalancing</div>
|
| 556 |
+
<div class="concept-desc">Selling overweight assets and buying underweight ones to restore your target allocation. Do it annually or at 5% drift.</div>
|
| 557 |
+
<div class="concept-tag"><span class="badge badge-cyan">Strategy</span></div>
|
| 558 |
+
</div>
|
| 559 |
+
<div class="concept-card" style="--concept-color:var(--amber)" onclick="sendText('What is expense ratio and how does it affect my returns?')">
|
| 560 |
+
<div class="concept-icon">💸</div>
|
| 561 |
+
<div class="concept-title">Expense Ratios</div>
|
| 562 |
+
<div class="concept-desc">The annual fee funds charge as % of your investment. Even 0.5% more can cost $100K+ over 30 years.</div>
|
| 563 |
+
<div class="concept-tag"><span class="badge badge-amber">Cost</span></div>
|
| 564 |
+
</div>
|
| 565 |
+
</div>
|
| 566 |
+
</div>
|
| 567 |
+
|
| 568 |
+
<!-- ════════════════════════════════════════════════════════════
|
| 569 |
+
DO'S & DON'TS TAB
|
| 570 |
+
════════════════════════════════════════════════════════════ -->
|
| 571 |
+
<div class="ins-panel" id="ins-dos-donts">
|
| 572 |
+
<div class="dos-donts">
|
| 573 |
+
<div class="do-card">
|
| 574 |
+
<div class="do-header">✅ Do This</div>
|
| 575 |
+
<div class="tip-item"><div class="tip-icon">🕰️</div><div><strong>Start early</strong> — Even $50/month at 22 beats $500/month starting at 40. Time is your biggest multiplier.</div></div>
|
| 576 |
+
<div class="tip-item"><div class="tip-icon">📅</div><div><strong>Automate contributions</strong> — Set it and forget it. Remove emotion and ensure consistency every month.</div></div>
|
| 577 |
+
<div class="tip-item"><div class="tip-icon">📊</div><div><strong>Keep expense ratios low</strong> — Choose ETFs like VOO (0.03%) over actively managed funds (1%+).</div></div>
|
| 578 |
+
<div class="tip-item"><div class="tip-icon">🌍</div><div><strong>Diversify broadly</strong> — No single stock should exceed 10% of your portfolio when starting out.</div></div>
|
| 579 |
+
<div class="tip-item"><div class="tip-icon">💰</div><div><strong>Max out tax-advantaged accounts</strong> — 401k, Roth IRA, or HSA first before taxable accounts.</div></div>
|
| 580 |
+
<div class="tip-item"><div class="tip-icon">🛡️</div><div><strong>Maintain an emergency fund</strong> — 3–6 months of expenses in cash before investing aggressively.</div></div>
|
| 581 |
+
<div class="tip-item"><div class="tip-icon">📚</div><div><strong>Keep learning</strong> — Read one finance book a year. Try "The Little Book of Common Sense Investing" by Bogle.</div></div>
|
| 582 |
+
<div class="tip-item"><div class="tip-icon">📆</div><div><strong>Review quarterly</strong> — Check your portfolio every 3 months, not every day. Avoid reaction trading.</div></div>
|
| 583 |
+
</div>
|
| 584 |
+
<div class="dont-card">
|
| 585 |
+
<div class="dont-header">❌ Don't Do This</div>
|
| 586 |
+
<div class="tip-item"><div class="tip-icon">📰</div><div><strong>Don't invest based on news</strong> — By the time it's news, the market has already priced it in. Stay the course.</div></div>
|
| 587 |
+
<div class="tip-item"><div class="tip-icon">🎲</div><div><strong>Don't YOLO into meme stocks</strong> — Treat speculative picks as entertainment money — never more than 5% of portfolio.</div></div>
|
| 588 |
+
<div class="tip-item"><div class="tip-icon">😱</div><div><strong>Don't panic sell in crashes</strong> — Market drops of 20–40% are normal and temporary. Selling locks in your loss permanently.</div></div>
|
| 589 |
+
<div class="tip-item"><div class="tip-icon">⏰</div><div><strong>Don't try to time the market</strong> — Missing the 10 best days in the market over 20 years can cut your returns in half.</div></div>
|
| 590 |
+
<div class="tip-item"><div class="tip-icon">💳</div><div><strong>Don't invest money you need soon</strong> — Any money needed within 3 years should be in a HYSA, not the market.</div></div>
|
| 591 |
+
<div class="tip-item"><div class="tip-icon">🤙</div><div><strong>Don't take tips from social media</strong> — Most "finfluencers" are not licensed advisors. Verify everything independently.</div></div>
|
| 592 |
+
<div class="tip-item"><div class="tip-icon">🏦</div><div><strong>Don't ignore fees</strong> — Trading commissions, fund fees, and advisory fees compound over time just like returns do — negatively.</div></div>
|
| 593 |
+
<div class="tip-item"><div class="tip-icon">🔮</div><div><strong>Don't predict the market</strong> — Even professional fund managers fail to consistently beat the index. Humility pays.</div></div>
|
| 594 |
+
</div>
|
| 595 |
+
</div>
|
| 596 |
+
</div>
|
| 597 |
+
|
| 598 |
+
<!-- ════════════════════════════════════════════════════════════
|
| 599 |
+
GLOSSARY TAB
|
| 600 |
+
════════════════════════════════════════════════════════════ -->
|
| 601 |
+
<div class="ins-panel" id="ins-glossary">
|
| 602 |
+
<div style="margin-bottom:16px">
|
| 603 |
+
<input type="text" id="glossary-search" placeholder="🔍 Search terms…" oninput="filterGlossary()" style="max-width:320px">
|
| 604 |
+
</div>
|
| 605 |
+
<div class="glossary-grid" id="glossary-grid"></div>
|
| 606 |
+
</div>
|
| 607 |
+
|
| 608 |
+
</main>
|
| 609 |
+
</div>
|
| 610 |
+
|
| 611 |
+
<nav class="bottom-nav">
|
| 612 |
+
<div class="bottom-nav-inner">
|
| 613 |
+
<a href="index.html" class="bottom-nav-item"><span class="bnav-icon">🏠</span>Home</a>
|
| 614 |
+
<a href="portfolio.html" class="bottom-nav-item"><span class="bnav-icon">📊</span>Portfolio</a>
|
| 615 |
+
<a href="risk.html" class="bottom-nav-item"><span class="bnav-icon">🎯</span>Risk</a>
|
| 616 |
+
<a href="tracker.html" class="bottom-nav-item"><span class="bnav-icon">📈</span>Track</a>
|
| 617 |
+
<a href="calculators.html" class="bottom-nav-item"><span class="bnav-icon">🧮</span>Calc</a>
|
| 618 |
+
<a href="insights.html" class="bottom-nav-item"><span class="bnav-icon">💡</span>Insights</a>
|
| 619 |
+
</div>
|
| 620 |
+
</nav>
|
| 621 |
+
|
| 622 |
+
<script src="shared.js"></script>
|
| 623 |
+
<script>
|
| 624 |
+
// ── Tab Switching ─────────────────────────────────────────────────
|
| 625 |
+
document.querySelectorAll('.ins-tab').forEach(btn => {
|
| 626 |
+
btn.addEventListener('click', () => {
|
| 627 |
+
document.querySelectorAll('.ins-tab,.ins-panel').forEach(el => el.classList.remove('active'));
|
| 628 |
+
btn.classList.add('active');
|
| 629 |
+
document.getElementById('ins-' + btn.dataset.ins).classList.add('active');
|
| 630 |
+
});
|
| 631 |
+
});
|
| 632 |
+
|
| 633 |
+
// ── Portfolio Context Summary ─────────────────────────────────────
|
| 634 |
+
function buildContextSummary() {
|
| 635 |
+
const p = getPortfolio();
|
| 636 |
+
const totalVal = p.assets.reduce((s,a)=>s+a.shares*a.price,0);
|
| 637 |
+
const riskScore = calcRiskScore(p);
|
| 638 |
+
const divScore = calcDiversification(p);
|
| 639 |
+
const equityPct = p.assets.filter(a=>a.type==='Stock'||a.type==='ETF').reduce((s,a)=>s+a.pct,0);
|
| 640 |
+
document.getElementById('context-summary').innerHTML = `
|
| 641 |
+
<div style="display:flex;flex-direction:column;gap:6px">
|
| 642 |
+
<div>💼 <strong>${p.assets.length} holdings</strong> · Total: <strong>${fmt$(totalVal)}</strong></div>
|
| 643 |
+
<div>🎯 Risk Score: <strong style="color:var(--amber)">${riskScore}/100</strong> · Profile: <strong>${p.riskProfile||'Moderate'}</strong></div>
|
| 644 |
+
<div>🌍 Diversification: <strong style="color:var(--emerald)">${divScore}/100</strong></div>
|
| 645 |
+
<div>📊 Equity: <strong>${equityPct}%</strong> · Goals: <strong>${(p.goals||['Wealth Building']).join(', ')}</strong></div>
|
| 646 |
+
</div>
|
| 647 |
+
`;
|
| 648 |
+
}
|
| 649 |
+
|
| 650 |
+
// ── AI Chat ───────────────────────────────────────────────────────
|
| 651 |
+
let chatHistory = [];
|
| 652 |
+
let isLoading = false;
|
| 653 |
+
|
| 654 |
+
function buildSystemPrompt() {
|
| 655 |
+
const p = getPortfolio();
|
| 656 |
+
const totalVal = p.assets.reduce((s,a)=>s+a.shares*(MARKET_PRICES[a.ticker]?.price||a.price),0);
|
| 657 |
+
const riskScore = calcRiskScore(p);
|
| 658 |
+
const divScore = calcDiversification(p);
|
| 659 |
+
const equityPct = p.assets.filter(a=>a.type==='Stock'||a.type==='ETF').reduce((s,a)=>s+a.pct,0);
|
| 660 |
+
const bondPct = p.assets.filter(a=>a.type==='Bond').reduce((s,a)=>s+a.pct,0);
|
| 661 |
+
|
| 662 |
+
return `You are FinWise AI, a friendly, expert financial advisor built into the FinWise personal finance app.
|
| 663 |
+
|
| 664 |
+
USER'S PORTFOLIO CONTEXT:
|
| 665 |
+
- Holdings: ${p.assets.map(a=>`${a.ticker} (${a.pct}%)`).join(', ')}
|
| 666 |
+
- Total Value: $${totalVal.toFixed(2)}
|
| 667 |
+
- Total Invested: $${p.totalInvested}
|
| 668 |
+
- Risk Score: ${riskScore}/100 (${p.riskProfile || 'Moderate'})
|
| 669 |
+
- Diversification Score: ${divScore}/100
|
| 670 |
+
- Equity Allocation: ${equityPct}%
|
| 671 |
+
- Bond Allocation: ${bondPct}%
|
| 672 |
+
- Investment Goals: ${(p.goals||['Wealth Building']).join(', ')}
|
| 673 |
+
|
| 674 |
+
PERSONA:
|
| 675 |
+
- Friendly, warm, encouraging — never condescending
|
| 676 |
+
- Use simple language; avoid jargon (or explain it when necessary)
|
| 677 |
+
- Be direct and actionable — give concrete advice, not just generic platitudes
|
| 678 |
+
- Use bullet points and bold text for clarity
|
| 679 |
+
- Add relevant emojis to make responses engaging
|
| 680 |
+
- Be concise but complete — aim for 100-200 words per response
|
| 681 |
+
- Always note: you're an AI, not a licensed financial advisor
|
| 682 |
+
|
| 683 |
+
FOCUS: Help beginners understand investing. Provide personalized insights based on their actual portfolio above.`;
|
| 684 |
+
}
|
| 685 |
+
|
| 686 |
+
async function sendMessage() {
|
| 687 |
+
const input = document.getElementById('chat-input');
|
| 688 |
+
const msg = input.value.trim();
|
| 689 |
+
if (!msg || isLoading) return;
|
| 690 |
+
input.value = '';
|
| 691 |
+
sendText(msg);
|
| 692 |
+
}
|
| 693 |
+
|
| 694 |
+
function sendQuickPrompt(btn) {
|
| 695 |
+
sendText(btn.textContent.replace(/^[^ ]+ /,'').trim());
|
| 696 |
+
}
|
| 697 |
+
|
| 698 |
+
async function sendText(text) {
|
| 699 |
+
if (isLoading) return;
|
| 700 |
+
|
| 701 |
+
appendMessage('user', text);
|
| 702 |
+
chatHistory.push({ role:'user', content: text });
|
| 703 |
+
|
| 704 |
+
isLoading = true;
|
| 705 |
+
document.getElementById('send-btn').disabled = true;
|
| 706 |
+
|
| 707 |
+
const typingEl = appendTyping();
|
| 708 |
+
|
| 709 |
+
try {
|
| 710 |
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
| 711 |
+
method: 'POST',
|
| 712 |
+
headers: { 'Content-Type': 'application/json' },
|
| 713 |
+
body: JSON.stringify({
|
| 714 |
+
model: 'claude-sonnet-4-20250514',
|
| 715 |
+
max_tokens: 1000,
|
| 716 |
+
system: buildSystemPrompt(),
|
| 717 |
+
messages: chatHistory
|
| 718 |
+
})
|
| 719 |
+
});
|
| 720 |
+
|
| 721 |
+
typingEl.remove();
|
| 722 |
+
const data = await response.json();
|
| 723 |
+
|
| 724 |
+
if (data.error) throw new Error(data.error.message);
|
| 725 |
+
|
| 726 |
+
const reply = data.content?.[0]?.text || 'Sorry, I had trouble generating a response. Please try again.';
|
| 727 |
+
appendMessage('ai', formatAIReply(reply));
|
| 728 |
+
chatHistory.push({ role:'assistant', content: reply });
|
| 729 |
+
|
| 730 |
+
} catch (err) {
|
| 731 |
+
typingEl.remove();
|
| 732 |
+
// Fallback: use built-in responses
|
| 733 |
+
const fallback = getFallbackResponse(text);
|
| 734 |
+
appendMessage('ai', formatAIReply(fallback));
|
| 735 |
+
chatHistory.push({ role:'assistant', content: fallback });
|
| 736 |
+
}
|
| 737 |
+
|
| 738 |
+
isLoading = false;
|
| 739 |
+
document.getElementById('send-btn').disabled = false;
|
| 740 |
+
}
|
| 741 |
+
|
| 742 |
+
function formatAIReply(text) {
|
| 743 |
+
return text
|
| 744 |
+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
| 745 |
+
.replace(/\*(.*?)\*/g, '<em>$1</em>')
|
| 746 |
+
.replace(/\n\n/g, '<br><br>')
|
| 747 |
+
.replace(/\n- /g, '<br>• ')
|
| 748 |
+
.replace(/\n• /g, '<br>• ')
|
| 749 |
+
.replace(/\n(\d+)\. /g, '<br>$1. ');
|
| 750 |
+
}
|
| 751 |
+
|
| 752 |
+
function getFallbackResponse(query) {
|
| 753 |
+
const q = query.toLowerCase();
|
| 754 |
+
const p = getPortfolio();
|
| 755 |
+
const equityPct = p.assets.filter(a=>a.type==='Stock'||a.type==='ETF').reduce((s,a)=>s+a.pct,0);
|
| 756 |
+
const riskScore = calcRiskScore(p);
|
| 757 |
+
|
| 758 |
+
if (q.includes('portfolio') || q.includes('analyze')) {
|
| 759 |
+
return `**Here's a quick analysis of your portfolio:** 📊\n\n` +
|
| 760 |
+
`You're holding **${p.assets.length} assets** with **${equityPct}% in equities**. ` +
|
| 761 |
+
`Your risk score is **${riskScore}/100**, which puts you in the **${p.riskProfile||'Moderate'}** category.\n\n` +
|
| 762 |
+
`**Strengths:**\n- Good asset count for diversification\n- Mix of ETFs and individual stocks\n\n` +
|
| 763 |
+
`**Suggestions:**\n- Consider if bond allocation matches your timeline\n- Review any single position over 30%\n\n` +
|
| 764 |
+
`*Disclaimer: This is AI-generated analysis, not licensed financial advice.*`;
|
| 765 |
+
}
|
| 766 |
+
if (q.includes('etf') || q.includes('index')) {
|
| 767 |
+
return `**ETFs (Exchange-Traded Funds) 101:** 📊\n\nAn ETF is a basket of stocks or bonds that trades on an exchange like a single stock.\n\n**Why they're great for beginners:**\n- **Instant diversification** — VOO holds ~500 companies\n- **Low cost** — VOO charges just 0.03% annually\n- **No stock picking required** — just buy the market\n\n**Your portfolio already has ETFs:** ${p.assets.filter(a=>a.type==='ETF').map(a=>a.ticker).join(', ')||'None yet'} — great choice! 🎉`;
|
| 768 |
+
}
|
| 769 |
+
if (q.includes('compound')) {
|
| 770 |
+
return `**Compound Interest — The 8th Wonder of the World** ⚡\n\nCompound interest means earning returns on your returns.\n\n**Simple example:**\n- Invest $10,000 at 8%/year\n- Year 1: $10,800\n- Year 10: $21,589\n- Year 30: $100,627 — **10× your money!**\n\nThe key insight: **time accelerates everything**. Starting at 25 vs 35 can mean the difference of $300,000+ at retirement — even with the same contributions.`;
|
| 771 |
+
}
|
| 772 |
+
if (q.includes('risk') || q.includes('rebalance')) {
|
| 773 |
+
return `**Your Risk Profile:** 🎯\n\nYour current risk score is **${riskScore}/100**.\n\n**Quick rebalancing check:**\n- If any single asset has drifted >5% from target → trim it\n- If equity % has grown significantly → add bonds\n- Do this **annually** or when allocation drifts >5%\n\n**For your portfolio:** ${equityPct > 80 ? '⚠️ High equity concentration. Consider adding some bonds for stability.' : '✅ Equity balance looks reasonable for a growth-oriented investor.'}\n\n*Review every quarter, rebalance annually.*`;
|
| 774 |
+
}
|
| 775 |
+
if (q.includes('retirement') || q.includes('4%')) {
|
| 776 |
+
return `**The 4% Rule Explained** 🏖️\n\nThe 4% rule says: in retirement, you can safely withdraw 4% of your portfolio annually and it should last 30+ years.\n\n**Example:**\n- $1,000,000 nest egg → $40,000/year ($3,333/mo)\n- $500,000 → $20,000/year ($1,667/mo)\n\n**Your goal:** Save **25× your annual expenses**\n\n**Use our Retirement Calculator** to see exactly when you'll hit your number! 🧮`;
|
| 777 |
+
}
|
| 778 |
+
if (q.includes('dollar') || q.includes('dca') || q.includes('averaging')) {
|
| 779 |
+
return `**Dollar-Cost Averaging (DCA)** 📅\n\nInstead of investing a lump sum, you invest a fixed amount at regular intervals — regardless of the market price.\n\n**Why it works:**\n- Removes emotion from investing\n- You buy more shares when prices are low\n- You buy fewer shares when prices are high\n- Averages out your cost over time\n\n**Example:** $300/month in VOO no matter what the market does. You don't need to predict anything — just stay consistent. That's it! 🎯`;
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
+
return `**Great question!** 💡\n\nThat's an important topic in personal finance. Based on your portfolio (${p.assets.map(a=>a.ticker).join(', ')}), here's what I'd suggest:\n\n- Keep learning and asking questions like this — financial literacy compounds just like money does!\n- Use the Calculators to model different scenarios\n- Review the Concepts section for deeper dives\n\n*Connect to the internet to get fully personalized AI responses powered by Claude.* 🤖`;
|
| 783 |
+
}
|
| 784 |
+
|
| 785 |
+
function appendMessage(role, html) {
|
| 786 |
+
const msgs = document.getElementById('chat-messages');
|
| 787 |
+
const div = document.createElement('div');
|
| 788 |
+
div.className = 'chat-message ' + (role==='user'?'user':'');
|
| 789 |
+
div.innerHTML = `
|
| 790 |
+
<div class="chat-avatar ${role==='user'?'user-av':'ai-av'}">${role==='user'?'👤':'🤖'}</div>
|
| 791 |
+
<div class="chat-bubble ${role}">${html}</div>
|
| 792 |
+
`;
|
| 793 |
+
msgs.appendChild(div);
|
| 794 |
+
msgs.scrollTop = msgs.scrollHeight;
|
| 795 |
+
return div;
|
| 796 |
+
}
|
| 797 |
+
|
| 798 |
+
function appendTyping() {
|
| 799 |
+
const msgs = document.getElementById('chat-messages');
|
| 800 |
+
const div = document.createElement('div');
|
| 801 |
+
div.className = 'chat-message';
|
| 802 |
+
div.innerHTML = `
|
| 803 |
+
<div class="chat-avatar ai-av">🤖</div>
|
| 804 |
+
<div class="chat-typing"><span></span><span></span><span></span></div>
|
| 805 |
+
`;
|
| 806 |
+
msgs.appendChild(div);
|
| 807 |
+
msgs.scrollTop = msgs.scrollHeight;
|
| 808 |
+
return div;
|
| 809 |
+
}
|
| 810 |
+
|
| 811 |
+
// ── Glossary ──────────────────────────────────────────────────────
|
| 812 |
+
const GLOSSARY = [
|
| 813 |
+
{ term:'APY', def:'Annual Percentage Yield — the real return earned in a year, including compound interest.' },
|
| 814 |
+
{ term:'Asset Allocation', def:'How you divide your portfolio among different asset classes like stocks, bonds, and cash.' },
|
| 815 |
+
{ term:'Bear Market', def:'A market decline of 20% or more from recent highs, lasting at least 2 months.' },
|
| 816 |
+
{ term:'Beta', def:'Measures an asset\'s volatility vs the market. Beta>1 = more volatile than the S&P 500.' },
|
| 817 |
+
{ term:'Blue-Chip Stock', def:'Shares of large, reputable companies with long track records of stable performance.' },
|
| 818 |
+
{ term:'Bond', def:'A loan you give to a company or government. They pay you interest and return your principal at maturity.' },
|
| 819 |
+
{ term:'Bull Market', def:'A period of rising prices, generally defined as a 20%+ gain from recent lows.' },
|
| 820 |
+
{ term:'Compound Interest', def:'Earning returns on your returns. The longer you invest, the more powerful this becomes.' },
|
| 821 |
+
{ term:'Diversification', def:'Spreading investments across many assets to reduce risk without necessarily reducing returns.' },
|
| 822 |
+
{ term:'Dividend', def:'A portion of a company\'s profits paid to shareholders, usually quarterly.' },
|
| 823 |
+
{ term:'DCA', def:'Dollar-Cost Averaging — investing a fixed amount regularly, regardless of market price.' },
|
| 824 |
+
{ term:'ETF', def:'Exchange-Traded Fund — a basket of securities that trades on an exchange like a single stock.' },
|
| 825 |
+
{ term:'Expense Ratio', def:'Annual fee charged by mutual funds/ETFs, expressed as % of your investment.' },
|
| 826 |
+
{ term:'FIRE', def:'Financial Independence, Retire Early — movement focused on saving aggressively to retire young.' },
|
| 827 |
+
{ term:'Index Fund', def:'A fund that tracks a market index (like S&P 500) passively. Usually low-cost and diversified.' },
|
| 828 |
+
{ term:'Liquidity', def:'How easily an asset can be converted to cash. Stocks are liquid; real estate is not.' },
|
| 829 |
+
{ term:'Market Cap', def:'Total market value of a company\'s outstanding shares (Price × Shares Outstanding).' },
|
| 830 |
+
{ term:'P/E Ratio', def:'Price-to-Earnings ratio — how much investors pay per dollar of earnings. Measures valuation.' },
|
| 831 |
+
{ term:'Rebalancing', def:'Adjusting your portfolio back to target allocation by selling overweighted and buying underweighted assets.' },
|
| 832 |
+
{ term:'Risk Tolerance', def:'Your ability (financial) and willingness (emotional) to endure market fluctuations.' },
|
| 833 |
+
{ term:'Roth IRA', def:'A retirement account funded with after-tax money. Qualified withdrawals are tax-free.' },
|
| 834 |
+
{ term:'S&P 500', def:'An index tracking 500 of the largest US publicly traded companies by market cap.' },
|
| 835 |
+
{ term:'SIP', def:'Systematic Investment Plan — regular, automated investments at fixed intervals.' },
|
| 836 |
+
{ term:'Volatility', def:'The degree of price variation over time. Higher volatility = higher risk and potential reward.' },
|
| 837 |
+
{ term:'Yield', def:'Income generated by an investment, usually expressed as a percentage of the investment\'s value.' },
|
| 838 |
+
];
|
| 839 |
+
|
| 840 |
+
function renderGlossary(filter='') {
|
| 841 |
+
const items = filter ? GLOSSARY.filter(g=>g.term.toLowerCase().includes(filter)||g.def.toLowerCase().includes(filter)) : GLOSSARY;
|
| 842 |
+
document.getElementById('glossary-grid').innerHTML = items.map(g => `
|
| 843 |
+
<div class="glossary-item">
|
| 844 |
+
<div class="glos-term">${g.term}</div>
|
| 845 |
+
<div class="glos-def">${g.def}</div>
|
| 846 |
+
</div>
|
| 847 |
+
`).join('');
|
| 848 |
+
}
|
| 849 |
+
|
| 850 |
+
function filterGlossary() {
|
| 851 |
+
renderGlossary(document.getElementById('glossary-search').value.toLowerCase());
|
| 852 |
+
}
|
| 853 |
+
|
| 854 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 855 |
+
buildContextSummary();
|
| 856 |
+
renderGlossary();
|
| 857 |
+
});
|
| 858 |
+
</script>
|
| 859 |
+
</body>
|
| 860 |
+
</html>
|
portfolio.html
ADDED
|
@@ -0,0 +1,673 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>FinWise — Portfolio Builder</title>
|
| 7 |
+
<link rel="stylesheet" href="shared.css">
|
| 8 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
| 9 |
+
<style>
|
| 10 |
+
.builder-steps {
|
| 11 |
+
display: flex;
|
| 12 |
+
gap: 8px;
|
| 13 |
+
margin-bottom: 28px;
|
| 14 |
+
background: var(--bg3);
|
| 15 |
+
border-radius: var(--r-sm);
|
| 16 |
+
padding: 4px;
|
| 17 |
+
}
|
| 18 |
+
.step-btn {
|
| 19 |
+
flex: 1;
|
| 20 |
+
padding: 10px 8px;
|
| 21 |
+
border-radius: 6px;
|
| 22 |
+
border: none;
|
| 23 |
+
background: transparent;
|
| 24 |
+
color: var(--text2);
|
| 25 |
+
font-size: 13px;
|
| 26 |
+
font-weight: 600;
|
| 27 |
+
cursor: pointer;
|
| 28 |
+
transition: all var(--transition);
|
| 29 |
+
text-align: center;
|
| 30 |
+
white-space: nowrap;
|
| 31 |
+
}
|
| 32 |
+
.step-btn.active {
|
| 33 |
+
background: var(--card);
|
| 34 |
+
color: var(--cyan);
|
| 35 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
| 36 |
+
}
|
| 37 |
+
.step-num {
|
| 38 |
+
display: inline-flex;
|
| 39 |
+
width: 20px; height: 20px;
|
| 40 |
+
background: var(--bg3);
|
| 41 |
+
border-radius: 50%;
|
| 42 |
+
align-items: center; justify-content: center;
|
| 43 |
+
font-size: 11px;
|
| 44 |
+
margin-right: 6px;
|
| 45 |
+
}
|
| 46 |
+
.step-btn.active .step-num { background: var(--cyan); color: var(--bg); }
|
| 47 |
+
|
| 48 |
+
.step-panel { display: none; }
|
| 49 |
+
.step-panel.active { display: block; }
|
| 50 |
+
|
| 51 |
+
.goal-cards {
|
| 52 |
+
display: grid;
|
| 53 |
+
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
| 54 |
+
gap: 12px;
|
| 55 |
+
margin-bottom: 24px;
|
| 56 |
+
}
|
| 57 |
+
.goal-card {
|
| 58 |
+
background: var(--bg3);
|
| 59 |
+
border: 2px solid var(--border);
|
| 60 |
+
border-radius: var(--r);
|
| 61 |
+
padding: 20px 16px;
|
| 62 |
+
text-align: center;
|
| 63 |
+
cursor: pointer;
|
| 64 |
+
transition: all var(--transition);
|
| 65 |
+
}
|
| 66 |
+
.goal-card:hover { border-color: var(--border2); transform: translateY(-2px); }
|
| 67 |
+
.goal-card.selected { border-color: var(--cyan); background: rgba(34,211,238,0.08); box-shadow: var(--glow-c); }
|
| 68 |
+
.goal-icon { font-size: 32px; margin-bottom: 8px; }
|
| 69 |
+
.goal-title { font-size: 13px; font-weight: 700; margin-bottom: 4px; }
|
| 70 |
+
.goal-desc { font-size: 11px; color: var(--text2); }
|
| 71 |
+
|
| 72 |
+
.risk-slider-wrap {
|
| 73 |
+
padding: 20px;
|
| 74 |
+
background: var(--bg3);
|
| 75 |
+
border-radius: var(--r);
|
| 76 |
+
margin-bottom: 20px;
|
| 77 |
+
}
|
| 78 |
+
.risk-labels {
|
| 79 |
+
display: flex;
|
| 80 |
+
justify-content: space-between;
|
| 81 |
+
font-size: 12px;
|
| 82 |
+
color: var(--text2);
|
| 83 |
+
margin-top: 10px;
|
| 84 |
+
}
|
| 85 |
+
.risk-profile-display {
|
| 86 |
+
text-align: center;
|
| 87 |
+
padding: 16px;
|
| 88 |
+
background: rgba(34,211,238,0.06);
|
| 89 |
+
border-radius: var(--r-sm);
|
| 90 |
+
border: 1px solid var(--border2);
|
| 91 |
+
margin-top: 16px;
|
| 92 |
+
}
|
| 93 |
+
.rp-icon { font-size: 36px; margin-bottom: 6px; }
|
| 94 |
+
.rp-label { font-family: var(--font-head); font-size: 20px; font-weight: 700; }
|
| 95 |
+
.rp-desc { font-size: 13px; color: var(--text2); margin-top: 4px; }
|
| 96 |
+
|
| 97 |
+
.asset-card {
|
| 98 |
+
display: flex;
|
| 99 |
+
align-items: center;
|
| 100 |
+
gap: 14px;
|
| 101 |
+
padding: 16px;
|
| 102 |
+
background: var(--bg3);
|
| 103 |
+
border-radius: var(--r-sm);
|
| 104 |
+
margin-bottom: 10px;
|
| 105 |
+
border: 1px solid var(--border);
|
| 106 |
+
transition: all var(--transition);
|
| 107 |
+
}
|
| 108 |
+
.asset-card:hover { border-color: var(--border2); }
|
| 109 |
+
.asset-logo {
|
| 110 |
+
width: 42px; height: 42px;
|
| 111 |
+
border-radius: 10px;
|
| 112 |
+
display: flex; align-items: center; justify-content: center;
|
| 113 |
+
font-weight: 800;
|
| 114 |
+
font-size: 11px;
|
| 115 |
+
font-family: var(--font-mono);
|
| 116 |
+
flex-shrink: 0;
|
| 117 |
+
color: var(--bg);
|
| 118 |
+
}
|
| 119 |
+
.asset-info { flex: 1; min-width: 0; }
|
| 120 |
+
.asset-ticker { font-weight: 700; font-size: 15px; }
|
| 121 |
+
.asset-name { font-size: 12px; color: var(--text2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
| 122 |
+
.asset-type { margin-top: 3px; }
|
| 123 |
+
.asset-sliders { flex: 2; }
|
| 124 |
+
.asset-pct-val {
|
| 125 |
+
font-family: var(--font-mono);
|
| 126 |
+
font-size: 15px;
|
| 127 |
+
font-weight: 700;
|
| 128 |
+
color: var(--cyan);
|
| 129 |
+
min-width: 46px;
|
| 130 |
+
text-align: right;
|
| 131 |
+
}
|
| 132 |
+
.asset-dollar { font-size: 11px; color: var(--text2); text-align: right; margin-top: 2px; }
|
| 133 |
+
.asset-remove {
|
| 134 |
+
background: rgba(244,63,94,0.1);
|
| 135 |
+
border: none;
|
| 136 |
+
color: var(--rose);
|
| 137 |
+
border-radius: 6px;
|
| 138 |
+
width: 28px; height: 28px;
|
| 139 |
+
display: flex; align-items: center; justify-content: center;
|
| 140 |
+
cursor: pointer;
|
| 141 |
+
font-size: 14px;
|
| 142 |
+
transition: background var(--transition);
|
| 143 |
+
flex-shrink: 0;
|
| 144 |
+
}
|
| 145 |
+
.asset-remove:hover { background: rgba(244,63,94,0.25); }
|
| 146 |
+
|
| 147 |
+
.add-asset-grid {
|
| 148 |
+
display: grid;
|
| 149 |
+
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
| 150 |
+
gap: 8px;
|
| 151 |
+
margin-top: 16px;
|
| 152 |
+
}
|
| 153 |
+
.add-asset-btn {
|
| 154 |
+
padding: 10px 8px;
|
| 155 |
+
background: var(--bg3);
|
| 156 |
+
border: 1px dashed var(--border2);
|
| 157 |
+
border-radius: var(--r-sm);
|
| 158 |
+
color: var(--text2);
|
| 159 |
+
font-size: 12px;
|
| 160 |
+
font-weight: 600;
|
| 161 |
+
cursor: pointer;
|
| 162 |
+
text-align: center;
|
| 163 |
+
transition: all var(--transition);
|
| 164 |
+
font-family: var(--font-body);
|
| 165 |
+
}
|
| 166 |
+
.add-asset-btn:hover { border-color: var(--cyan); color: var(--cyan); background: rgba(34,211,238,0.05); }
|
| 167 |
+
.add-asset-btn.in-portfolio { border-style: solid; border-color: var(--emerald); color: var(--emerald); background: rgba(16,185,129,0.06); }
|
| 168 |
+
|
| 169 |
+
.portfolio-summary-side {
|
| 170 |
+
position: sticky;
|
| 171 |
+
top: 20px;
|
| 172 |
+
}
|
| 173 |
+
.pie-wrap { height: 220px; position: relative; }
|
| 174 |
+
.portfolio-total {
|
| 175 |
+
text-align: center;
|
| 176 |
+
padding: 16px;
|
| 177 |
+
background: var(--bg3);
|
| 178 |
+
border-radius: var(--r-sm);
|
| 179 |
+
margin: 16px 0;
|
| 180 |
+
}
|
| 181 |
+
.pt-label { font-size: 12px; color: var(--text2); text-transform: uppercase; letter-spacing: 0.06em; }
|
| 182 |
+
.pt-value { font-family: var(--font-head); font-size: 28px; font-weight: 800; color: var(--cyan); }
|
| 183 |
+
|
| 184 |
+
.balance-warning {
|
| 185 |
+
display: flex;
|
| 186 |
+
align-items: center;
|
| 187 |
+
gap: 10px;
|
| 188 |
+
padding: 12px 14px;
|
| 189 |
+
border-radius: var(--r-sm);
|
| 190 |
+
font-size: 13px;
|
| 191 |
+
margin-bottom: 16px;
|
| 192 |
+
}
|
| 193 |
+
.balance-warning.ok { background: rgba(16,185,129,0.1); border: 1px solid rgba(16,185,129,0.3); color: var(--emerald); }
|
| 194 |
+
.balance-warning.warn { background: rgba(245,158,11,0.1); border: 1px solid rgba(245,158,11,0.3); color: var(--amber); }
|
| 195 |
+
.balance-warning.err { background: rgba(244,63,94,0.1); border: 1px solid rgba(244,63,94,0.3); color: var(--rose); }
|
| 196 |
+
|
| 197 |
+
.rebal-btn {
|
| 198 |
+
width: 100%;
|
| 199 |
+
padding: 10px;
|
| 200 |
+
background: var(--bg3);
|
| 201 |
+
border: 1px solid var(--border);
|
| 202 |
+
border-radius: var(--r-sm);
|
| 203 |
+
color: var(--text2);
|
| 204 |
+
font-size: 13px;
|
| 205 |
+
font-weight: 600;
|
| 206 |
+
cursor: pointer;
|
| 207 |
+
transition: all var(--transition);
|
| 208 |
+
font-family: var(--font-body);
|
| 209 |
+
margin-bottom: 8px;
|
| 210 |
+
}
|
| 211 |
+
.rebal-btn:hover { border-color: var(--cyan); color: var(--cyan); background: rgba(34,211,238,0.05); }
|
| 212 |
+
</style>
|
| 213 |
+
</head>
|
| 214 |
+
<body>
|
| 215 |
+
<div class="app-shell">
|
| 216 |
+
|
| 217 |
+
<!-- Sidebar -->
|
| 218 |
+
<nav class="sidebar">
|
| 219 |
+
<div class="sidebar-logo">
|
| 220 |
+
<div class="logo-mark">
|
| 221 |
+
<div class="logo-icon">📈</div>
|
| 222 |
+
<div><div class="logo-text">FinWise</div><div class="logo-sub">Smart Investing</div></div>
|
| 223 |
+
</div>
|
| 224 |
+
</div>
|
| 225 |
+
<div class="nav-section">
|
| 226 |
+
<div class="nav-label">Main</div>
|
| 227 |
+
<a href="index.html" class="nav-item"><span class="nav-icon">🏠</span> Dashboard</a>
|
| 228 |
+
<a href="portfolio.html" class="nav-item"><span class="nav-icon">📊</span> Portfolio Builder</a>
|
| 229 |
+
<a href="risk.html" class="nav-item"><span class="nav-icon">🎯</span> Risk Analyzer</a>
|
| 230 |
+
<a href="tracker.html" class="nav-item"><span class="nav-icon">📈</span> Tracker</a>
|
| 231 |
+
<div class="nav-label">Tools</div>
|
| 232 |
+
<a href="calculators.html" class="nav-item"><span class="nav-icon">🧮</span> Calculators</a>
|
| 233 |
+
<a href="insights.html" class="nav-item"><span class="nav-icon">💡</span> Insights</a>
|
| 234 |
+
</div>
|
| 235 |
+
<div class="sidebar-footer">
|
| 236 |
+
<div class="market-ticker">Live Market</div>
|
| 237 |
+
<div id="sidebar-tickers"></div>
|
| 238 |
+
</div>
|
| 239 |
+
</nav>
|
| 240 |
+
|
| 241 |
+
<main class="main-content">
|
| 242 |
+
<div class="page-header fade-in">
|
| 243 |
+
<div class="page-title">Portfolio <span>Builder</span></div>
|
| 244 |
+
<div class="page-subtitle">Build a smart, diversified portfolio in 3 easy steps</div>
|
| 245 |
+
</div>
|
| 246 |
+
|
| 247 |
+
<!-- Step Nav -->
|
| 248 |
+
<div class="builder-steps fade-in">
|
| 249 |
+
<button class="step-btn active" data-step="1"><span class="step-num">1</span>Your Goals</button>
|
| 250 |
+
<button class="step-btn" data-step="2"><span class="step-num">2</span>Risk Profile</button>
|
| 251 |
+
<button class="step-btn" data-step="3"><span class="step-num">3</span>Build & Tune</button>
|
| 252 |
+
</div>
|
| 253 |
+
|
| 254 |
+
<div class="grid-60-40">
|
| 255 |
+
<!-- Left: Steps -->
|
| 256 |
+
<div>
|
| 257 |
+
|
| 258 |
+
<!-- Step 1: Goals -->
|
| 259 |
+
<div class="step-panel active" id="step-1">
|
| 260 |
+
<div class="card fade-in">
|
| 261 |
+
<div class="section-title">🎯 What are you investing for?</div>
|
| 262 |
+
<div class="text-muted text-sm" style="margin-bottom:20px">Select one or more goals (you can always change later)</div>
|
| 263 |
+
<div class="goal-cards" id="goal-cards">
|
| 264 |
+
<div class="goal-card" data-goal="Retirement"><div class="goal-icon">🏖️</div><div class="goal-title">Retirement</div><div class="goal-desc">Long-term wealth for a comfortable retirement</div></div>
|
| 265 |
+
<div class="goal-card" data-goal="Wealth Building"><div class="goal-icon">💰</div><div class="goal-title">Wealth Building</div><div class="goal-desc">Grow your money over time</div></div>
|
| 266 |
+
<div class="goal-card" data-goal="Down Payment"><div class="goal-icon">🏠</div><div class="goal-title">Home Purchase</div><div class="goal-desc">Save for a down payment</div></div>
|
| 267 |
+
<div class="goal-card" data-goal="Education"><div class="goal-icon">📚</div><div class="goal-title">Education</div><div class="goal-desc">Fund for college or courses</div></div>
|
| 268 |
+
<div class="goal-card" data-goal="Emergency Fund"><div class="goal-icon">🛡️</div><div class="goal-title">Emergency Fund</div><div class="goal-desc">Safety net for unexpected expenses</div></div>
|
| 269 |
+
<div class="goal-card" data-goal="Passive Income"><div class="goal-icon">💸</div><div class="goal-title">Passive Income</div><div class="goal-desc">Earn dividends & interest</div></div>
|
| 270 |
+
</div>
|
| 271 |
+
<div class="slider-wrap">
|
| 272 |
+
<div class="slider-header">
|
| 273 |
+
<span class="slider-label">💵 How much to invest?</span>
|
| 274 |
+
<span class="slider-val" id="invest-display">$5,000</span>
|
| 275 |
+
</div>
|
| 276 |
+
<input type="range" id="invest-slider" min="500" max="100000" value="5000" step="500">
|
| 277 |
+
<div style="display:flex;justify-content:space-between;font-size:11px;color:var(--text3);margin-top:4px">
|
| 278 |
+
<span>$500</span><span>$100K</span>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
<button class="btn btn-primary" onclick="goStep(2)">Next: Set Risk Profile →</button>
|
| 282 |
+
</div>
|
| 283 |
+
</div>
|
| 284 |
+
|
| 285 |
+
<!-- Step 2: Risk -->
|
| 286 |
+
<div class="step-panel" id="step-2">
|
| 287 |
+
<div class="card fade-in">
|
| 288 |
+
<div class="section-title">🎯 Your Risk Tolerance</div>
|
| 289 |
+
<div class="text-muted text-sm" style="margin-bottom:16px">Drag the slider to find your comfort level</div>
|
| 290 |
+
<div class="risk-slider-wrap">
|
| 291 |
+
<div class="slider-header">
|
| 292 |
+
<span class="slider-label">Risk Level</span>
|
| 293 |
+
<span class="slider-val" id="risk-val-display">Moderate</span>
|
| 294 |
+
</div>
|
| 295 |
+
<input type="range" id="risk-slider" min="1" max="5" value="3" step="1">
|
| 296 |
+
<div class="risk-labels">
|
| 297 |
+
<span>🐢 Very Conservative</span>
|
| 298 |
+
<span>🦁 Very Aggressive</span>
|
| 299 |
+
</div>
|
| 300 |
+
</div>
|
| 301 |
+
<div class="risk-profile-display" id="risk-profile-box">
|
| 302 |
+
<div class="rp-icon">⚖️</div>
|
| 303 |
+
<div class="rp-label">Moderate Investor</div>
|
| 304 |
+
<div class="rp-desc">Balanced mix of growth and stability. You're OK with some market swings for better long-term returns.</div>
|
| 305 |
+
</div>
|
| 306 |
+
<div class="flex gap-12" style="margin-top:20px">
|
| 307 |
+
<button class="btn btn-ghost" onclick="goStep(1)">← Back</button>
|
| 308 |
+
<button class="btn btn-primary" onclick="goStep(3);buildPortfolio()">Generate Portfolio →</button>
|
| 309 |
+
</div>
|
| 310 |
+
</div>
|
| 311 |
+
</div>
|
| 312 |
+
|
| 313 |
+
<!-- Step 3: Build -->
|
| 314 |
+
<div class="step-panel" id="step-3">
|
| 315 |
+
<div class="card fade-in">
|
| 316 |
+
<div class="flex justify-between items-center" style="margin-bottom:16px">
|
| 317 |
+
<div class="section-title" style="margin-bottom:0">🏗️ Your Portfolio</div>
|
| 318 |
+
<div class="flex gap-8">
|
| 319 |
+
<button class="rebal-btn" style="width:auto;padding:8px 14px" onclick="autoRebalance()">⚖️ Auto-rebalance</button>
|
| 320 |
+
</div>
|
| 321 |
+
</div>
|
| 322 |
+
<div id="asset-balance-warning" class="balance-warning ok" style="margin-bottom:14px">
|
| 323 |
+
✅ Portfolio is balanced at 100%
|
| 324 |
+
</div>
|
| 325 |
+
<div id="asset-list"></div>
|
| 326 |
+
<div class="divider"></div>
|
| 327 |
+
<div class="section-title" style="font-size:14px">➕ Add Assets</div>
|
| 328 |
+
<div class="add-asset-grid" id="add-asset-grid"></div>
|
| 329 |
+
<div class="flex gap-12" style="margin-top:20px">
|
| 330 |
+
<button class="btn btn-ghost" onclick="goStep(2)">← Back</button>
|
| 331 |
+
<button class="btn btn-emerald btn-full" onclick="savePortfolio_()">💾 Save Portfolio</button>
|
| 332 |
+
</div>
|
| 333 |
+
</div>
|
| 334 |
+
</div>
|
| 335 |
+
|
| 336 |
+
</div>
|
| 337 |
+
|
| 338 |
+
<!-- Right: Live Preview -->
|
| 339 |
+
<div class="portfolio-summary-side">
|
| 340 |
+
<div class="card fade-in-1">
|
| 341 |
+
<div class="card-title">🔴 Live Preview</div>
|
| 342 |
+
<div class="pie-wrap">
|
| 343 |
+
<canvas id="previewPieChart"></canvas>
|
| 344 |
+
</div>
|
| 345 |
+
<div class="portfolio-total">
|
| 346 |
+
<div class="pt-label">Total Value</div>
|
| 347 |
+
<div class="pt-value" id="preview-total">$5,000</div>
|
| 348 |
+
</div>
|
| 349 |
+
<div id="preview-legend" style="margin-top:8px"></div>
|
| 350 |
+
<div class="divider"></div>
|
| 351 |
+
<div class="card-title">📊 Portfolio Metrics</div>
|
| 352 |
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:8px">
|
| 353 |
+
<div style="background:var(--bg3);border-radius:var(--r-sm);padding:12px;text-align:center">
|
| 354 |
+
<div style="font-size:11px;color:var(--text2);margin-bottom:4px">Risk Score</div>
|
| 355 |
+
<div style="font-family:var(--font-head);font-size:20px;font-weight:800;color:var(--amber)" id="preview-risk">—</div>
|
| 356 |
+
</div>
|
| 357 |
+
<div style="background:var(--bg3);border-radius:var(--r-sm);padding:12px;text-align:center">
|
| 358 |
+
<div style="font-size:11px;color:var(--text2);margin-bottom:4px">Diversification</div>
|
| 359 |
+
<div style="font-family:var(--font-head);font-size:20px;font-weight:800;color:var(--emerald)" id="preview-div">—</div>
|
| 360 |
+
</div>
|
| 361 |
+
</div>
|
| 362 |
+
</div>
|
| 363 |
+
</div>
|
| 364 |
+
</div>
|
| 365 |
+
|
| 366 |
+
</main>
|
| 367 |
+
</div>
|
| 368 |
+
|
| 369 |
+
<!-- Mobile Bottom Nav -->
|
| 370 |
+
<nav class="bottom-nav">
|
| 371 |
+
<div class="bottom-nav-inner">
|
| 372 |
+
<a href="index.html" class="bottom-nav-item"><span class="bnav-icon">🏠</span>Home</a>
|
| 373 |
+
<a href="portfolio.html" class="bottom-nav-item"><span class="bnav-icon">📊</span>Portfolio</a>
|
| 374 |
+
<a href="risk.html" class="bottom-nav-item"><span class="bnav-icon">🎯</span>Risk</a>
|
| 375 |
+
<a href="tracker.html" class="bottom-nav-item"><span class="bnav-icon">📈</span>Track</a>
|
| 376 |
+
<a href="calculators.html" class="bottom-nav-item"><span class="bnav-icon">🧮</span>Calc</a>
|
| 377 |
+
<a href="insights.html" class="bottom-nav-item"><span class="bnav-icon">💡</span>Insights</a>
|
| 378 |
+
</div>
|
| 379 |
+
</nav>
|
| 380 |
+
|
| 381 |
+
<script src="shared.js"></script>
|
| 382 |
+
<script>
|
| 383 |
+
// ── State ─────────────────────────────────────────────────────────
|
| 384 |
+
let portfolio = getPortfolio();
|
| 385 |
+
let investAmount = portfolio.totalInvested || 5000;
|
| 386 |
+
let riskLevel = 3; // 1-5
|
| 387 |
+
let selectedGoals = portfolio.goals || ['Wealth Building'];
|
| 388 |
+
let previewChart = null;
|
| 389 |
+
|
| 390 |
+
const RISK_PROFILES = {
|
| 391 |
+
1: { icon:'🐢', label:'Very Conservative', desc:'Capital preservation is your #1 priority. Heavy bonds & cash equivalents.', template:[30,0,5,0,50,10,5] },
|
| 392 |
+
2: { icon:'🛡️', label:'Conservative', desc:'Modest growth with low risk. Bonds-heavy with some equity exposure.', template:[20,10,10,5,40,10,5] },
|
| 393 |
+
3: { icon:'⚖️', label:'Moderate', desc:'Balanced growth & stability. OK with some swings for better returns.', template:[30,20,15,12,13,7,3] },
|
| 394 |
+
4: { icon:'🚀', label:'Aggressive', desc:'Growth-focused. Higher potential returns, higher volatility accepted.', template:[35,30,20,12,0,3,0] },
|
| 395 |
+
5: { icon:'🦁', label:'Very Aggressive', desc:'Maximum growth. Concentrated tech & equities. High-risk, high-reward.', template:[20,35,30,15,0,0,0] },
|
| 396 |
+
};
|
| 397 |
+
|
| 398 |
+
const ALL_ASSETS = [
|
| 399 |
+
{ ticker:'VOO', name:'Vanguard S&P 500 ETF', price:478.22, color:'#22d3ee', type:'ETF' },
|
| 400 |
+
{ ticker:'VTI', name:'Vanguard Total Market', price:240.30, color:'#0ea5e9', type:'ETF' },
|
| 401 |
+
{ ticker:'QQQ', name:'Invesco Nasdaq 100', price:456.80, color:'#8b5cf6', type:'ETF' },
|
| 402 |
+
{ ticker:'NVDA', name:'NVIDIA Corp', price:875.40, color:'#10b981', type:'Stock' },
|
| 403 |
+
{ ticker:'AAPL', name:'Apple Inc.', price:188.60, color:'#f59e0b', type:'Stock' },
|
| 404 |
+
{ ticker:'AMZN', name:'Amazon.com Inc.', price:188.90, color:'#0891b2', type:'Stock' },
|
| 405 |
+
{ ticker:'TSLA', name:'Tesla Inc.', price:182.30, color:'#f43f5e', type:'Stock' },
|
| 406 |
+
{ ticker:'WMT', name:'Walmart Inc.', price: 67.80, color:'#34d399', type:'Stock' },
|
| 407 |
+
{ ticker:'MCD', name:"McDonald's Corp", price:281.50, color:'#fb923c', type:'Stock' },
|
| 408 |
+
{ ticker:'BND', name:'Vanguard Bond ETF', price: 73.40, color:'#6366f1', type:'Bond' },
|
| 409 |
+
{ ticker:'GLD', name:'SPDR Gold Trust', price:218.10, color:'#fbbf24', type:'Commodity' },
|
| 410 |
+
{ ticker:'SLV', name:'iShares Silver Trust', price: 28.60, color:'#94a3b8', type:'Commodity' },
|
| 411 |
+
];
|
| 412 |
+
|
| 413 |
+
// ── Step Navigation ───────────────────────────────────────────────
|
| 414 |
+
function goStep(n) {
|
| 415 |
+
document.querySelectorAll('.step-panel').forEach(p => p.classList.remove('active'));
|
| 416 |
+
document.querySelectorAll('.step-btn').forEach(b => b.classList.remove('active'));
|
| 417 |
+
document.getElementById('step-' + n).classList.add('active');
|
| 418 |
+
document.querySelector(`.step-btn[data-step="${n}"]`).classList.add('active');
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
document.querySelectorAll('.step-btn').forEach(btn => {
|
| 422 |
+
btn.addEventListener('click', () => {
|
| 423 |
+
const s = parseInt(btn.dataset.step);
|
| 424 |
+
if (s === 3) buildPortfolio();
|
| 425 |
+
goStep(s);
|
| 426 |
+
});
|
| 427 |
+
});
|
| 428 |
+
|
| 429 |
+
// ── Goal Cards ────────────────────────────────────────────────────
|
| 430 |
+
document.querySelectorAll('.goal-card').forEach(card => {
|
| 431 |
+
card.addEventListener('click', () => {
|
| 432 |
+
const goal = card.dataset.goal;
|
| 433 |
+
const idx = selectedGoals.indexOf(goal);
|
| 434 |
+
if (idx >= 0) selectedGoals.splice(idx, 1);
|
| 435 |
+
else selectedGoals.push(goal);
|
| 436 |
+
card.classList.toggle('selected');
|
| 437 |
+
});
|
| 438 |
+
});
|
| 439 |
+
// Pre-select saved goals
|
| 440 |
+
selectedGoals.forEach(g => {
|
| 441 |
+
const c = document.querySelector(`.goal-card[data-goal="${g}"]`);
|
| 442 |
+
if (c) c.classList.add('selected');
|
| 443 |
+
});
|
| 444 |
+
|
| 445 |
+
// ── Investment Slider ─────────────────────────────────────────────
|
| 446 |
+
const investSlider = document.getElementById('invest-slider');
|
| 447 |
+
investSlider.value = investAmount;
|
| 448 |
+
document.getElementById('invest-display').textContent = '$' + investAmount.toLocaleString();
|
| 449 |
+
investSlider.addEventListener('input', () => {
|
| 450 |
+
investAmount = parseInt(investSlider.value);
|
| 451 |
+
document.getElementById('invest-display').textContent = '$' + investAmount.toLocaleString();
|
| 452 |
+
document.getElementById('preview-total').textContent = '$' + investAmount.toLocaleString();
|
| 453 |
+
updatePortfolioAmounts();
|
| 454 |
+
});
|
| 455 |
+
|
| 456 |
+
// ── Risk Slider ───────────────────────────────────────────────────
|
| 457 |
+
const riskSlider = document.getElementById('risk-slider');
|
| 458 |
+
riskSlider.addEventListener('input', () => {
|
| 459 |
+
riskLevel = parseInt(riskSlider.value);
|
| 460 |
+
updateRiskDisplay();
|
| 461 |
+
});
|
| 462 |
+
|
| 463 |
+
function updateRiskDisplay() {
|
| 464 |
+
const profile = RISK_PROFILES[riskLevel];
|
| 465 |
+
const box = document.getElementById('risk-profile-box');
|
| 466 |
+
box.querySelector('.rp-icon').textContent = profile.icon;
|
| 467 |
+
box.querySelector('.rp-label').textContent = profile.label;
|
| 468 |
+
box.querySelector('.rp-desc').textContent = profile.desc;
|
| 469 |
+
document.getElementById('risk-val-display').textContent = profile.label;
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
// ── Build Portfolio from template ─────────────────────────────────
|
| 473 |
+
function buildPortfolio() {
|
| 474 |
+
const template = RISK_PROFILES[riskLevel].template;
|
| 475 |
+
const baseAssets = [
|
| 476 |
+
ALL_ASSETS.find(a=>a.ticker==='VOO'),
|
| 477 |
+
ALL_ASSETS.find(a=>a.ticker==='QQQ'),
|
| 478 |
+
ALL_ASSETS.find(a=>a.ticker==='NVDA'),
|
| 479 |
+
ALL_ASSETS.find(a=>a.ticker==='AAPL'),
|
| 480 |
+
ALL_ASSETS.find(a=>a.ticker==='BND'),
|
| 481 |
+
ALL_ASSETS.find(a=>a.ticker==='GLD'),
|
| 482 |
+
ALL_ASSETS.find(a=>a.ticker==='AMZN'),
|
| 483 |
+
];
|
| 484 |
+
|
| 485 |
+
portfolio.assets = baseAssets.map((a, i) => ({
|
| 486 |
+
...a,
|
| 487 |
+
pct: template[i],
|
| 488 |
+
shares: parseFloat(((investAmount * template[i] / 100) / a.price).toFixed(3))
|
| 489 |
+
})).filter(a => a.pct > 0);
|
| 490 |
+
|
| 491 |
+
portfolio.totalInvested = investAmount;
|
| 492 |
+
portfolio.goals = selectedGoals;
|
| 493 |
+
portfolio.riskProfile = RISK_PROFILES[riskLevel].label;
|
| 494 |
+
|
| 495 |
+
renderAssetList();
|
| 496 |
+
renderAddGrid();
|
| 497 |
+
renderPreviewChart();
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
// ── Render Asset List ─────────────────────────────────────────────
|
| 501 |
+
function renderAssetList() {
|
| 502 |
+
const container = document.getElementById('asset-list');
|
| 503 |
+
container.innerHTML = '';
|
| 504 |
+
|
| 505 |
+
portfolio.assets.forEach((asset, idx) => {
|
| 506 |
+
const dollarVal = (investAmount * asset.pct / 100).toFixed(0);
|
| 507 |
+
const div = document.createElement('div');
|
| 508 |
+
div.className = 'asset-card';
|
| 509 |
+
div.innerHTML = `
|
| 510 |
+
<div class="asset-logo" style="background:${asset.color}">${asset.ticker}</div>
|
| 511 |
+
<div class="asset-info">
|
| 512 |
+
<div class="asset-ticker">${asset.ticker}</div>
|
| 513 |
+
<div class="asset-name">${asset.name}</div>
|
| 514 |
+
<span class="badge badge-${asset.type==='ETF'?'cyan':asset.type==='Bond'?'violet':asset.type==='Commodity'?'amber':'emerald'}">${asset.type}</span>
|
| 515 |
+
</div>
|
| 516 |
+
<div class="asset-sliders">
|
| 517 |
+
<input type="range" min="0" max="80" value="${asset.pct}" step="1"
|
| 518 |
+
oninput="updateAssetPct(${idx}, this.value)"
|
| 519 |
+
style="width:100%;margin-bottom:4px">
|
| 520 |
+
</div>
|
| 521 |
+
<div>
|
| 522 |
+
<div class="asset-pct-val" id="pct-${idx}">${asset.pct}%</div>
|
| 523 |
+
<div class="asset-dollar" id="dollar-${idx}">$${parseInt(dollarVal).toLocaleString()}</div>
|
| 524 |
+
</div>
|
| 525 |
+
<button class="asset-remove" onclick="removeAsset(${idx})">✕</button>
|
| 526 |
+
`;
|
| 527 |
+
container.appendChild(div);
|
| 528 |
+
});
|
| 529 |
+
|
| 530 |
+
updateBalanceWarning();
|
| 531 |
+
updateMetrics();
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
function updateAssetPct(idx, val) {
|
| 535 |
+
portfolio.assets[idx].pct = parseInt(val);
|
| 536 |
+
const dollarVal = (investAmount * parseInt(val) / 100).toFixed(0);
|
| 537 |
+
document.getElementById('pct-' + idx).textContent = val + '%';
|
| 538 |
+
document.getElementById('dollar-' + idx).textContent = '$' + parseInt(dollarVal).toLocaleString();
|
| 539 |
+
updateBalanceWarning();
|
| 540 |
+
renderPreviewChart();
|
| 541 |
+
updateMetrics();
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
+
function updatePortfolioAmounts() {
|
| 545 |
+
portfolio.assets.forEach((a, i) => {
|
| 546 |
+
const dEl = document.getElementById('dollar-' + i);
|
| 547 |
+
if (dEl) dEl.textContent = '$' + parseInt(investAmount * a.pct / 100).toLocaleString();
|
| 548 |
+
});
|
| 549 |
+
renderPreviewChart();
|
| 550 |
+
}
|
| 551 |
+
|
| 552 |
+
function updateBalanceWarning() {
|
| 553 |
+
const total = portfolio.assets.reduce((s, a) => s + a.pct, 0);
|
| 554 |
+
const warn = document.getElementById('asset-balance-warning');
|
| 555 |
+
if (total === 100) {
|
| 556 |
+
warn.className = 'balance-warning ok';
|
| 557 |
+
warn.innerHTML = '✅ Portfolio is balanced at 100%';
|
| 558 |
+
} else if (total < 100) {
|
| 559 |
+
warn.className = 'balance-warning warn';
|
| 560 |
+
warn.innerHTML = `⚠️ Under-allocated: ${total}% used, ${100-total}% remaining`;
|
| 561 |
+
} else {
|
| 562 |
+
warn.className = 'balance-warning err';
|
| 563 |
+
warn.innerHTML = `❌ Over-allocated: ${total}% total (exceeds 100% by ${total-100}%)`;
|
| 564 |
+
}
|
| 565 |
+
}
|
| 566 |
+
|
| 567 |
+
function updateMetrics() {
|
| 568 |
+
document.getElementById('preview-risk').textContent = calcRiskScore(portfolio) + '/100';
|
| 569 |
+
document.getElementById('preview-div').textContent = calcDiversification(portfolio) + '/100';
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
function removeAsset(idx) {
|
| 573 |
+
portfolio.assets.splice(idx, 1);
|
| 574 |
+
renderAssetList();
|
| 575 |
+
renderAddGrid();
|
| 576 |
+
renderPreviewChart();
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
function autoRebalance() {
|
| 580 |
+
const total = portfolio.assets.reduce((s, a) => s + a.pct, 0);
|
| 581 |
+
const perAsset = Math.floor(100 / portfolio.assets.length);
|
| 582 |
+
let rem = 100 - perAsset * portfolio.assets.length;
|
| 583 |
+
portfolio.assets.forEach((a, i) => { a.pct = perAsset + (i === 0 ? rem : 0); });
|
| 584 |
+
renderAssetList();
|
| 585 |
+
renderPreviewChart();
|
| 586 |
+
showToast('Portfolio auto-rebalanced to equal weights!');
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
// ── Add Asset Grid ────────────────────────────────────────────────
|
| 590 |
+
function renderAddGrid() {
|
| 591 |
+
const grid = document.getElementById('add-asset-grid');
|
| 592 |
+
grid.innerHTML = ALL_ASSETS.map(a => {
|
| 593 |
+
const inPortfolio = portfolio.assets.some(pa => pa.ticker === a.ticker);
|
| 594 |
+
return `<button class="add-asset-btn ${inPortfolio?'in-portfolio':''}"
|
| 595 |
+
onclick="toggleAsset('${a.ticker}')" title="${a.name}">
|
| 596 |
+
${inPortfolio ? '✓ ' : '+'} ${a.ticker}
|
| 597 |
+
</button>`;
|
| 598 |
+
}).join('');
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
function toggleAsset(ticker) {
|
| 602 |
+
const exists = portfolio.assets.findIndex(a => a.ticker === ticker);
|
| 603 |
+
if (exists >= 0) {
|
| 604 |
+
removeAsset(exists);
|
| 605 |
+
} else {
|
| 606 |
+
if (portfolio.assets.length >= 10) { showToast('Max 10 assets per portfolio', 'error'); return; }
|
| 607 |
+
const asset = ALL_ASSETS.find(a => a.ticker === ticker);
|
| 608 |
+
portfolio.assets.push({ ...asset, pct: 5, shares: 0 });
|
| 609 |
+
renderAssetList();
|
| 610 |
+
renderPreviewChart();
|
| 611 |
+
renderAddGrid();
|
| 612 |
+
}
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
// ── Preview Pie Chart ─────────────────────────────────────────────
|
| 616 |
+
function renderPreviewChart() {
|
| 617 |
+
const ctx = document.getElementById('previewPieChart').getContext('2d');
|
| 618 |
+
if (previewChart) previewChart.destroy();
|
| 619 |
+
|
| 620 |
+
const validAssets = portfolio.assets.filter(a => a.pct > 0);
|
| 621 |
+
previewChart = new Chart(ctx, {
|
| 622 |
+
type: 'doughnut',
|
| 623 |
+
data: {
|
| 624 |
+
labels: validAssets.map(a => a.ticker),
|
| 625 |
+
datasets: [{
|
| 626 |
+
data: validAssets.map(a => a.pct),
|
| 627 |
+
backgroundColor: validAssets.map(a => a.color),
|
| 628 |
+
borderColor: 'rgba(5,13,26,0.8)',
|
| 629 |
+
borderWidth: 3,
|
| 630 |
+
hoverOffset: 8
|
| 631 |
+
}]
|
| 632 |
+
},
|
| 633 |
+
options: {
|
| 634 |
+
responsive: true, maintainAspectRatio: false,
|
| 635 |
+
cutout: '68%',
|
| 636 |
+
plugins: { legend: { display: false } }
|
| 637 |
+
}
|
| 638 |
+
});
|
| 639 |
+
|
| 640 |
+
const leg = document.getElementById('preview-legend');
|
| 641 |
+
leg.innerHTML = validAssets.map(a => `
|
| 642 |
+
<div class="legend-item">
|
| 643 |
+
<div class="legend-dot" style="background:${a.color}"></div>
|
| 644 |
+
<span class="legend-name">${a.ticker}</span>
|
| 645 |
+
<span class="legend-pct">${a.pct}%</span>
|
| 646 |
+
</div>
|
| 647 |
+
`).join('');
|
| 648 |
+
document.getElementById('preview-total').textContent = '$' + investAmount.toLocaleString();
|
| 649 |
+
}
|
| 650 |
+
|
| 651 |
+
// ── Save Portfolio ────────────────────────────────────────────────
|
| 652 |
+
function savePortfolio_() {
|
| 653 |
+
const total = portfolio.assets.reduce((s,a) => s + a.pct, 0);
|
| 654 |
+
if (total !== 100) { showToast(`Total must be 100% (currently ${total}%)`, 'error'); return; }
|
| 655 |
+
portfolio.assets.forEach(a => {
|
| 656 |
+
a.shares = parseFloat(((investAmount * a.pct / 100) / a.price).toFixed(3));
|
| 657 |
+
});
|
| 658 |
+
savePortfolio(portfolio);
|
| 659 |
+
showToast('✅ Portfolio saved successfully!');
|
| 660 |
+
setTimeout(() => window.location.href = 'index.html', 1500);
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
+
// ── Init ──────────────────────────────────────────────────────────
|
| 664 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 665 |
+
applyChartDefaults();
|
| 666 |
+
renderAddGrid();
|
| 667 |
+
renderAssetList();
|
| 668 |
+
renderPreviewChart();
|
| 669 |
+
updateMetrics();
|
| 670 |
+
});
|
| 671 |
+
</script>
|
| 672 |
+
</body>
|
| 673 |
+
</html>
|
risk.html
ADDED
|
@@ -0,0 +1,618 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>FinWise — Risk Analyzer</title>
|
| 7 |
+
<link rel="stylesheet" href="shared.css">
|
| 8 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
| 9 |
+
<style>
|
| 10 |
+
.quiz-card { margin-bottom: 16px; transition: opacity 0.3s; }
|
| 11 |
+
.quiz-card.inactive { opacity: 0.4; pointer-events: none; }
|
| 12 |
+
.question-num {
|
| 13 |
+
font-size: 11px; font-weight: 700; text-transform: uppercase;
|
| 14 |
+
letter-spacing: 0.1em; color: var(--cyan); margin-bottom: 6px;
|
| 15 |
+
}
|
| 16 |
+
.question-text { font-size: 16px; font-weight: 600; margin-bottom: 14px; line-height: 1.4; }
|
| 17 |
+
.answer-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
| 18 |
+
.answer-btn {
|
| 19 |
+
padding: 12px 14px;
|
| 20 |
+
background: var(--bg3);
|
| 21 |
+
border: 1px solid var(--border);
|
| 22 |
+
border-radius: var(--r-sm);
|
| 23 |
+
color: var(--text2);
|
| 24 |
+
font-family: var(--font-body);
|
| 25 |
+
font-size: 13px;
|
| 26 |
+
font-weight: 500;
|
| 27 |
+
cursor: pointer;
|
| 28 |
+
text-align: left;
|
| 29 |
+
transition: all var(--transition);
|
| 30 |
+
line-height: 1.3;
|
| 31 |
+
}
|
| 32 |
+
.answer-btn:hover { border-color: var(--border2); color: var(--text); background: var(--card2); }
|
| 33 |
+
.answer-btn.selected { border-color: var(--cyan); background: rgba(34,211,238,0.10); color: var(--cyan); }
|
| 34 |
+
|
| 35 |
+
/* Risk Gauge */
|
| 36 |
+
.gauge-container { position: relative; width: 220px; height: 120px; margin: 0 auto; }
|
| 37 |
+
.gauge-svg { width: 220px; height: 120px; }
|
| 38 |
+
.gauge-needle {
|
| 39 |
+
transform-origin: 110px 110px;
|
| 40 |
+
transition: transform 1.2s cubic-bezier(.34,1.56,.64,1);
|
| 41 |
+
}
|
| 42 |
+
.gauge-center-text {
|
| 43 |
+
position: absolute;
|
| 44 |
+
bottom: 0; left: 50%;
|
| 45 |
+
transform: translateX(-50%);
|
| 46 |
+
text-align: center;
|
| 47 |
+
}
|
| 48 |
+
.gauge-score { font-family: var(--font-head); font-size: 32px; font-weight: 800; }
|
| 49 |
+
.gauge-label { font-size: 12px; color: var(--text2); }
|
| 50 |
+
|
| 51 |
+
/* Heatmap */
|
| 52 |
+
.heatmap-grid {
|
| 53 |
+
display: grid;
|
| 54 |
+
gap: 3px;
|
| 55 |
+
}
|
| 56 |
+
.heatmap-cell {
|
| 57 |
+
height: 40px;
|
| 58 |
+
border-radius: 4px;
|
| 59 |
+
display: flex;
|
| 60 |
+
align-items: center;
|
| 61 |
+
justify-content: center;
|
| 62 |
+
font-size: 11px;
|
| 63 |
+
font-weight: 700;
|
| 64 |
+
color: white;
|
| 65 |
+
transition: transform var(--transition);
|
| 66 |
+
cursor: default;
|
| 67 |
+
}
|
| 68 |
+
.heatmap-cell:hover { transform: scale(1.05); }
|
| 69 |
+
|
| 70 |
+
.rebal-item {
|
| 71 |
+
display: flex;
|
| 72 |
+
align-items: center;
|
| 73 |
+
gap: 14px;
|
| 74 |
+
padding: 14px;
|
| 75 |
+
background: var(--bg3);
|
| 76 |
+
border-radius: var(--r-sm);
|
| 77 |
+
border: 1px solid var(--border);
|
| 78 |
+
margin-bottom: 10px;
|
| 79 |
+
transition: all var(--transition);
|
| 80 |
+
}
|
| 81 |
+
.rebal-item:hover { border-color: var(--border2); }
|
| 82 |
+
.rebal-action {
|
| 83 |
+
font-size: 11px;
|
| 84 |
+
font-weight: 700;
|
| 85 |
+
text-transform: uppercase;
|
| 86 |
+
letter-spacing: 0.08em;
|
| 87 |
+
margin-bottom: 3px;
|
| 88 |
+
}
|
| 89 |
+
.rebal-desc { font-size: 13px; color: var(--text2); }
|
| 90 |
+
.rebal-arrow { font-size: 20px; margin-left: auto; }
|
| 91 |
+
|
| 92 |
+
.risk-breakdown { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
| 93 |
+
.rb-item {
|
| 94 |
+
background: var(--bg3);
|
| 95 |
+
border-radius: var(--r-sm);
|
| 96 |
+
padding: 14px;
|
| 97 |
+
text-align: center;
|
| 98 |
+
}
|
| 99 |
+
.rb-val { font-family: var(--font-head); font-size: 22px; font-weight: 800; }
|
| 100 |
+
.rb-label { font-size: 11px; color: var(--text2); margin-top: 4px; text-transform: uppercase; letter-spacing: 0.06em; }
|
| 101 |
+
|
| 102 |
+
.volatility-bar-wrap { margin-bottom: 12px; }
|
| 103 |
+
.vol-header { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 5px; }
|
| 104 |
+
.vol-name { color: var(--text2); font-weight: 600; }
|
| 105 |
+
|
| 106 |
+
.score-ring {
|
| 107 |
+
width: 80px; height: 80px;
|
| 108 |
+
border-radius: 50%;
|
| 109 |
+
display: flex; align-items: center; justify-content: center;
|
| 110 |
+
font-family: var(--font-head);
|
| 111 |
+
font-size: 24px;
|
| 112 |
+
font-weight: 800;
|
| 113 |
+
margin: 0 auto 12px;
|
| 114 |
+
position: relative;
|
| 115 |
+
}
|
| 116 |
+
.score-ring::before {
|
| 117 |
+
content: '';
|
| 118 |
+
position: absolute;
|
| 119 |
+
inset: 4px;
|
| 120 |
+
border-radius: 50%;
|
| 121 |
+
background: var(--bg2);
|
| 122 |
+
}
|
| 123 |
+
.score-ring span { position: relative; z-index: 1; }
|
| 124 |
+
|
| 125 |
+
.scenario-card {
|
| 126 |
+
background: var(--bg3);
|
| 127 |
+
border-radius: var(--r-sm);
|
| 128 |
+
padding: 16px;
|
| 129 |
+
border: 1px solid var(--border);
|
| 130 |
+
transition: all var(--transition);
|
| 131 |
+
}
|
| 132 |
+
.scenario-card:hover { border-color: var(--border2); }
|
| 133 |
+
.scenario-icon { font-size: 28px; margin-bottom: 8px; }
|
| 134 |
+
.scenario-title { font-weight: 700; font-size: 14px; margin-bottom: 6px; }
|
| 135 |
+
.scenario-impact { font-size: 13px; }
|
| 136 |
+
</style>
|
| 137 |
+
</head>
|
| 138 |
+
<body>
|
| 139 |
+
<div class="app-shell">
|
| 140 |
+
<nav class="sidebar">
|
| 141 |
+
<div class="sidebar-logo">
|
| 142 |
+
<div class="logo-mark">
|
| 143 |
+
<div class="logo-icon">📈</div>
|
| 144 |
+
<div><div class="logo-text">FinWise</div><div class="logo-sub">Smart Investing</div></div>
|
| 145 |
+
</div>
|
| 146 |
+
</div>
|
| 147 |
+
<div class="nav-section">
|
| 148 |
+
<div class="nav-label">Main</div>
|
| 149 |
+
<a href="index.html" class="nav-item"><span class="nav-icon">🏠</span> Dashboard</a>
|
| 150 |
+
<a href="portfolio.html" class="nav-item"><span class="nav-icon">📊</span> Portfolio Builder</a>
|
| 151 |
+
<a href="risk.html" class="nav-item"><span class="nav-icon">🎯</span> Risk Analyzer</a>
|
| 152 |
+
<a href="tracker.html" class="nav-item"><span class="nav-icon">📈</span> Tracker</a>
|
| 153 |
+
<div class="nav-label">Tools</div>
|
| 154 |
+
<a href="calculators.html" class="nav-item"><span class="nav-icon">🧮</span> Calculators</a>
|
| 155 |
+
<a href="insights.html" class="nav-item"><span class="nav-icon">💡</span> Insights</a>
|
| 156 |
+
</div>
|
| 157 |
+
<div class="sidebar-footer">
|
| 158 |
+
<div class="market-ticker">Live Market</div>
|
| 159 |
+
<div id="sidebar-tickers"></div>
|
| 160 |
+
</div>
|
| 161 |
+
</nav>
|
| 162 |
+
|
| 163 |
+
<main class="main-content">
|
| 164 |
+
<div class="page-header fade-in">
|
| 165 |
+
<div class="page-title">Risk <span>Analyzer</span></div>
|
| 166 |
+
<div class="page-subtitle">Understand and optimize your portfolio's risk profile</div>
|
| 167 |
+
</div>
|
| 168 |
+
|
| 169 |
+
<div class="grid-60-40">
|
| 170 |
+
<!-- Left Column -->
|
| 171 |
+
<div>
|
| 172 |
+
|
| 173 |
+
<!-- Quiz -->
|
| 174 |
+
<div class="card fade-in" id="quiz-section">
|
| 175 |
+
<div class="section-title">🧠 Risk Tolerance Assessment</div>
|
| 176 |
+
<div class="text-muted text-sm" style="margin-bottom:20px">Answer 5 quick questions — no typing needed</div>
|
| 177 |
+
|
| 178 |
+
<div id="quiz-container"></div>
|
| 179 |
+
|
| 180 |
+
<div id="quiz-result" class="hidden" style="margin-top:16px">
|
| 181 |
+
<div style="text-align:center;padding:20px">
|
| 182 |
+
<div style="font-size:48px;margin-bottom:8px" id="result-icon">⚖️</div>
|
| 183 |
+
<div style="font-family:var(--font-head);font-size:24px;font-weight:800;margin-bottom:8px" id="result-title">Moderate Investor</div>
|
| 184 |
+
<div style="color:var(--text2);font-size:14px;max-width:360px;margin:0 auto" id="result-desc"></div>
|
| 185 |
+
</div>
|
| 186 |
+
</div>
|
| 187 |
+
|
| 188 |
+
<div class="flex gap-12" style="margin-top:16px">
|
| 189 |
+
<button class="btn btn-ghost btn-sm" onclick="resetQuiz()">↺ Retake</button>
|
| 190 |
+
<button class="btn btn-primary" id="analyze-btn" onclick="runAnalysis()">Analyze My Risk →</button>
|
| 191 |
+
</div>
|
| 192 |
+
</div>
|
| 193 |
+
|
| 194 |
+
<!-- Volatility Breakdown -->
|
| 195 |
+
<div class="card fade-in fade-in-2" style="margin-top:20px">
|
| 196 |
+
<div class="section-title">📊 Asset Volatility</div>
|
| 197 |
+
<div id="volatility-bars"></div>
|
| 198 |
+
</div>
|
| 199 |
+
|
| 200 |
+
<!-- Scenarios -->
|
| 201 |
+
<div class="card fade-in fade-in-3" style="margin-top:20px">
|
| 202 |
+
<div class="section-title">🌪️ Scenario Simulation</div>
|
| 203 |
+
<div class="text-muted text-sm" style="margin-bottom:16px">How would your portfolio perform in these scenarios?</div>
|
| 204 |
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px" id="scenarios-grid"></div>
|
| 205 |
+
</div>
|
| 206 |
+
|
| 207 |
+
</div>
|
| 208 |
+
|
| 209 |
+
<!-- Right Column -->
|
| 210 |
+
<div>
|
| 211 |
+
|
| 212 |
+
<!-- Risk Gauge -->
|
| 213 |
+
<div class="card fade-in-1">
|
| 214 |
+
<div class="card-title">🎯 Risk Score</div>
|
| 215 |
+
<div class="gauge-container">
|
| 216 |
+
<svg class="gauge-svg" viewBox="0 0 220 120">
|
| 217 |
+
<!-- Background arcs -->
|
| 218 |
+
<path d="M 20 110 A 90 90 0 0 1 200 110" fill="none" stroke="var(--bg3)" stroke-width="18" stroke-linecap="round"/>
|
| 219 |
+
<!-- Colored sections -->
|
| 220 |
+
<path d="M 20 110 A 90 90 0 0 1 65 29" fill="none" stroke="#10b981" stroke-width="18" stroke-linecap="round" opacity="0.8"/>
|
| 221 |
+
<path d="M 65 29 A 90 90 0 0 1 110 20" fill="none" stroke="#22d3ee" stroke-width="18" opacity="0.8"/>
|
| 222 |
+
<path d="M 110 20 A 90 90 0 0 1 155 29" fill="none" stroke="#f59e0b" stroke-width="18" opacity="0.8"/>
|
| 223 |
+
<path d="M 155 29 A 90 90 0 0 1 200 110" fill="none" stroke="#f43f5e" stroke-width="18" stroke-linecap="round" opacity="0.8"/>
|
| 224 |
+
<!-- Needle -->
|
| 225 |
+
<g class="gauge-needle" id="gauge-needle" style="transform:rotate(-90deg)">
|
| 226 |
+
<line x1="110" y1="110" x2="110" y2="30" stroke="white" stroke-width="2.5" stroke-linecap="round"/>
|
| 227 |
+
<circle cx="110" cy="110" r="6" fill="white"/>
|
| 228 |
+
</g>
|
| 229 |
+
</svg>
|
| 230 |
+
<div class="gauge-center-text">
|
| 231 |
+
<div class="gauge-score" id="gauge-score">—</div>
|
| 232 |
+
<div class="gauge-label">Risk Score</div>
|
| 233 |
+
</div>
|
| 234 |
+
</div>
|
| 235 |
+
<div style="text-align:center;margin-top:16px">
|
| 236 |
+
<span class="badge" id="gauge-badge" style="font-size:13px;padding:6px 16px">Calculating...</span>
|
| 237 |
+
</div>
|
| 238 |
+
|
| 239 |
+
<div class="divider"></div>
|
| 240 |
+
<div class="risk-breakdown">
|
| 241 |
+
<div class="rb-item">
|
| 242 |
+
<div class="rb-val" id="rb-div" style="color:var(--emerald)">—</div>
|
| 243 |
+
<div class="rb-label">Diversification</div>
|
| 244 |
+
</div>
|
| 245 |
+
<div class="rb-item">
|
| 246 |
+
<div class="rb-val" id="rb-concentrate" style="color:var(--amber)">—</div>
|
| 247 |
+
<div class="rb-label">Concentration</div>
|
| 248 |
+
</div>
|
| 249 |
+
<div class="rb-item">
|
| 250 |
+
<div class="rb-val" id="rb-equity" style="color:var(--violet)">—</div>
|
| 251 |
+
<div class="rb-label">Equity %</div>
|
| 252 |
+
</div>
|
| 253 |
+
<div class="rb-item">
|
| 254 |
+
<div class="rb-val" id="rb-safe" style="color:var(--cyan)">—</div>
|
| 255 |
+
<div class="rb-label">Safe Assets</div>
|
| 256 |
+
</div>
|
| 257 |
+
</div>
|
| 258 |
+
</div>
|
| 259 |
+
|
| 260 |
+
<!-- Rebalancing Suggestions -->
|
| 261 |
+
<div class="card fade-in-2" style="margin-top:20px">
|
| 262 |
+
<div class="card-title">⚖️ Rebalancing Suggestions</div>
|
| 263 |
+
<div id="rebal-list">
|
| 264 |
+
<div style="color:var(--text2);font-size:13px;text-align:center;padding:20px">
|
| 265 |
+
Complete the risk assessment to see suggestions
|
| 266 |
+
</div>
|
| 267 |
+
</div>
|
| 268 |
+
</div>
|
| 269 |
+
|
| 270 |
+
<!-- Allocation Type Chart -->
|
| 271 |
+
<div class="card fade-in-3" style="margin-top:20px">
|
| 272 |
+
<div class="card-title">🏦 Asset Type Breakdown</div>
|
| 273 |
+
<div style="height:180px;position:relative">
|
| 274 |
+
<canvas id="typeChart"></canvas>
|
| 275 |
+
</div>
|
| 276 |
+
</div>
|
| 277 |
+
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
|
| 281 |
+
</main>
|
| 282 |
+
</div>
|
| 283 |
+
|
| 284 |
+
<nav class="bottom-nav">
|
| 285 |
+
<div class="bottom-nav-inner">
|
| 286 |
+
<a href="index.html" class="bottom-nav-item"><span class="bnav-icon">🏠</span>Home</a>
|
| 287 |
+
<a href="portfolio.html" class="bottom-nav-item"><span class="bnav-icon">📊</span>Portfolio</a>
|
| 288 |
+
<a href="risk.html" class="bottom-nav-item"><span class="bnav-icon">🎯</span>Risk</a>
|
| 289 |
+
<a href="tracker.html" class="bottom-nav-item"><span class="bnav-icon">📈</span>Track</a>
|
| 290 |
+
<a href="calculators.html" class="bottom-nav-item"><span class="bnav-icon">🧮</span>Calc</a>
|
| 291 |
+
<a href="insights.html" class="bottom-nav-item"><span class="bnav-icon">💡</span>Insights</a>
|
| 292 |
+
</div>
|
| 293 |
+
</nav>
|
| 294 |
+
|
| 295 |
+
<script src="shared.js"></script>
|
| 296 |
+
<script>
|
| 297 |
+
const QUESTIONS = [
|
| 298 |
+
{
|
| 299 |
+
q: "How long do you plan to keep your investments?",
|
| 300 |
+
answers: [
|
| 301 |
+
{ text: "⏱️ Less than 1 year", score: 1 },
|
| 302 |
+
{ text: "📅 1–3 years", score: 2 },
|
| 303 |
+
{ text: "📆 3–10 years", score: 4 },
|
| 304 |
+
{ text: "🗓️ 10+ years", score: 5 },
|
| 305 |
+
]
|
| 306 |
+
},
|
| 307 |
+
{
|
| 308 |
+
q: "If your portfolio dropped 20% suddenly, you would:",
|
| 309 |
+
answers: [
|
| 310 |
+
{ text: "😱 Sell everything immediately", score: 1 },
|
| 311 |
+
{ text: "😟 Sell some to reduce exposure", score: 2 },
|
| 312 |
+
{ text: "😐 Hold and wait for recovery", score: 4 },
|
| 313 |
+
{ text: "😏 Buy more at lower prices", score: 5 },
|
| 314 |
+
]
|
| 315 |
+
},
|
| 316 |
+
{
|
| 317 |
+
q: "What's your primary investment objective?",
|
| 318 |
+
answers: [
|
| 319 |
+
{ text: "🛡️ Preserve what I have", score: 1 },
|
| 320 |
+
{ text: "🌱 Steady, modest growth", score: 2 },
|
| 321 |
+
{ text: "⚖️ Balance growth and safety", score: 3 },
|
| 322 |
+
{ text: "🚀 Maximum long-term growth", score: 5 },
|
| 323 |
+
]
|
| 324 |
+
},
|
| 325 |
+
{
|
| 326 |
+
q: "What % of your monthly income do you invest?",
|
| 327 |
+
answers: [
|
| 328 |
+
{ text: "💸 Less than 5%", score: 1 },
|
| 329 |
+
{ text: "💰 5–10%", score: 2 },
|
| 330 |
+
{ text: "📈 10–25%", score: 4 },
|
| 331 |
+
{ text: "🏦 More than 25%", score: 5 },
|
| 332 |
+
]
|
| 333 |
+
},
|
| 334 |
+
{
|
| 335 |
+
q: "Which describes your investing experience?",
|
| 336 |
+
answers: [
|
| 337 |
+
{ text: "🐣 Complete beginner", score: 1 },
|
| 338 |
+
{ text: "📖 Read a bit about it", score: 2 },
|
| 339 |
+
{ text: "🤓 Have some investments", score: 3 },
|
| 340 |
+
{ text: "💹 Active investor / trader", score: 5 },
|
| 341 |
+
]
|
| 342 |
+
},
|
| 343 |
+
];
|
| 344 |
+
|
| 345 |
+
let answers = new Array(QUESTIONS.length).fill(null);
|
| 346 |
+
let quizComplete = false;
|
| 347 |
+
|
| 348 |
+
function renderQuiz() {
|
| 349 |
+
const container = document.getElementById('quiz-container');
|
| 350 |
+
container.innerHTML = QUESTIONS.map((q, qi) => `
|
| 351 |
+
<div class="card quiz-card ${answers[qi] === null && qi > 0 && answers[qi-1] === null ? 'inactive' : ''}"
|
| 352 |
+
id="quiz-card-${qi}" style="padding:16px;margin-bottom:12px;background:var(--bg3);border:1px solid var(--border)">
|
| 353 |
+
<div class="question-num">Question ${qi+1} of ${QUESTIONS.length}</div>
|
| 354 |
+
<div class="question-text">${q.q}</div>
|
| 355 |
+
<div class="answer-grid">
|
| 356 |
+
${q.answers.map((a, ai) => `
|
| 357 |
+
<button class="answer-btn ${answers[qi] === ai ? 'selected' : ''}"
|
| 358 |
+
onclick="selectAnswer(${qi}, ${ai})">${a.text}</button>
|
| 359 |
+
`).join('')}
|
| 360 |
+
</div>
|
| 361 |
+
</div>
|
| 362 |
+
`).join('');
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
function selectAnswer(qi, ai) {
|
| 366 |
+
answers[qi] = ai;
|
| 367 |
+
document.querySelectorAll(`#quiz-card-${qi} .answer-btn`).forEach((btn, i) => {
|
| 368 |
+
btn.classList.toggle('selected', i === ai);
|
| 369 |
+
});
|
| 370 |
+
const next = document.getElementById('quiz-card-' + (qi+1));
|
| 371 |
+
if (next) next.classList.remove('inactive');
|
| 372 |
+
|
| 373 |
+
quizComplete = answers.every(a => a !== null);
|
| 374 |
+
if (quizComplete) {
|
| 375 |
+
document.getElementById('analyze-btn').style.background = 'linear-gradient(135deg,var(--emerald-d),var(--emerald))';
|
| 376 |
+
}
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
function resetQuiz() {
|
| 380 |
+
answers.fill(null);
|
| 381 |
+
quizComplete = false;
|
| 382 |
+
renderQuiz();
|
| 383 |
+
document.getElementById('quiz-result').classList.add('hidden');
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
function getTotalScore() {
|
| 387 |
+
return answers.reduce((s, ai, qi) => s + (ai !== null ? QUESTIONS[qi].answers[ai].score : 0), 0);
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
const RISK_RESULTS = [
|
| 391 |
+
null,
|
| 392 |
+
{ icon:'🐢', title:'Very Conservative', desc:'You prefer capital safety above all. Bonds and stable assets suit you best. Low risk, low return.', color:'var(--emerald)' },
|
| 393 |
+
{ icon:'🛡️', title:'Conservative', desc:'Modest growth with downside protection. A mix of bonds and blue-chip stocks works for you.', color:'var(--cyan)' },
|
| 394 |
+
{ icon:'⚖️', title:'Moderate', desc:'Balanced approach. You accept some volatility for reasonable long-term gains.', color:'var(--amber)' },
|
| 395 |
+
{ icon:'🚀', title:'Aggressive', desc:'Growth-focused investor. You can stomach volatility and aim for superior long-term returns.', color:'var(--rose)' },
|
| 396 |
+
{ icon:'🦁', title:'Very Aggressive', desc:'Maximum growth seeker. High risk, high reward. Concentrated in equities and high-growth assets.', color:'var(--rose)' },
|
| 397 |
+
];
|
| 398 |
+
|
| 399 |
+
function getProfileFromScore(score) {
|
| 400 |
+
if (score <= 8) return RISK_RESULTS[1];
|
| 401 |
+
if (score <= 12) return RISK_RESULTS[2];
|
| 402 |
+
if (score <= 16) return RISK_RESULTS[3];
|
| 403 |
+
if (score <= 20) return RISK_RESULTS[4];
|
| 404 |
+
return RISK_RESULTS[5];
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
function runAnalysis() {
|
| 408 |
+
if (!quizComplete) { showToast('Please answer all 5 questions first', 'error'); return; }
|
| 409 |
+
|
| 410 |
+
const score = getTotalScore();
|
| 411 |
+
const profile = getProfileFromScore(score);
|
| 412 |
+
document.getElementById('quiz-result').classList.remove('hidden');
|
| 413 |
+
document.getElementById('result-icon').textContent = profile.icon;
|
| 414 |
+
document.getElementById('result-title').textContent = profile.title;
|
| 415 |
+
document.getElementById('result-desc').textContent = profile.desc;
|
| 416 |
+
|
| 417 |
+
const portfolio = getPortfolio();
|
| 418 |
+
const riskScore = calcRiskScore(portfolio);
|
| 419 |
+
const divScore = calcDiversification(portfolio);
|
| 420 |
+
|
| 421 |
+
// Gauge
|
| 422 |
+
const deg = -90 + (riskScore / 100) * 180;
|
| 423 |
+
document.getElementById('gauge-needle').style.transform = `rotate(${deg}deg)`;
|
| 424 |
+
document.getElementById('gauge-score').textContent = riskScore;
|
| 425 |
+
|
| 426 |
+
const riskColors = ['badge-emerald','badge-cyan','badge-amber','badge-amber','badge-rose'];
|
| 427 |
+
const riskNames = ['Low Risk','Moderate-Low','Moderate','Moderate-High','High Risk'];
|
| 428 |
+
const rIdx = Math.floor(riskScore / 25);
|
| 429 |
+
const badge = document.getElementById('gauge-badge');
|
| 430 |
+
badge.className = 'badge ' + riskColors[rIdx];
|
| 431 |
+
badge.textContent = riskNames[rIdx];
|
| 432 |
+
|
| 433 |
+
// Breakdown
|
| 434 |
+
const equityPct = portfolio.assets.filter(a=>a.type==='Stock'||a.type==='ETF').reduce((s,a)=>s+a.pct,0);
|
| 435 |
+
const safePct = portfolio.assets.filter(a=>a.type==='Bond').reduce((s,a)=>s+a.pct,0);
|
| 436 |
+
const maxPct = Math.max(...portfolio.assets.map(a=>a.pct));
|
| 437 |
+
document.getElementById('rb-div').textContent = divScore + '%';
|
| 438 |
+
document.getElementById('rb-concentrate').textContent = maxPct + '%';
|
| 439 |
+
document.getElementById('rb-equity').textContent = equityPct + '%';
|
| 440 |
+
document.getElementById('rb-safe').textContent = safePct + '%';
|
| 441 |
+
|
| 442 |
+
renderRebalSuggestions(portfolio, riskScore, equityPct);
|
| 443 |
+
renderVolatility(portfolio);
|
| 444 |
+
renderScenarios(portfolio);
|
| 445 |
+
renderTypeChart(portfolio);
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
function renderRebalSuggestions(portfolio, riskScore, equityPct) {
|
| 449 |
+
const list = document.getElementById('rebal-list');
|
| 450 |
+
const suggestions = [];
|
| 451 |
+
|
| 452 |
+
const maxAsset = portfolio.assets.reduce((m, a) => a.pct > m.pct ? a : m, portfolio.assets[0]);
|
| 453 |
+
if (maxAsset.pct > 35) suggestions.push({
|
| 454 |
+
type:'REDUCE', color:'var(--rose)',
|
| 455 |
+
action: `Reduce ${maxAsset.ticker}`,
|
| 456 |
+
desc: `${maxAsset.ticker} is ${maxAsset.pct}% of your portfolio — consider trimming to under 30% for better balance.`,
|
| 457 |
+
arrow: '↓'
|
| 458 |
+
});
|
| 459 |
+
|
| 460 |
+
const bondPct = portfolio.assets.filter(a=>a.type==='Bond').reduce((s,a)=>s+a.pct,0);
|
| 461 |
+
if (bondPct < 10 && riskScore < 60) suggestions.push({
|
| 462 |
+
type:'ADD', color:'var(--emerald)',
|
| 463 |
+
action: 'Add Bonds (BND)',
|
| 464 |
+
desc: 'Your portfolio has little fixed income. Adding 10-15% bonds reduces volatility significantly.',
|
| 465 |
+
arrow: '+'
|
| 466 |
+
});
|
| 467 |
+
|
| 468 |
+
if (equityPct > 85) suggestions.push({
|
| 469 |
+
type:'DIVERSIFY', color:'var(--amber)',
|
| 470 |
+
action: 'Add Alternative Assets',
|
| 471 |
+
desc: 'Over 85% in equities. Consider Gold (GLD) or REITs for non-correlated returns.',
|
| 472 |
+
arrow: '↗'
|
| 473 |
+
});
|
| 474 |
+
|
| 475 |
+
const hasIntl = portfolio.assets.some(a=>a.ticker==='VEA'||a.ticker==='VXUS');
|
| 476 |
+
if (!hasIntl) suggestions.push({
|
| 477 |
+
type:'CONSIDER', color:'var(--cyan)',
|
| 478 |
+
action: 'Consider International Exposure',
|
| 479 |
+
desc: 'Adding 10-15% international ETFs (VEA, VXUS) can reduce US-market concentration risk.',
|
| 480 |
+
arrow: '🌍'
|
| 481 |
+
});
|
| 482 |
+
|
| 483 |
+
if (suggestions.length === 0) suggestions.push({
|
| 484 |
+
type:'OK', color:'var(--emerald)',
|
| 485 |
+
action: '✅ Portfolio looks healthy!',
|
| 486 |
+
desc: 'Your diversification and risk levels are well-balanced. Keep up with regular contributions.',
|
| 487 |
+
arrow: '👍'
|
| 488 |
+
});
|
| 489 |
+
|
| 490 |
+
list.innerHTML = suggestions.map(s => `
|
| 491 |
+
<div class="rebal-item">
|
| 492 |
+
<div>
|
| 493 |
+
<div class="rebal-action" style="color:${s.color}">${s.type}</div>
|
| 494 |
+
<div style="font-weight:700;font-size:14px;margin-bottom:4px">${s.action}</div>
|
| 495 |
+
<div class="rebal-desc">${s.desc}</div>
|
| 496 |
+
</div>
|
| 497 |
+
<div class="rebal-arrow">${s.arrow}</div>
|
| 498 |
+
</div>
|
| 499 |
+
`).join('');
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
const VOLATILITY_DATA = {
|
| 503 |
+
'VOO': { vol: 15, beta: 1.00, color: '#22d3ee' },
|
| 504 |
+
'QQQ': { vol: 22, beta: 1.18, color: '#8b5cf6' },
|
| 505 |
+
'NVDA': { vol: 48, beta: 1.85, color: '#10b981' },
|
| 506 |
+
'AAPL': { vol: 25, beta: 1.22, color: '#f59e0b' },
|
| 507 |
+
'BND': { vol: 4, beta: 0.10, color: '#6366f1' },
|
| 508 |
+
'GLD': { vol: 12, beta: 0.05, color: '#fbbf24' },
|
| 509 |
+
'AMZN': { vol: 30, beta: 1.35, color: '#0ea5e9' },
|
| 510 |
+
'VTI': { vol: 15, beta: 1.00, color: '#34d399' },
|
| 511 |
+
'TSLA': { vol: 62, beta: 2.10, color: '#f43f5e' },
|
| 512 |
+
'WMT': { vol: 8, beta: 0.55, color: '#34d399' },
|
| 513 |
+
'MCD': { vol: 9, beta: 0.70, color: '#fb923c' },
|
| 514 |
+
};
|
| 515 |
+
|
| 516 |
+
function renderVolatility(portfolio) {
|
| 517 |
+
const container = document.getElementById('volatility-bars');
|
| 518 |
+
container.innerHTML = portfolio.assets.map(a => {
|
| 519 |
+
const vdata = VOLATILITY_DATA[a.ticker] || { vol: 20, beta: 1.0, color: '#22d3ee' };
|
| 520 |
+
const volColor = vdata.vol < 10 ? '#10b981' : vdata.vol < 25 ? '#f59e0b' : '#f43f5e';
|
| 521 |
+
return `
|
| 522 |
+
<div class="volatility-bar-wrap">
|
| 523 |
+
<div class="vol-header">
|
| 524 |
+
<span class="vol-name">${a.ticker} <span style="color:var(--text3);font-weight:400;font-size:11px">— ${a.name || ''}</span></span>
|
| 525 |
+
<span style="color:${volColor};font-family:var(--font-mono);font-size:12px">±${vdata.vol}% vol · β ${vdata.beta}</span>
|
| 526 |
+
</div>
|
| 527 |
+
<div class="progress-bar">
|
| 528 |
+
<div class="progress-fill" style="width:${Math.min(vdata.vol*1.2,100)}%;background:${volColor}"></div>
|
| 529 |
+
</div>
|
| 530 |
+
</div>
|
| 531 |
+
`;
|
| 532 |
+
}).join('');
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
function renderScenarios(portfolio) {
|
| 536 |
+
const equity = portfolio.assets.filter(a=>a.type==='Stock'||a.type==='ETF').reduce((s,a)=>s+a.pct,0)/100;
|
| 537 |
+
const bond = portfolio.assets.filter(a=>a.type==='Bond').reduce((s,a)=>s+a.pct,0)/100;
|
| 538 |
+
const gold = portfolio.assets.filter(a=>a.type==='Commodity').reduce((s,a)=>s+a.pct,0)/100;
|
| 539 |
+
|
| 540 |
+
const scenarios = [
|
| 541 |
+
{ icon:'📉', title:'2008 Financial Crisis', impact: ((-38*equity) + (5*bond) + (8*gold)).toFixed(1), desc:'Worst-case equity selloff' },
|
| 542 |
+
{ icon:'🦠', title:'2020 COVID Crash', impact: ((-30*equity) + (8*bond) + (5*gold)).toFixed(1), desc:'Fast drop, rapid recovery' },
|
| 543 |
+
{ icon:'🔥', title:'Inflation Spike (10%)', impact: ((-15*equity) + (-8*bond) + (20*gold)).toFixed(1), desc:'Rising rates environment' },
|
| 544 |
+
{ icon:'🚀', title:'Bull Market (+30%)', impact: ((30*equity) + (3*bond) + (8*gold)).toFixed(1), desc:'Strong economic growth' },
|
| 545 |
+
];
|
| 546 |
+
|
| 547 |
+
document.getElementById('scenarios-grid').innerHTML = scenarios.map(s => {
|
| 548 |
+
const isPos = parseFloat(s.impact) >= 0;
|
| 549 |
+
return `
|
| 550 |
+
<div class="scenario-card">
|
| 551 |
+
<div class="scenario-icon">${s.icon}</div>
|
| 552 |
+
<div class="scenario-title">${s.title}</div>
|
| 553 |
+
<div class="scenario-impact" style="color:${isPos?'var(--emerald)':'var(--rose)'};font-family:var(--font-mono);font-size:18px;font-weight:700">
|
| 554 |
+
${isPos?'+':''}${s.impact}%
|
| 555 |
+
</div>
|
| 556 |
+
<div style="font-size:11px;color:var(--text2);margin-top:4px">${s.desc}</div>
|
| 557 |
+
</div>
|
| 558 |
+
`;
|
| 559 |
+
}).join('');
|
| 560 |
+
}
|
| 561 |
+
|
| 562 |
+
function renderTypeChart(portfolio) {
|
| 563 |
+
const types = { ETF:0, Stock:0, Bond:0, Commodity:0 };
|
| 564 |
+
portfolio.assets.forEach(a => { types[a.type] = (types[a.type]||0) + a.pct; });
|
| 565 |
+
|
| 566 |
+
const ctx = document.getElementById('typeChart').getContext('2d');
|
| 567 |
+
new Chart(ctx, {
|
| 568 |
+
type: 'bar',
|
| 569 |
+
data: {
|
| 570 |
+
labels: Object.keys(types),
|
| 571 |
+
datasets: [{
|
| 572 |
+
data: Object.values(types),
|
| 573 |
+
backgroundColor: ['rgba(34,211,238,0.7)','rgba(16,185,129,0.7)','rgba(99,102,241,0.7)','rgba(245,158,11,0.7)'],
|
| 574 |
+
borderRadius: 6,
|
| 575 |
+
borderSkipped: false,
|
| 576 |
+
}]
|
| 577 |
+
},
|
| 578 |
+
options: {
|
| 579 |
+
responsive: true, maintainAspectRatio: false,
|
| 580 |
+
plugins: { legend: { display: false } },
|
| 581 |
+
scales: {
|
| 582 |
+
x: { grid: { display: false } },
|
| 583 |
+
y: { grid: { color: 'rgba(34,211,238,0.06)' }, ticks: { callback: v => v + '%' } }
|
| 584 |
+
}
|
| 585 |
+
}
|
| 586 |
+
});
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 590 |
+
applyChartDefaults();
|
| 591 |
+
renderQuiz();
|
| 592 |
+
// Auto-run analysis from saved portfolio
|
| 593 |
+
const portfolio = getPortfolio();
|
| 594 |
+
const riskScore = calcRiskScore(portfolio);
|
| 595 |
+
const deg = -90 + (riskScore/100)*180;
|
| 596 |
+
setTimeout(() => {
|
| 597 |
+
document.getElementById('gauge-needle').style.transform = `rotate(${deg}deg)`;
|
| 598 |
+
document.getElementById('gauge-score').textContent = riskScore;
|
| 599 |
+
const rIdx = Math.floor(riskScore/25);
|
| 600 |
+
const badge = document.getElementById('gauge-badge');
|
| 601 |
+
badge.className = 'badge ' + ['badge-emerald','badge-cyan','badge-amber','badge-amber','badge-rose'][rIdx];
|
| 602 |
+
badge.textContent = ['Low Risk','Moderate-Low','Moderate','Moderate-High','High Risk'][rIdx];
|
| 603 |
+
const equityPct = portfolio.assets.filter(a=>a.type==='Stock'||a.type==='ETF').reduce((s,a)=>s+a.pct,0);
|
| 604 |
+
const safePct = portfolio.assets.filter(a=>a.type==='Bond').reduce((s,a)=>s+a.pct,0);
|
| 605 |
+
const maxPct = Math.max(...portfolio.assets.map(a=>a.pct));
|
| 606 |
+
document.getElementById('rb-div').textContent = calcDiversification(portfolio) + '%';
|
| 607 |
+
document.getElementById('rb-concentrate').textContent = maxPct + '%';
|
| 608 |
+
document.getElementById('rb-equity').textContent = equityPct + '%';
|
| 609 |
+
document.getElementById('rb-safe').textContent = safePct + '%';
|
| 610 |
+
renderRebalSuggestions(portfolio, riskScore, equityPct);
|
| 611 |
+
renderVolatility(portfolio);
|
| 612 |
+
renderScenarios(portfolio);
|
| 613 |
+
renderTypeChart(portfolio);
|
| 614 |
+
}, 400);
|
| 615 |
+
});
|
| 616 |
+
</script>
|
| 617 |
+
</body>
|
| 618 |
+
</html>
|
shared.css
ADDED
|
@@ -0,0 +1,772 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ================================================================
|
| 2 |
+
FinWise — Shared Design System
|
| 3 |
+
Aesthetic: Dark fintech luxury · Syne + DM Sans typography
|
| 4 |
+
================================================================ */
|
| 5 |
+
|
| 6 |
+
@import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:ital,wght@0,300;0,400;0,500;0,600;1,400&family=DM+Mono:wght@400;500&display=swap');
|
| 7 |
+
|
| 8 |
+
/* ── Variables ─────────────────────────────────────────────────── */
|
| 9 |
+
:root {
|
| 10 |
+
--bg: #050d1a;
|
| 11 |
+
--bg2: #091525;
|
| 12 |
+
--bg3: #0d1f38;
|
| 13 |
+
--card: rgba(12,24,48,0.85);
|
| 14 |
+
--card2: rgba(18,34,66,0.70);
|
| 15 |
+
--border: rgba(34,211,238,0.12);
|
| 16 |
+
--border2: rgba(34,211,238,0.30);
|
| 17 |
+
|
| 18 |
+
--cyan: #22d3ee;
|
| 19 |
+
--cyan-d: #0891b2;
|
| 20 |
+
--emerald: #10b981;
|
| 21 |
+
--emerald-d: #059669;
|
| 22 |
+
--rose: #f43f5e;
|
| 23 |
+
--amber: #f59e0b;
|
| 24 |
+
--violet: #8b5cf6;
|
| 25 |
+
--indigo: #6366f1;
|
| 26 |
+
|
| 27 |
+
--text: #f0f6ff;
|
| 28 |
+
--text2: #8faac8;
|
| 29 |
+
--text3: #3d5a7a;
|
| 30 |
+
|
| 31 |
+
--nav-w: 240px;
|
| 32 |
+
--r: 14px;
|
| 33 |
+
--r-sm: 8px;
|
| 34 |
+
--r-lg: 20px;
|
| 35 |
+
--shadow: 0 8px 40px rgba(0,0,0,0.5);
|
| 36 |
+
--glow-c: 0 0 30px rgba(34,211,238,0.25);
|
| 37 |
+
--glow-e: 0 0 30px rgba(16,185,129,0.25);
|
| 38 |
+
|
| 39 |
+
--font-head: 'Syne', sans-serif;
|
| 40 |
+
--font-body: 'DM Sans', sans-serif;
|
| 41 |
+
--font-mono: 'DM Mono', monospace;
|
| 42 |
+
|
| 43 |
+
--transition: 0.22s cubic-bezier(.4,0,.2,1);
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
/* ── Reset & Base ──────────────────────────────────────────────── */
|
| 47 |
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
| 48 |
+
|
| 49 |
+
html { scroll-behavior: smooth; }
|
| 50 |
+
|
| 51 |
+
body {
|
| 52 |
+
font-family: var(--font-body);
|
| 53 |
+
background: var(--bg);
|
| 54 |
+
color: var(--text);
|
| 55 |
+
min-height: 100vh;
|
| 56 |
+
line-height: 1.6;
|
| 57 |
+
font-size: 15px;
|
| 58 |
+
overflow-x: hidden;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
/* Subtle grid background */
|
| 62 |
+
body::before {
|
| 63 |
+
content: '';
|
| 64 |
+
position: fixed;
|
| 65 |
+
inset: 0;
|
| 66 |
+
background-image:
|
| 67 |
+
linear-gradient(rgba(34,211,238,0.03) 1px, transparent 1px),
|
| 68 |
+
linear-gradient(90deg, rgba(34,211,238,0.03) 1px, transparent 1px);
|
| 69 |
+
background-size: 40px 40px;
|
| 70 |
+
pointer-events: none;
|
| 71 |
+
z-index: 0;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
/* ── Layout ────────────────────────────────────────────────────── */
|
| 75 |
+
.app-shell {
|
| 76 |
+
display: flex;
|
| 77 |
+
min-height: 100vh;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.main-content {
|
| 81 |
+
margin-left: var(--nav-w);
|
| 82 |
+
flex: 1;
|
| 83 |
+
padding: 32px;
|
| 84 |
+
position: relative;
|
| 85 |
+
z-index: 1;
|
| 86 |
+
max-width: calc(100vw - var(--nav-w));
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
/* ── Sidebar Navigation ────────────────────────────────────────── */
|
| 90 |
+
.sidebar {
|
| 91 |
+
position: fixed;
|
| 92 |
+
left: 0; top: 0; bottom: 0;
|
| 93 |
+
width: var(--nav-w);
|
| 94 |
+
background: linear-gradient(180deg, #071224 0%, #050d1a 100%);
|
| 95 |
+
border-right: 1px solid var(--border);
|
| 96 |
+
display: flex;
|
| 97 |
+
flex-direction: column;
|
| 98 |
+
z-index: 100;
|
| 99 |
+
padding: 0 0 20px;
|
| 100 |
+
overflow-y: auto;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.sidebar-logo {
|
| 104 |
+
padding: 28px 24px 24px;
|
| 105 |
+
border-bottom: 1px solid var(--border);
|
| 106 |
+
margin-bottom: 12px;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.sidebar-logo .logo-mark {
|
| 110 |
+
display: flex;
|
| 111 |
+
align-items: center;
|
| 112 |
+
gap: 10px;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.logo-icon {
|
| 116 |
+
width: 36px; height: 36px;
|
| 117 |
+
background: linear-gradient(135deg, var(--cyan), var(--emerald));
|
| 118 |
+
border-radius: 10px;
|
| 119 |
+
display: flex; align-items: center; justify-content: center;
|
| 120 |
+
font-size: 18px;
|
| 121 |
+
box-shadow: var(--glow-c);
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
.logo-text {
|
| 125 |
+
font-family: var(--font-head);
|
| 126 |
+
font-size: 22px;
|
| 127 |
+
font-weight: 800;
|
| 128 |
+
background: linear-gradient(90deg, var(--cyan), var(--emerald));
|
| 129 |
+
-webkit-background-clip: text;
|
| 130 |
+
-webkit-text-fill-color: transparent;
|
| 131 |
+
background-clip: text;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.logo-sub {
|
| 135 |
+
font-size: 11px;
|
| 136 |
+
color: var(--text3);
|
| 137 |
+
margin-top: 2px;
|
| 138 |
+
letter-spacing: 0.05em;
|
| 139 |
+
text-transform: uppercase;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.nav-section {
|
| 143 |
+
padding: 8px 12px;
|
| 144 |
+
flex: 1;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
.nav-label {
|
| 148 |
+
font-size: 10px;
|
| 149 |
+
letter-spacing: 0.12em;
|
| 150 |
+
text-transform: uppercase;
|
| 151 |
+
color: var(--text3);
|
| 152 |
+
padding: 8px 12px 4px;
|
| 153 |
+
font-weight: 600;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
.nav-item {
|
| 157 |
+
display: flex;
|
| 158 |
+
align-items: center;
|
| 159 |
+
gap: 12px;
|
| 160 |
+
padding: 11px 14px;
|
| 161 |
+
border-radius: var(--r-sm);
|
| 162 |
+
color: var(--text2);
|
| 163 |
+
text-decoration: none;
|
| 164 |
+
font-size: 14px;
|
| 165 |
+
font-weight: 500;
|
| 166 |
+
transition: all var(--transition);
|
| 167 |
+
margin-bottom: 2px;
|
| 168 |
+
position: relative;
|
| 169 |
+
overflow: hidden;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
.nav-item::before {
|
| 173 |
+
content: '';
|
| 174 |
+
position: absolute;
|
| 175 |
+
inset: 0;
|
| 176 |
+
background: linear-gradient(90deg, var(--cyan), transparent);
|
| 177 |
+
opacity: 0;
|
| 178 |
+
transition: opacity var(--transition);
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
.nav-item:hover {
|
| 182 |
+
color: var(--text);
|
| 183 |
+
background: rgba(34,211,238,0.07);
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
.nav-item.active {
|
| 187 |
+
color: var(--cyan);
|
| 188 |
+
background: rgba(34,211,238,0.10);
|
| 189 |
+
border: 1px solid rgba(34,211,238,0.2);
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
.nav-item.active .nav-icon { filter: drop-shadow(0 0 6px var(--cyan)); }
|
| 193 |
+
|
| 194 |
+
.nav-icon {
|
| 195 |
+
font-size: 18px;
|
| 196 |
+
width: 24px;
|
| 197 |
+
text-align: center;
|
| 198 |
+
flex-shrink: 0;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
.nav-badge {
|
| 202 |
+
margin-left: auto;
|
| 203 |
+
background: var(--cyan);
|
| 204 |
+
color: var(--bg);
|
| 205 |
+
font-size: 10px;
|
| 206 |
+
font-weight: 700;
|
| 207 |
+
padding: 2px 6px;
|
| 208 |
+
border-radius: 20px;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
.sidebar-footer {
|
| 212 |
+
padding: 16px 20px 0;
|
| 213 |
+
border-top: 1px solid var(--border);
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
.market-ticker {
|
| 217 |
+
font-size: 11px;
|
| 218 |
+
color: var(--text3);
|
| 219 |
+
margin-bottom: 8px;
|
| 220 |
+
letter-spacing: 0.05em;
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
.ticker-item {
|
| 224 |
+
display: flex;
|
| 225 |
+
justify-content: space-between;
|
| 226 |
+
font-size: 12px;
|
| 227 |
+
padding: 3px 0;
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
.ticker-item .up { color: var(--emerald); font-family: var(--font-mono); }
|
| 231 |
+
.ticker-item .down { color: var(--rose); font-family: var(--font-mono); }
|
| 232 |
+
.ticker-name { color: var(--text2); font-weight: 500; }
|
| 233 |
+
|
| 234 |
+
/* ── Bottom Mobile Nav ─────────────────────────────────────────── */
|
| 235 |
+
.bottom-nav {
|
| 236 |
+
display: none;
|
| 237 |
+
position: fixed;
|
| 238 |
+
bottom: 0; left: 0; right: 0;
|
| 239 |
+
background: rgba(5,13,26,0.96);
|
| 240 |
+
backdrop-filter: blur(20px);
|
| 241 |
+
border-top: 1px solid var(--border);
|
| 242 |
+
padding: 8px 0 env(safe-area-inset-bottom);
|
| 243 |
+
z-index: 200;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
.bottom-nav-inner {
|
| 247 |
+
display: flex;
|
| 248 |
+
justify-content: space-around;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
.bottom-nav-item {
|
| 252 |
+
display: flex;
|
| 253 |
+
flex-direction: column;
|
| 254 |
+
align-items: center;
|
| 255 |
+
gap: 3px;
|
| 256 |
+
padding: 6px 12px;
|
| 257 |
+
color: var(--text3);
|
| 258 |
+
text-decoration: none;
|
| 259 |
+
font-size: 10px;
|
| 260 |
+
font-weight: 600;
|
| 261 |
+
transition: color var(--transition);
|
| 262 |
+
border-radius: var(--r-sm);
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
.bottom-nav-item .bnav-icon { font-size: 20px; }
|
| 266 |
+
.bottom-nav-item.active { color: var(--cyan); }
|
| 267 |
+
.bottom-nav-item:hover { color: var(--text2); }
|
| 268 |
+
|
| 269 |
+
/* ── Page Header ───────────────────────────────────────────────── */
|
| 270 |
+
.page-header {
|
| 271 |
+
margin-bottom: 32px;
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
.page-title {
|
| 275 |
+
font-family: var(--font-head);
|
| 276 |
+
font-size: 32px;
|
| 277 |
+
font-weight: 800;
|
| 278 |
+
line-height: 1.1;
|
| 279 |
+
margin-bottom: 6px;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
.page-title span {
|
| 283 |
+
background: linear-gradient(90deg, var(--cyan), var(--emerald));
|
| 284 |
+
-webkit-background-clip: text;
|
| 285 |
+
-webkit-text-fill-color: transparent;
|
| 286 |
+
background-clip: text;
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
.page-subtitle { color: var(--text2); font-size: 15px; }
|
| 290 |
+
|
| 291 |
+
/* ── Cards ─────────────────────────────────────────────────────── */
|
| 292 |
+
.card {
|
| 293 |
+
background: var(--card);
|
| 294 |
+
border: 1px solid var(--border);
|
| 295 |
+
border-radius: var(--r);
|
| 296 |
+
padding: 24px;
|
| 297 |
+
backdrop-filter: blur(12px);
|
| 298 |
+
transition: border-color var(--transition), box-shadow var(--transition);
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
.card:hover {
|
| 302 |
+
border-color: var(--border2);
|
| 303 |
+
box-shadow: var(--glow-c);
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
.card-title {
|
| 307 |
+
font-family: var(--font-head);
|
| 308 |
+
font-size: 14px;
|
| 309 |
+
font-weight: 700;
|
| 310 |
+
color: var(--text2);
|
| 311 |
+
text-transform: uppercase;
|
| 312 |
+
letter-spacing: 0.08em;
|
| 313 |
+
margin-bottom: 16px;
|
| 314 |
+
display: flex;
|
| 315 |
+
align-items: center;
|
| 316 |
+
gap: 8px;
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
/* ── Stat Cards ────────────────────────────────────────────────── */
|
| 320 |
+
.stat-grid {
|
| 321 |
+
display: grid;
|
| 322 |
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
| 323 |
+
gap: 16px;
|
| 324 |
+
margin-bottom: 28px;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
.stat-card {
|
| 328 |
+
background: var(--card);
|
| 329 |
+
border: 1px solid var(--border);
|
| 330 |
+
border-radius: var(--r);
|
| 331 |
+
padding: 22px;
|
| 332 |
+
backdrop-filter: blur(12px);
|
| 333 |
+
transition: all var(--transition);
|
| 334 |
+
position: relative;
|
| 335 |
+
overflow: hidden;
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
.stat-card::after {
|
| 339 |
+
content: '';
|
| 340 |
+
position: absolute;
|
| 341 |
+
top: 0; left: 0; right: 0;
|
| 342 |
+
height: 2px;
|
| 343 |
+
background: var(--accent-color, var(--cyan));
|
| 344 |
+
opacity: 0.7;
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
.stat-card:hover {
|
| 348 |
+
border-color: var(--border2);
|
| 349 |
+
transform: translateY(-2px);
|
| 350 |
+
box-shadow: 0 12px 40px rgba(0,0,0,0.4);
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
.stat-label {
|
| 354 |
+
font-size: 11px;
|
| 355 |
+
font-weight: 600;
|
| 356 |
+
text-transform: uppercase;
|
| 357 |
+
letter-spacing: 0.08em;
|
| 358 |
+
color: var(--text3);
|
| 359 |
+
margin-bottom: 10px;
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
.stat-value {
|
| 363 |
+
font-family: var(--font-head);
|
| 364 |
+
font-size: 26px;
|
| 365 |
+
font-weight: 800;
|
| 366 |
+
color: var(--text);
|
| 367 |
+
line-height: 1;
|
| 368 |
+
margin-bottom: 8px;
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
.stat-change {
|
| 372 |
+
font-size: 12px;
|
| 373 |
+
font-weight: 600;
|
| 374 |
+
display: flex;
|
| 375 |
+
align-items: center;
|
| 376 |
+
gap: 4px;
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
.stat-change.up { color: var(--emerald); }
|
| 380 |
+
.stat-change.down { color: var(--rose); }
|
| 381 |
+
|
| 382 |
+
/* ── Grids ─────────────────────────────────────────────────────── */
|
| 383 |
+
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
| 384 |
+
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; }
|
| 385 |
+
.grid-60-40 { display: grid; grid-template-columns: 60fr 40fr; gap: 20px; }
|
| 386 |
+
.grid-40-60 { display: grid; grid-template-columns: 40fr 60fr; gap: 20px; }
|
| 387 |
+
|
| 388 |
+
/* ── Buttons ───────────────────────────────────────────────────── */
|
| 389 |
+
.btn {
|
| 390 |
+
display: inline-flex;
|
| 391 |
+
align-items: center;
|
| 392 |
+
gap: 8px;
|
| 393 |
+
padding: 11px 22px;
|
| 394 |
+
border-radius: var(--r-sm);
|
| 395 |
+
font-family: var(--font-body);
|
| 396 |
+
font-size: 14px;
|
| 397 |
+
font-weight: 600;
|
| 398 |
+
cursor: pointer;
|
| 399 |
+
border: none;
|
| 400 |
+
transition: all var(--transition);
|
| 401 |
+
text-decoration: none;
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
.btn-primary {
|
| 405 |
+
background: linear-gradient(135deg, var(--cyan-d), var(--cyan));
|
| 406 |
+
color: var(--bg);
|
| 407 |
+
box-shadow: 0 4px 16px rgba(34,211,238,0.3);
|
| 408 |
+
}
|
| 409 |
+
.btn-primary:hover {
|
| 410 |
+
box-shadow: 0 6px 24px rgba(34,211,238,0.5);
|
| 411 |
+
transform: translateY(-1px);
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
.btn-secondary {
|
| 415 |
+
background: transparent;
|
| 416 |
+
color: var(--cyan);
|
| 417 |
+
border: 1px solid rgba(34,211,238,0.3);
|
| 418 |
+
}
|
| 419 |
+
.btn-secondary:hover {
|
| 420 |
+
background: rgba(34,211,238,0.08);
|
| 421 |
+
border-color: var(--cyan);
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
.btn-ghost {
|
| 425 |
+
background: rgba(255,255,255,0.04);
|
| 426 |
+
color: var(--text2);
|
| 427 |
+
border: 1px solid var(--border);
|
| 428 |
+
}
|
| 429 |
+
.btn-ghost:hover { background: rgba(255,255,255,0.08); color: var(--text); }
|
| 430 |
+
|
| 431 |
+
.btn-emerald {
|
| 432 |
+
background: linear-gradient(135deg, var(--emerald-d), var(--emerald));
|
| 433 |
+
color: #fff;
|
| 434 |
+
box-shadow: var(--glow-e);
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
.btn-sm { padding: 7px 14px; font-size: 13px; }
|
| 438 |
+
.btn-lg { padding: 14px 28px; font-size: 16px; }
|
| 439 |
+
.btn-full { width: 100%; justify-content: center; }
|
| 440 |
+
|
| 441 |
+
/* ── Choice Buttons ────────────────────────────────────────────── */
|
| 442 |
+
.choice-group {
|
| 443 |
+
display: flex;
|
| 444 |
+
flex-wrap: wrap;
|
| 445 |
+
gap: 10px;
|
| 446 |
+
margin-bottom: 20px;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
.choice-btn {
|
| 450 |
+
padding: 10px 18px;
|
| 451 |
+
border-radius: 100px;
|
| 452 |
+
border: 1px solid var(--border2);
|
| 453 |
+
background: transparent;
|
| 454 |
+
color: var(--text2);
|
| 455 |
+
font-family: var(--font-body);
|
| 456 |
+
font-size: 13px;
|
| 457 |
+
font-weight: 600;
|
| 458 |
+
cursor: pointer;
|
| 459 |
+
transition: all var(--transition);
|
| 460 |
+
display: flex;
|
| 461 |
+
align-items: center;
|
| 462 |
+
gap: 6px;
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
.choice-btn:hover { border-color: var(--cyan); color: var(--text); }
|
| 466 |
+
|
| 467 |
+
.choice-btn.selected {
|
| 468 |
+
background: rgba(34,211,238,0.12);
|
| 469 |
+
border-color: var(--cyan);
|
| 470 |
+
color: var(--cyan);
|
| 471 |
+
box-shadow: 0 0 16px rgba(34,211,238,0.2);
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
+
/* ── Sliders ───────────────────────────────────────────────────── */
|
| 475 |
+
.slider-wrap { margin-bottom: 20px; }
|
| 476 |
+
.slider-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
|
| 477 |
+
.slider-label { font-size: 13px; color: var(--text2); font-weight: 500; }
|
| 478 |
+
.slider-val { font-family: var(--font-mono); font-size: 14px; color: var(--cyan); font-weight: 500; }
|
| 479 |
+
|
| 480 |
+
input[type=range] {
|
| 481 |
+
-webkit-appearance: none;
|
| 482 |
+
width: 100%;
|
| 483 |
+
height: 6px;
|
| 484 |
+
border-radius: 3px;
|
| 485 |
+
background: var(--bg3);
|
| 486 |
+
outline: none;
|
| 487 |
+
cursor: pointer;
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
input[type=range]::-webkit-slider-thumb {
|
| 491 |
+
-webkit-appearance: none;
|
| 492 |
+
width: 18px; height: 18px;
|
| 493 |
+
border-radius: 50%;
|
| 494 |
+
background: linear-gradient(135deg, var(--cyan-d), var(--cyan));
|
| 495 |
+
box-shadow: 0 0 10px rgba(34,211,238,0.5);
|
| 496 |
+
cursor: pointer;
|
| 497 |
+
transition: box-shadow var(--transition);
|
| 498 |
+
}
|
| 499 |
+
input[type=range]::-webkit-slider-thumb:hover {
|
| 500 |
+
box-shadow: 0 0 18px rgba(34,211,238,0.8);
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
/* ── Progress Bar ──────────────────────────────────────────────── */
|
| 504 |
+
.progress-bar {
|
| 505 |
+
height: 8px;
|
| 506 |
+
background: var(--bg3);
|
| 507 |
+
border-radius: 4px;
|
| 508 |
+
overflow: hidden;
|
| 509 |
+
margin-bottom: 4px;
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
.progress-fill {
|
| 513 |
+
height: 100%;
|
| 514 |
+
border-radius: 4px;
|
| 515 |
+
transition: width 0.6s cubic-bezier(.4,0,.2,1);
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
/* ── Tags / Badges ─────────────────────────────────────────────── */
|
| 519 |
+
.badge {
|
| 520 |
+
display: inline-flex;
|
| 521 |
+
align-items: center;
|
| 522 |
+
gap: 5px;
|
| 523 |
+
padding: 4px 10px;
|
| 524 |
+
border-radius: 100px;
|
| 525 |
+
font-size: 11px;
|
| 526 |
+
font-weight: 700;
|
| 527 |
+
letter-spacing: 0.04em;
|
| 528 |
+
text-transform: uppercase;
|
| 529 |
+
}
|
| 530 |
+
|
| 531 |
+
.badge-cyan { background: rgba(34,211,238,0.15); color: var(--cyan); }
|
| 532 |
+
.badge-emerald { background: rgba(16,185,129,0.15); color: var(--emerald); }
|
| 533 |
+
.badge-rose { background: rgba(244,63,94,0.15); color: var(--rose); }
|
| 534 |
+
.badge-amber { background: rgba(245,158,11,0.15); color: var(--amber); }
|
| 535 |
+
.badge-violet { background: rgba(139,92,246,0.15); color: var(--violet); }
|
| 536 |
+
|
| 537 |
+
/* ── Table ─────────────────────────────────────────────────────── */
|
| 538 |
+
.table-wrap { overflow-x: auto; }
|
| 539 |
+
|
| 540 |
+
table {
|
| 541 |
+
width: 100%;
|
| 542 |
+
border-collapse: collapse;
|
| 543 |
+
font-size: 14px;
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
thead th {
|
| 547 |
+
padding: 12px 16px;
|
| 548 |
+
text-align: left;
|
| 549 |
+
font-size: 11px;
|
| 550 |
+
font-weight: 700;
|
| 551 |
+
text-transform: uppercase;
|
| 552 |
+
letter-spacing: 0.08em;
|
| 553 |
+
color: var(--text3);
|
| 554 |
+
border-bottom: 1px solid var(--border);
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
tbody tr {
|
| 558 |
+
border-bottom: 1px solid rgba(34,211,238,0.05);
|
| 559 |
+
transition: background var(--transition);
|
| 560 |
+
}
|
| 561 |
+
|
| 562 |
+
tbody tr:hover { background: rgba(34,211,238,0.04); }
|
| 563 |
+
|
| 564 |
+
tbody td {
|
| 565 |
+
padding: 14px 16px;
|
| 566 |
+
color: var(--text2);
|
| 567 |
+
vertical-align: middle;
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
tbody td:first-child { color: var(--text); font-weight: 600; }
|
| 571 |
+
|
| 572 |
+
/* ── Tooltips (simple) ─────────────────────────────────────────── */
|
| 573 |
+
.tooltip-wrap { position: relative; display: inline-flex; }
|
| 574 |
+
.tooltip-icon {
|
| 575 |
+
width: 16px; height: 16px;
|
| 576 |
+
border-radius: 50%;
|
| 577 |
+
background: var(--bg3);
|
| 578 |
+
border: 1px solid var(--border2);
|
| 579 |
+
color: var(--text3);
|
| 580 |
+
font-size: 10px;
|
| 581 |
+
display: inline-flex;
|
| 582 |
+
align-items: center;
|
| 583 |
+
justify-content: center;
|
| 584 |
+
cursor: help;
|
| 585 |
+
font-weight: 700;
|
| 586 |
+
font-style: italic;
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
/* ── Section headings ──────────────────────────────────────────── */
|
| 590 |
+
.section-title {
|
| 591 |
+
font-family: var(--font-head);
|
| 592 |
+
font-size: 18px;
|
| 593 |
+
font-weight: 700;
|
| 594 |
+
margin-bottom: 16px;
|
| 595 |
+
display: flex;
|
| 596 |
+
align-items: center;
|
| 597 |
+
gap: 10px;
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
/* ── Divider ───────────────────────────────────────────────────── */
|
| 601 |
+
.divider { height: 1px; background: var(--border); margin: 24px 0; }
|
| 602 |
+
|
| 603 |
+
/* ── Input Fields ──────────────────────────────────────────────── */
|
| 604 |
+
.field-group { margin-bottom: 16px; }
|
| 605 |
+
.field-label {
|
| 606 |
+
display: block;
|
| 607 |
+
font-size: 12px;
|
| 608 |
+
font-weight: 600;
|
| 609 |
+
color: var(--text2);
|
| 610 |
+
text-transform: uppercase;
|
| 611 |
+
letter-spacing: 0.06em;
|
| 612 |
+
margin-bottom: 6px;
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
input[type=text], input[type=number], select, textarea {
|
| 616 |
+
width: 100%;
|
| 617 |
+
padding: 11px 14px;
|
| 618 |
+
background: var(--bg3);
|
| 619 |
+
border: 1px solid var(--border);
|
| 620 |
+
border-radius: var(--r-sm);
|
| 621 |
+
color: var(--text);
|
| 622 |
+
font-family: var(--font-body);
|
| 623 |
+
font-size: 14px;
|
| 624 |
+
outline: none;
|
| 625 |
+
transition: border-color var(--transition);
|
| 626 |
+
}
|
| 627 |
+
|
| 628 |
+
input[type=text]:focus,
|
| 629 |
+
input[type=number]:focus,
|
| 630 |
+
select:focus,
|
| 631 |
+
textarea:focus {
|
| 632 |
+
border-color: var(--cyan);
|
| 633 |
+
box-shadow: 0 0 0 3px rgba(34,211,238,0.1);
|
| 634 |
+
}
|
| 635 |
+
|
| 636 |
+
select option { background: var(--bg2); }
|
| 637 |
+
|
| 638 |
+
/* ── Tabs ──────────────────────────────────────────────────────── */
|
| 639 |
+
.tab-bar {
|
| 640 |
+
display: flex;
|
| 641 |
+
gap: 4px;
|
| 642 |
+
background: var(--bg3);
|
| 643 |
+
border-radius: var(--r-sm);
|
| 644 |
+
padding: 4px;
|
| 645 |
+
margin-bottom: 24px;
|
| 646 |
+
flex-wrap: wrap;
|
| 647 |
+
}
|
| 648 |
+
|
| 649 |
+
.tab-btn {
|
| 650 |
+
flex: 1;
|
| 651 |
+
min-width: 120px;
|
| 652 |
+
padding: 9px 16px;
|
| 653 |
+
border-radius: 6px;
|
| 654 |
+
border: none;
|
| 655 |
+
background: transparent;
|
| 656 |
+
color: var(--text2);
|
| 657 |
+
font-family: var(--font-body);
|
| 658 |
+
font-size: 13px;
|
| 659 |
+
font-weight: 600;
|
| 660 |
+
cursor: pointer;
|
| 661 |
+
transition: all var(--transition);
|
| 662 |
+
white-space: nowrap;
|
| 663 |
+
text-align: center;
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
.tab-btn.active {
|
| 667 |
+
background: var(--card);
|
| 668 |
+
color: var(--cyan);
|
| 669 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
| 670 |
+
}
|
| 671 |
+
|
| 672 |
+
.tab-pane { display: none; }
|
| 673 |
+
.tab-pane.active { display: block; }
|
| 674 |
+
|
| 675 |
+
/* ── Risk Meter ────────────────────────────────────────────────── */
|
| 676 |
+
.risk-meter-wrap {
|
| 677 |
+
text-align: center;
|
| 678 |
+
padding: 20px 0;
|
| 679 |
+
}
|
| 680 |
+
|
| 681 |
+
.risk-meter-label {
|
| 682 |
+
font-size: 13px;
|
| 683 |
+
color: var(--text2);
|
| 684 |
+
margin-top: 12px;
|
| 685 |
+
font-weight: 600;
|
| 686 |
+
}
|
| 687 |
+
|
| 688 |
+
/* ── Allocation Bar ────────────────────────────────────────────── */
|
| 689 |
+
.alloc-bar {
|
| 690 |
+
height: 10px;
|
| 691 |
+
border-radius: 5px;
|
| 692 |
+
display: flex;
|
| 693 |
+
overflow: hidden;
|
| 694 |
+
gap: 2px;
|
| 695 |
+
margin: 10px 0;
|
| 696 |
+
}
|
| 697 |
+
|
| 698 |
+
.alloc-segment {
|
| 699 |
+
height: 100%;
|
| 700 |
+
border-radius: 5px;
|
| 701 |
+
transition: width 0.5s cubic-bezier(.4,0,.2,1);
|
| 702 |
+
}
|
| 703 |
+
|
| 704 |
+
/* ── Animations ────────────────────────────────────────────────── */
|
| 705 |
+
@keyframes fadeInUp {
|
| 706 |
+
from { opacity: 0; transform: translateY(16px); }
|
| 707 |
+
to { opacity: 1; transform: translateY(0); }
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
@keyframes pulse-glow {
|
| 711 |
+
0%, 100% { box-shadow: 0 0 10px rgba(34,211,238,0.2); }
|
| 712 |
+
50% { box-shadow: 0 0 25px rgba(34,211,238,0.5); }
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
@keyframes counter { from { opacity: 0; } to { opacity: 1; } }
|
| 716 |
+
|
| 717 |
+
.fade-in { animation: fadeInUp 0.5s ease both; }
|
| 718 |
+
.fade-in-1 { animation-delay: 0.1s; }
|
| 719 |
+
.fade-in-2 { animation-delay: 0.2s; }
|
| 720 |
+
.fade-in-3 { animation-delay: 0.3s; }
|
| 721 |
+
.fade-in-4 { animation-delay: 0.4s; }
|
| 722 |
+
|
| 723 |
+
/* ── Utility ───────────────────────────────────────────────────── */
|
| 724 |
+
.up { color: var(--emerald); }
|
| 725 |
+
.down { color: var(--rose); }
|
| 726 |
+
.text-muted { color: var(--text2); }
|
| 727 |
+
.text-sm { font-size: 13px; }
|
| 728 |
+
.mono { font-family: var(--font-mono); }
|
| 729 |
+
.mt-0 { margin-top: 0; }
|
| 730 |
+
.mt-16 { margin-top: 16px; }
|
| 731 |
+
.mt-24 { margin-top: 24px; }
|
| 732 |
+
.mb-16 { margin-bottom: 16px; }
|
| 733 |
+
.mb-24 { margin-bottom: 24px; }
|
| 734 |
+
.flex { display: flex; }
|
| 735 |
+
.flex-col { flex-direction: column; }
|
| 736 |
+
.items-center { align-items: center; }
|
| 737 |
+
.justify-between { justify-content: space-between; }
|
| 738 |
+
.gap-8 { gap: 8px; }
|
| 739 |
+
.gap-12 { gap: 12px; }
|
| 740 |
+
.gap-16 { gap: 16px; }
|
| 741 |
+
.full-width { width: 100%; }
|
| 742 |
+
.text-right { text-align: right; }
|
| 743 |
+
.text-center { text-align: center; }
|
| 744 |
+
.bold { font-weight: 700; }
|
| 745 |
+
.hidden { display: none !important; }
|
| 746 |
+
|
| 747 |
+
/* ── Scroll ────────────────────────────────────────────────────── */
|
| 748 |
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
| 749 |
+
::-webkit-scrollbar-track { background: var(--bg); }
|
| 750 |
+
::-webkit-scrollbar-thumb { background: var(--bg3); border-radius: 3px; }
|
| 751 |
+
::-webkit-scrollbar-thumb:hover { background: var(--text3); }
|
| 752 |
+
|
| 753 |
+
/* ── Responsive ────────────────────────────────────────────────── */
|
| 754 |
+
@media (max-width: 900px) {
|
| 755 |
+
.sidebar { display: none; }
|
| 756 |
+
.bottom-nav { display: block; }
|
| 757 |
+
.main-content {
|
| 758 |
+
margin-left: 0;
|
| 759 |
+
padding: 20px 16px 80px;
|
| 760 |
+
max-width: 100vw;
|
| 761 |
+
}
|
| 762 |
+
.grid-2, .grid-3, .grid-60-40, .grid-40-60 {
|
| 763 |
+
grid-template-columns: 1fr;
|
| 764 |
+
}
|
| 765 |
+
.stat-grid { grid-template-columns: 1fr 1fr; }
|
| 766 |
+
.page-title { font-size: 24px; }
|
| 767 |
+
}
|
| 768 |
+
|
| 769 |
+
@media (max-width: 480px) {
|
| 770 |
+
.stat-grid { grid-template-columns: 1fr; }
|
| 771 |
+
.choice-group { flex-direction: column; }
|
| 772 |
+
}
|
shared.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ================================================================
|
| 2 |
+
FinWise — Shared JavaScript
|
| 3 |
+
Utilities, navigation, localStorage helpers
|
| 4 |
+
================================================================ */
|
| 5 |
+
|
| 6 |
+
// ── Page detection ────────────────────────────────────────────────
|
| 7 |
+
(function setActiveNav() {
|
| 8 |
+
const page = location.pathname.split('/').pop() || 'index.html';
|
| 9 |
+
document.querySelectorAll('.nav-item, .bottom-nav-item').forEach(el => {
|
| 10 |
+
const href = el.getAttribute('href') || '';
|
| 11 |
+
if (href === page || (page === '' && href === 'index.html')) {
|
| 12 |
+
el.classList.add('active');
|
| 13 |
+
}
|
| 14 |
+
});
|
| 15 |
+
})();
|
| 16 |
+
|
| 17 |
+
// ── LocalStorage Helpers ──────────────────────────────────────────
|
| 18 |
+
const LS = {
|
| 19 |
+
get(key, fallback = null) {
|
| 20 |
+
try { const v = localStorage.getItem(key); return v ? JSON.parse(v) : fallback; }
|
| 21 |
+
catch { return fallback; }
|
| 22 |
+
},
|
| 23 |
+
set(key, val) {
|
| 24 |
+
try { localStorage.setItem(key, JSON.stringify(val)); return true; }
|
| 25 |
+
catch { return false; }
|
| 26 |
+
}
|
| 27 |
+
};
|
| 28 |
+
|
| 29 |
+
// ── Default Portfolio Data ────────────────────────────────────────
|
| 30 |
+
const DEFAULT_PORTFOLIO = {
|
| 31 |
+
assets: [
|
| 32 |
+
{ ticker: 'VOO', name: 'Vanguard S&P 500 ETF', pct: 30, price: 478.22, shares: 3.1, color: '#22d3ee', type: 'ETF' },
|
| 33 |
+
{ ticker: 'QQQ', name: 'Invesco Nasdaq 100 ETF', pct: 20, price: 456.80, shares: 2.2, color: '#8b5cf6', type: 'ETF' },
|
| 34 |
+
{ ticker: 'NVDA', name: 'NVIDIA Corporation', pct: 15, price: 875.40, shares: 0.85, color: '#10b981', type: 'Stock' },
|
| 35 |
+
{ ticker: 'AAPL', name: 'Apple Inc.', pct: 12, price: 188.60, shares: 3.2, color: '#f59e0b', type: 'Stock' },
|
| 36 |
+
{ ticker: 'BND', name: 'Vanguard Bond ETF', pct: 13, price: 73.40, shares: 8.8, color: '#6366f1', type: 'Bond' },
|
| 37 |
+
{ ticker: 'GLD', name: 'SPDR Gold Trust', pct: 7, price: 218.10, shares: 1.6, color: '#f43f5e', type: 'Commodity' },
|
| 38 |
+
{ ticker: 'AMZN', name: 'Amazon.com Inc.', pct: 3, price: 188.90, shares: 0.8, color: '#0ea5e9', type: 'Stock' },
|
| 39 |
+
],
|
| 40 |
+
totalInvested: 12500,
|
| 41 |
+
riskProfile: 'Moderate',
|
| 42 |
+
goals: ['Wealth Building'],
|
| 43 |
+
lastUpdated: new Date().toISOString()
|
| 44 |
+
};
|
| 45 |
+
|
| 46 |
+
function getPortfolio() {
|
| 47 |
+
return LS.get('fw_portfolio', DEFAULT_PORTFOLIO);
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
function savePortfolio(p) {
|
| 51 |
+
p.lastUpdated = new Date().toISOString();
|
| 52 |
+
LS.set('fw_portfolio', p);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
// ── Simulated Market Data ─────────────────────────────────────────
|
| 56 |
+
const MARKET_PRICES = {
|
| 57 |
+
'VOO': { price: 478.22, change: +1.24, changePct: +0.26 },
|
| 58 |
+
'QQQ': { price: 456.80, change: -2.10, changePct: -0.46 },
|
| 59 |
+
'NVDA': { price: 875.40, change: +18.5, changePct: +2.16 },
|
| 60 |
+
'AAPL': { price: 188.60, change: +0.80, changePct: +0.43 },
|
| 61 |
+
'BND': { price: 73.40, change: -0.05, changePct: -0.07 },
|
| 62 |
+
'GLD': { price: 218.10, change: +3.20, changePct: +1.49 },
|
| 63 |
+
'AMZN': { price: 188.90, change: +1.60, changePct: +0.86 },
|
| 64 |
+
'VTI': { price: 240.30, change: +0.94, changePct: +0.39 },
|
| 65 |
+
'TSLA': { price: 182.30, change: -4.20, changePct: -2.25 },
|
| 66 |
+
'WMT': { price: 67.80, change: +0.30, changePct: +0.44 },
|
| 67 |
+
'MCD': { price: 281.50, change: +1.10, changePct: +0.39 },
|
| 68 |
+
};
|
| 69 |
+
|
| 70 |
+
// ── Historical Performance Generator ─────────────────────────────
|
| 71 |
+
function generateHistory(days = 180, startVal = 10000, volatility = 0.012) {
|
| 72 |
+
const data = [];
|
| 73 |
+
let val = startVal;
|
| 74 |
+
const now = Date.now();
|
| 75 |
+
for (let i = days; i >= 0; i--) {
|
| 76 |
+
const date = new Date(now - i * 86400000);
|
| 77 |
+
const change = (Math.random() - 0.46) * volatility;
|
| 78 |
+
val = val * (1 + change);
|
| 79 |
+
data.push({ date: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), value: Math.round(val * 100) / 100 });
|
| 80 |
+
}
|
| 81 |
+
return data;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
// ── Number Formatting ─────────────────────────────────────────────
|
| 85 |
+
function fmt$(n) { return '$' + n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }
|
| 86 |
+
function fmtPct(n) { return (n > 0 ? '+' : '') + n.toFixed(2) + '%'; }
|
| 87 |
+
function fmtK(n) { return n >= 1000000 ? '$' + (n/1000000).toFixed(2) + 'M' : n >= 1000 ? '$' + (n/1000).toFixed(1) + 'K' : '$' + n.toFixed(0); }
|
| 88 |
+
|
| 89 |
+
// ── Animated Counter ──────────────────────────────────────────────
|
| 90 |
+
function animateCounter(el, target, prefix = '', suffix = '', duration = 1200) {
|
| 91 |
+
const start = parseFloat(el.textContent.replace(/[^0-9.-]/g, '')) || 0;
|
| 92 |
+
const startTime = performance.now();
|
| 93 |
+
function step(now) {
|
| 94 |
+
const p = Math.min((now - startTime) / duration, 1);
|
| 95 |
+
const ease = 1 - Math.pow(1 - p, 3);
|
| 96 |
+
const val = start + (target - start) * ease;
|
| 97 |
+
el.textContent = prefix + val.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + suffix;
|
| 98 |
+
if (p < 1) requestAnimationFrame(step);
|
| 99 |
+
}
|
| 100 |
+
requestAnimationFrame(step);
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
// ── Chart.js Defaults ─────────────────────────────────────────────
|
| 104 |
+
function applyChartDefaults() {
|
| 105 |
+
if (typeof Chart === 'undefined') return;
|
| 106 |
+
Chart.defaults.color = '#8faac8';
|
| 107 |
+
Chart.defaults.borderColor = 'rgba(34,211,238,0.10)';
|
| 108 |
+
Chart.defaults.font.family = "'DM Sans', sans-serif";
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
// ── Ticker Data for Sidebar ───────────────────────────────────────
|
| 112 |
+
const TICKERS = [
|
| 113 |
+
{ sym: 'S&P 500', val: '5,308', chg: '+0.26%', up: true },
|
| 114 |
+
{ sym: 'NASDAQ', val: '16,742', chg: '-0.46%', up: false },
|
| 115 |
+
{ sym: 'BTC', val: '68,420', chg: '+2.14%', up: true },
|
| 116 |
+
{ sym: 'GOLD', val: '2,318', chg: '+1.49%', up: true },
|
| 117 |
+
];
|
| 118 |
+
|
| 119 |
+
function renderSidebarTickers() {
|
| 120 |
+
const container = document.getElementById('sidebar-tickers');
|
| 121 |
+
if (!container) return;
|
| 122 |
+
container.innerHTML = TICKERS.map(t => `
|
| 123 |
+
<div class="ticker-item">
|
| 124 |
+
<span class="ticker-name">${t.sym}</span>
|
| 125 |
+
<span class="${t.up ? 'up' : 'down'}">${t.chg}</span>
|
| 126 |
+
</div>
|
| 127 |
+
`).join('');
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
// ── Risk Score Calculator ─────────────────────────────────────────
|
| 131 |
+
function calcRiskScore(portfolio) {
|
| 132 |
+
const weights = { ETF: 3, Stock: 5, Bond: 1, Commodity: 4 };
|
| 133 |
+
let score = 0;
|
| 134 |
+
portfolio.assets.forEach(a => {
|
| 135 |
+
score += (weights[a.type] || 3) * (a.pct / 100);
|
| 136 |
+
});
|
| 137 |
+
return Math.round((score / 5) * 100); // 0-100
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
// ── Diversification Score ─────────────────────────────────────────
|
| 141 |
+
function calcDiversification(portfolio) {
|
| 142 |
+
const types = [...new Set(portfolio.assets.map(a => a.type))].length;
|
| 143 |
+
const count = portfolio.assets.length;
|
| 144 |
+
const maxPct = Math.max(...portfolio.assets.map(a => a.pct));
|
| 145 |
+
const concentration = maxPct > 40 ? 0.6 : maxPct > 25 ? 0.8 : 1.0;
|
| 146 |
+
return Math.round(((types / 4) * 0.4 + (Math.min(count, 7) / 7) * 0.4 + concentration * 0.2) * 100);
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
// ── Color Palette ─────────────────────────────────────────────────
|
| 150 |
+
const ASSET_COLORS = ['#22d3ee','#10b981','#8b5cf6','#f59e0b','#f43f5e','#6366f1','#0ea5e9','#34d399','#a78bfa','#fb923c'];
|
| 151 |
+
|
| 152 |
+
// ── Toast Notification ────────────────────────────────────────────
|
| 153 |
+
function showToast(msg, type = 'success') {
|
| 154 |
+
const t = document.createElement('div');
|
| 155 |
+
t.style.cssText = `
|
| 156 |
+
position:fixed; bottom:90px; right:20px; z-index:9999;
|
| 157 |
+
padding:12px 20px; border-radius:10px; font-size:13px; font-weight:600;
|
| 158 |
+
background:${type === 'success' ? 'rgba(16,185,129,0.95)' : 'rgba(244,63,94,0.95)'};
|
| 159 |
+
color:#fff; box-shadow:0 8px 32px rgba(0,0,0,0.4);
|
| 160 |
+
animation: fadeInUp 0.3s ease;
|
| 161 |
+
max-width: 300px;
|
| 162 |
+
`;
|
| 163 |
+
t.textContent = msg;
|
| 164 |
+
document.body.appendChild(t);
|
| 165 |
+
setTimeout(() => t.remove(), 3000);
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
// ── On DOM Ready ──────────────────────────────────────────────────
|
| 169 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 170 |
+
applyChartDefaults();
|
| 171 |
+
renderSidebarTickers();
|
| 172 |
+
});
|
tracker.html
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>FinWise — Investment Tracker</title>
|
| 7 |
+
<link rel="stylesheet" href="shared.css">
|
| 8 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
| 9 |
+
<style>
|
| 10 |
+
.tracker-header-bar {
|
| 11 |
+
display: flex;
|
| 12 |
+
align-items: center;
|
| 13 |
+
justify-content: space-between;
|
| 14 |
+
flex-wrap: wrap;
|
| 15 |
+
gap: 12px;
|
| 16 |
+
margin-bottom: 20px;
|
| 17 |
+
}
|
| 18 |
+
.holding-row td:first-child { font-weight: 700; }
|
| 19 |
+
.gain-cell { font-family: var(--font-mono); font-weight: 700; }
|
| 20 |
+
.ticker-cell {
|
| 21 |
+
display: flex;
|
| 22 |
+
align-items: center;
|
| 23 |
+
gap: 10px;
|
| 24 |
+
}
|
| 25 |
+
.ticker-logo {
|
| 26 |
+
width: 34px; height: 34px;
|
| 27 |
+
border-radius: 8px;
|
| 28 |
+
display: flex; align-items: center; justify-content: center;
|
| 29 |
+
font-size: 9px;
|
| 30 |
+
font-weight: 800;
|
| 31 |
+
font-family: var(--font-mono);
|
| 32 |
+
color: var(--bg);
|
| 33 |
+
flex-shrink: 0;
|
| 34 |
+
}
|
| 35 |
+
.ticker-name-sub { font-size: 11px; color: var(--text2); font-weight: 400; }
|
| 36 |
+
.mini-chart { width: 80px; height: 34px; }
|
| 37 |
+
.pnl-bar-mini { height: 3px; border-radius: 2px; margin-top: 4px; }
|
| 38 |
+
|
| 39 |
+
.edit-input {
|
| 40 |
+
background: transparent;
|
| 41 |
+
border: none;
|
| 42 |
+
border-bottom: 1px dashed var(--border2);
|
| 43 |
+
color: var(--text);
|
| 44 |
+
font-family: var(--font-mono);
|
| 45 |
+
font-size: 13px;
|
| 46 |
+
width: 80px;
|
| 47 |
+
padding: 2px 4px;
|
| 48 |
+
text-align: right;
|
| 49 |
+
outline: none;
|
| 50 |
+
}
|
| 51 |
+
.edit-input:focus { border-bottom-color: var(--cyan); }
|
| 52 |
+
|
| 53 |
+
.add-holding-form {
|
| 54 |
+
display: grid;
|
| 55 |
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
| 56 |
+
gap: 12px;
|
| 57 |
+
padding: 16px;
|
| 58 |
+
background: var(--bg3);
|
| 59 |
+
border-radius: var(--r-sm);
|
| 60 |
+
border: 1px dashed var(--border2);
|
| 61 |
+
margin-top: 16px;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.summary-strip {
|
| 65 |
+
display: grid;
|
| 66 |
+
grid-template-columns: repeat(auto-fit, minmax(140px,1fr));
|
| 67 |
+
gap: 12px;
|
| 68 |
+
margin-bottom: 20px;
|
| 69 |
+
}
|
| 70 |
+
.ss-item {
|
| 71 |
+
background: var(--card);
|
| 72 |
+
border: 1px solid var(--border);
|
| 73 |
+
border-radius: var(--r-sm);
|
| 74 |
+
padding: 14px 16px;
|
| 75 |
+
display: flex;
|
| 76 |
+
flex-direction: column;
|
| 77 |
+
gap: 4px;
|
| 78 |
+
}
|
| 79 |
+
.ss-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text3); font-weight: 700; }
|
| 80 |
+
.ss-val { font-family: var(--font-head); font-size: 20px; font-weight: 800; }
|
| 81 |
+
|
| 82 |
+
.period-tabs {
|
| 83 |
+
display: flex;
|
| 84 |
+
gap: 4px;
|
| 85 |
+
background: var(--bg3);
|
| 86 |
+
border-radius: 8px;
|
| 87 |
+
padding: 3px;
|
| 88 |
+
}
|
| 89 |
+
.period-tab {
|
| 90 |
+
padding: 6px 14px;
|
| 91 |
+
border-radius: 6px;
|
| 92 |
+
border: none;
|
| 93 |
+
background: transparent;
|
| 94 |
+
color: var(--text2);
|
| 95 |
+
font-size: 12px;
|
| 96 |
+
font-weight: 700;
|
| 97 |
+
cursor: pointer;
|
| 98 |
+
font-family: var(--font-body);
|
| 99 |
+
transition: all var(--transition);
|
| 100 |
+
}
|
| 101 |
+
.period-tab.active { background: var(--card); color: var(--cyan); }
|
| 102 |
+
|
| 103 |
+
.sort-header { cursor: pointer; user-select: none; white-space: nowrap; }
|
| 104 |
+
.sort-header:hover { color: var(--cyan); }
|
| 105 |
+
.sort-arrow { margin-left: 4px; opacity: 0.5; font-size: 10px; }
|
| 106 |
+
|
| 107 |
+
.delete-btn {
|
| 108 |
+
background: none;
|
| 109 |
+
border: none;
|
| 110 |
+
color: var(--text3);
|
| 111 |
+
cursor: pointer;
|
| 112 |
+
font-size: 16px;
|
| 113 |
+
padding: 4px;
|
| 114 |
+
border-radius: 4px;
|
| 115 |
+
transition: all var(--transition);
|
| 116 |
+
}
|
| 117 |
+
.delete-btn:hover { color: var(--rose); background: rgba(244,63,94,0.1); }
|
| 118 |
+
|
| 119 |
+
.watch-tag {
|
| 120 |
+
display: inline-flex;
|
| 121 |
+
align-items: center;
|
| 122 |
+
gap: 4px;
|
| 123 |
+
font-size: 10px;
|
| 124 |
+
font-weight: 700;
|
| 125 |
+
padding: 2px 8px;
|
| 126 |
+
border-radius: 100px;
|
| 127 |
+
text-transform: uppercase;
|
| 128 |
+
letter-spacing: 0.05em;
|
| 129 |
+
}
|
| 130 |
+
</style>
|
| 131 |
+
</head>
|
| 132 |
+
<body>
|
| 133 |
+
<div class="app-shell">
|
| 134 |
+
<nav class="sidebar">
|
| 135 |
+
<div class="sidebar-logo">
|
| 136 |
+
<div class="logo-mark">
|
| 137 |
+
<div class="logo-icon">📈</div>
|
| 138 |
+
<div><div class="logo-text">FinWise</div><div class="logo-sub">Smart Investing</div></div>
|
| 139 |
+
</div>
|
| 140 |
+
</div>
|
| 141 |
+
<div class="nav-section">
|
| 142 |
+
<div class="nav-label">Main</div>
|
| 143 |
+
<a href="index.html" class="nav-item"><span class="nav-icon">🏠</span> Dashboard</a>
|
| 144 |
+
<a href="portfolio.html" class="nav-item"><span class="nav-icon">📊</span> Portfolio Builder</a>
|
| 145 |
+
<a href="risk.html" class="nav-item"><span class="nav-icon">🎯</span> Risk Analyzer</a>
|
| 146 |
+
<a href="tracker.html" class="nav-item"><span class="nav-icon">📈</span> Tracker</a>
|
| 147 |
+
<div class="nav-label">Tools</div>
|
| 148 |
+
<a href="calculators.html" class="nav-item"><span class="nav-icon">🧮</span> Calculators</a>
|
| 149 |
+
<a href="insights.html" class="nav-item"><span class="nav-icon">💡</span> Insights</a>
|
| 150 |
+
</div>
|
| 151 |
+
<div class="sidebar-footer">
|
| 152 |
+
<div class="market-ticker">Live Market</div>
|
| 153 |
+
<div id="sidebar-tickers"></div>
|
| 154 |
+
</div>
|
| 155 |
+
</nav>
|
| 156 |
+
|
| 157 |
+
<main class="main-content">
|
| 158 |
+
<div class="page-header fade-in">
|
| 159 |
+
<div class="page-title">Investment <span>Tracker</span></div>
|
| 160 |
+
<div class="page-subtitle">Track your holdings, gains, and portfolio performance</div>
|
| 161 |
+
</div>
|
| 162 |
+
|
| 163 |
+
<!-- Summary Strip -->
|
| 164 |
+
<div class="summary-strip fade-in">
|
| 165 |
+
<div class="ss-item">
|
| 166 |
+
<div class="ss-label">Total Value</div>
|
| 167 |
+
<div class="ss-val" id="total-val" style="color:var(--cyan)">—</div>
|
| 168 |
+
</div>
|
| 169 |
+
<div class="ss-item">
|
| 170 |
+
<div class="ss-label">Total Cost</div>
|
| 171 |
+
<div class="ss-val" id="total-cost">—</div>
|
| 172 |
+
</div>
|
| 173 |
+
<div class="ss-item">
|
| 174 |
+
<div class="ss-label">Total Gain</div>
|
| 175 |
+
<div class="ss-val" id="total-gain">—</div>
|
| 176 |
+
</div>
|
| 177 |
+
<div class="ss-item">
|
| 178 |
+
<div class="ss-label">Gain %</div>
|
| 179 |
+
<div class="ss-val" id="total-gain-pct">—</div>
|
| 180 |
+
</div>
|
| 181 |
+
<div class="ss-item">
|
| 182 |
+
<div class="ss-label">Best Performer</div>
|
| 183 |
+
<div class="ss-val" id="best-performer" style="color:var(--emerald)">—</div>
|
| 184 |
+
</div>
|
| 185 |
+
<div class="ss-item">
|
| 186 |
+
<div class="ss-label">Holdings</div>
|
| 187 |
+
<div class="ss-val" id="holdings-count" style="color:var(--violet)">—</div>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
|
| 191 |
+
<!-- Performance Chart -->
|
| 192 |
+
<div class="card fade-in fade-in-1" style="margin-bottom:20px">
|
| 193 |
+
<div class="flex justify-between items-center" style="margin-bottom:16px">
|
| 194 |
+
<div class="card-title" style="margin-bottom:0">📈 Performance History</div>
|
| 195 |
+
<div class="period-tabs">
|
| 196 |
+
<button class="period-tab" onclick="setPeriod(7)">1W</button>
|
| 197 |
+
<button class="period-tab" onclick="setPeriod(30)">1M</button>
|
| 198 |
+
<button class="period-tab active" onclick="setPeriod(90)">3M</button>
|
| 199 |
+
<button class="period-tab" onclick="setPeriod(180)">6M</button>
|
| 200 |
+
<button class="period-tab" onclick="setPeriod(365)">1Y</button>
|
| 201 |
+
</div>
|
| 202 |
+
</div>
|
| 203 |
+
<div style="height:220px;position:relative">
|
| 204 |
+
<canvas id="perfChart"></canvas>
|
| 205 |
+
</div>
|
| 206 |
+
</div>
|
| 207 |
+
|
| 208 |
+
<!-- Holdings Table -->
|
| 209 |
+
<div class="card fade-in fade-in-2">
|
| 210 |
+
<div class="tracker-header-bar">
|
| 211 |
+
<div class="section-title" style="margin-bottom:0">📋 Holdings</div>
|
| 212 |
+
<div class="flex gap-8">
|
| 213 |
+
<input type="text" id="search-input" placeholder="🔍 Search ticker..." style="width:160px;padding:8px 12px;font-size:13px">
|
| 214 |
+
<button class="btn btn-primary btn-sm" onclick="toggleAddForm()">+ Add Holding</button>
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
|
| 218 |
+
<div class="table-wrap">
|
| 219 |
+
<table id="holdings-table">
|
| 220 |
+
<thead>
|
| 221 |
+
<tr>
|
| 222 |
+
<th>Asset</th>
|
| 223 |
+
<th class="sort-header" onclick="sortBy('currentPrice')">Price <span class="sort-arrow">↕</span></th>
|
| 224 |
+
<th class="sort-header" onclick="sortBy('shares')">Shares <span class="sort-arrow">↕</span></th>
|
| 225 |
+
<th class="sort-header" onclick="sortBy('totalValue')">Value <span class="sort-arrow">↕</span></th>
|
| 226 |
+
<th>Avg Cost</th>
|
| 227 |
+
<th class="sort-header" onclick="sortBy('gain')">Gain/Loss <span class="sort-arrow">↕</span></th>
|
| 228 |
+
<th class="sort-header" onclick="sortBy('gainPct')">Return <span class="sort-arrow">↕</span></th>
|
| 229 |
+
<th>Alloc</th>
|
| 230 |
+
<th></th>
|
| 231 |
+
</tr>
|
| 232 |
+
</thead>
|
| 233 |
+
<tbody id="holdings-body"></tbody>
|
| 234 |
+
</table>
|
| 235 |
+
</div>
|
| 236 |
+
|
| 237 |
+
<!-- Add Holding Form -->
|
| 238 |
+
<div class="add-holding-form hidden" id="add-form">
|
| 239 |
+
<div class="field-group" style="margin-bottom:0">
|
| 240 |
+
<label class="field-label">Ticker</label>
|
| 241 |
+
<select id="new-ticker" style="padding:9px 12px">
|
| 242 |
+
<option value="">Select…</option>
|
| 243 |
+
<option value="VOO">VOO — Vanguard S&P 500</option>
|
| 244 |
+
<option value="QQQ">QQQ — Nasdaq 100</option>
|
| 245 |
+
<option value="NVDA">NVDA — NVIDIA</option>
|
| 246 |
+
<option value="AAPL">AAPL — Apple</option>
|
| 247 |
+
<option value="AMZN">AMZN — Amazon</option>
|
| 248 |
+
<option value="TSLA">TSLA — Tesla</option>
|
| 249 |
+
<option value="BND">BND — Bond ETF</option>
|
| 250 |
+
<option value="GLD">GLD — Gold Trust</option>
|
| 251 |
+
<option value="WMT">WMT — Walmart</option>
|
| 252 |
+
<option value="MCD">MCD — McDonald's</option>
|
| 253 |
+
<option value="VTI">VTI — Total Market</option>
|
| 254 |
+
</select>
|
| 255 |
+
</div>
|
| 256 |
+
<div class="field-group" style="margin-bottom:0">
|
| 257 |
+
<label class="field-label">Shares</label>
|
| 258 |
+
<input type="number" id="new-shares" placeholder="e.g. 5" min="0.001" step="0.001" style="padding:9px 12px">
|
| 259 |
+
</div>
|
| 260 |
+
<div class="field-group" style="margin-bottom:0">
|
| 261 |
+
<label class="field-label">Avg Buy Price</label>
|
| 262 |
+
<input type="number" id="new-cost" placeholder="e.g. 450.00" min="0" step="0.01" style="padding:9px 12px">
|
| 263 |
+
</div>
|
| 264 |
+
<div style="display:flex;gap:8px;align-items:flex-end">
|
| 265 |
+
<button class="btn btn-emerald btn-full" onclick="addHolding()">Add</button>
|
| 266 |
+
<button class="btn btn-ghost" onclick="toggleAddForm()">✕</button>
|
| 267 |
+
</div>
|
| 268 |
+
</div>
|
| 269 |
+
</div>
|
| 270 |
+
|
| 271 |
+
<!-- Allocation Chart + Distribution -->
|
| 272 |
+
<div class="grid-2 fade-in fade-in-3" style="margin-top:20px">
|
| 273 |
+
<div class="card">
|
| 274 |
+
<div class="card-title">🥧 Portfolio Allocation</div>
|
| 275 |
+
<div style="height:200px;position:relative">
|
| 276 |
+
<canvas id="allocChart"></canvas>
|
| 277 |
+
</div>
|
| 278 |
+
</div>
|
| 279 |
+
<div class="card">
|
| 280 |
+
<div class="card-title">🏆 Top Performers</div>
|
| 281 |
+
<div id="top-performers"></div>
|
| 282 |
+
</div>
|
| 283 |
+
</div>
|
| 284 |
+
|
| 285 |
+
</main>
|
| 286 |
+
</div>
|
| 287 |
+
|
| 288 |
+
<nav class="bottom-nav">
|
| 289 |
+
<div class="bottom-nav-inner">
|
| 290 |
+
<a href="index.html" class="bottom-nav-item"><span class="bnav-icon">🏠</span>Home</a>
|
| 291 |
+
<a href="portfolio.html" class="bottom-nav-item"><span class="bnav-icon">📊</span>Portfolio</a>
|
| 292 |
+
<a href="risk.html" class="bottom-nav-item"><span class="bnav-icon">🎯</span>Risk</a>
|
| 293 |
+
<a href="tracker.html" class="bottom-nav-item"><span class="bnav-icon">📈</span>Track</a>
|
| 294 |
+
<a href="calculators.html" class="bottom-nav-item"><span class="bnav-icon">🧮</span>Calc</a>
|
| 295 |
+
<a href="insights.html" class="bottom-nav-item"><span class="bnav-icon">💡</span>Insights</a>
|
| 296 |
+
</div>
|
| 297 |
+
</nav>
|
| 298 |
+
|
| 299 |
+
<script src="shared.js"></script>
|
| 300 |
+
<script>
|
| 301 |
+
let holdings = [];
|
| 302 |
+
let sortField = 'totalValue';
|
| 303 |
+
let sortAsc = false;
|
| 304 |
+
let perfChart = null, allocChart = null;
|
| 305 |
+
let currentPeriod = 90;
|
| 306 |
+
|
| 307 |
+
const AVG_COSTS = { VOO:420, QQQ:390, NVDA:640, AAPL:170, BND:74, GLD:190, AMZN:165, VTI:210, TSLA:210, WMT:63, MCD:270 };
|
| 308 |
+
|
| 309 |
+
function initHoldings() {
|
| 310 |
+
const portfolio = getPortfolio();
|
| 311 |
+
holdings = portfolio.assets.map(a => ({
|
| 312 |
+
ticker: a.ticker,
|
| 313 |
+
name: a.name,
|
| 314 |
+
color: a.color,
|
| 315 |
+
type: a.type,
|
| 316 |
+
shares: a.shares,
|
| 317 |
+
currentPrice: MARKET_PRICES[a.ticker]?.price || a.price,
|
| 318 |
+
avgCost: AVG_COSTS[a.ticker] || a.price * 0.9,
|
| 319 |
+
}));
|
| 320 |
+
computeAndRender();
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
function computeAndRender() {
|
| 324 |
+
const totalVal = holdings.reduce((s,h) => s + h.shares * h.currentPrice, 0);
|
| 325 |
+
|
| 326 |
+
holdings.forEach(h => {
|
| 327 |
+
h.totalValue = h.shares * h.currentPrice;
|
| 328 |
+
h.totalCost = h.shares * h.avgCost;
|
| 329 |
+
h.gain = h.totalValue - h.totalCost;
|
| 330 |
+
h.gainPct = h.totalCost > 0 ? (h.gain / h.totalCost) * 100 : 0;
|
| 331 |
+
h.alloc = totalVal > 0 ? (h.totalValue / totalVal) * 100 : 0;
|
| 332 |
+
});
|
| 333 |
+
|
| 334 |
+
updateSummary(totalVal);
|
| 335 |
+
renderTable();
|
| 336 |
+
renderPerfChart(currentPeriod);
|
| 337 |
+
renderAllocChart();
|
| 338 |
+
renderTopPerformers();
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
function updateSummary(totalVal) {
|
| 342 |
+
const totalCost = holdings.reduce((s,h) => s + h.totalCost, 0);
|
| 343 |
+
const totalGain = totalVal - totalCost;
|
| 344 |
+
const gainPct = totalCost > 0 ? (totalGain/totalCost)*100 : 0;
|
| 345 |
+
const best = holdings.length > 0 ? holdings.reduce((m,h) => h.gainPct > m.gainPct ? h : m, holdings[0]) : null;
|
| 346 |
+
|
| 347 |
+
document.getElementById('total-val').textContent = fmt$(totalVal);
|
| 348 |
+
document.getElementById('total-cost').textContent = fmt$(totalCost);
|
| 349 |
+
const gainEl = document.getElementById('total-gain');
|
| 350 |
+
gainEl.textContent = (totalGain>=0?'+':'') + fmt$(totalGain);
|
| 351 |
+
gainEl.style.color = totalGain >= 0 ? 'var(--emerald)' : 'var(--rose)';
|
| 352 |
+
const gainPctEl = document.getElementById('total-gain-pct');
|
| 353 |
+
gainPctEl.textContent = fmtPct(gainPct);
|
| 354 |
+
gainPctEl.style.color = gainPct >= 0 ? 'var(--emerald)' : 'var(--rose)';
|
| 355 |
+
document.getElementById('best-performer').textContent = best ? best.ticker : '—';
|
| 356 |
+
document.getElementById('holdings-count').textContent = holdings.length;
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
function renderTable() {
|
| 360 |
+
const query = document.getElementById('search-input').value.toLowerCase();
|
| 361 |
+
let rows = [...holdings].filter(h => h.ticker.toLowerCase().includes(query) || h.name.toLowerCase().includes(query));
|
| 362 |
+
rows.sort((a,b) => sortAsc ? a[sortField]-b[sortField] : b[sortField]-a[sortField]);
|
| 363 |
+
|
| 364 |
+
const body = document.getElementById('holdings-body');
|
| 365 |
+
body.innerHTML = rows.map((h,i) => {
|
| 366 |
+
const mktData = MARKET_PRICES[h.ticker] || {};
|
| 367 |
+
const dayChg = mktData.changePct || 0;
|
| 368 |
+
const badgeClass = h.type==='ETF'?'badge-cyan':h.type==='Bond'?'badge-violet':h.type==='Commodity'?'badge-amber':'badge-emerald';
|
| 369 |
+
return `
|
| 370 |
+
<tr class="holding-row" id="row-${h.ticker}">
|
| 371 |
+
<td>
|
| 372 |
+
<div class="ticker-cell">
|
| 373 |
+
<div class="ticker-logo" style="background:${h.color}">${h.ticker}</div>
|
| 374 |
+
<div>
|
| 375 |
+
<div style="font-weight:700">${h.ticker} <span class="badge ${badgeClass}" style="font-size:9px">${h.type}</span></div>
|
| 376 |
+
<div class="ticker-name-sub">${h.name}</div>
|
| 377 |
+
<div style="font-size:11px;margin-top:2px;color:${dayChg>=0?'var(--emerald)':'var(--rose)'}">${dayChg>=0?'▲':'▼'} ${Math.abs(dayChg).toFixed(2)}% today</div>
|
| 378 |
+
</div>
|
| 379 |
+
</div>
|
| 380 |
+
</td>
|
| 381 |
+
<td class="mono">${fmt$(h.currentPrice)}</td>
|
| 382 |
+
<td class="mono">
|
| 383 |
+
<input class="edit-input" type="number" value="${h.shares.toFixed(3)}" step="0.001" min="0"
|
| 384 |
+
onchange="updateShares('${h.ticker}', this.value)">
|
| 385 |
+
</td>
|
| 386 |
+
<td class="mono bold">${fmt$(h.totalValue)}</td>
|
| 387 |
+
<td class="mono">
|
| 388 |
+
<input class="edit-input" type="number" value="${h.avgCost.toFixed(2)}" step="0.01" min="0"
|
| 389 |
+
onchange="updateAvgCost('${h.ticker}', this.value)">
|
| 390 |
+
</td>
|
| 391 |
+
<td class="gain-cell ${h.gain>=0?'up':'down'}">${h.gain>=0?'+':''}${fmt$(h.gain)}</td>
|
| 392 |
+
<td class="gain-cell ${h.gainPct>=0?'up':'down'}">${fmtPct(h.gainPct)}</td>
|
| 393 |
+
<td style="min-width:90px">
|
| 394 |
+
<div style="font-size:12px;font-family:var(--font-mono)">${h.alloc.toFixed(1)}%</div>
|
| 395 |
+
<div class="pnl-bar-mini" style="width:${Math.min(h.alloc,100)}%;background:${h.color}"></div>
|
| 396 |
+
</td>
|
| 397 |
+
<td><button class="delete-btn" onclick="removeHolding('${h.ticker}')">🗑</button></td>
|
| 398 |
+
</tr>
|
| 399 |
+
`;
|
| 400 |
+
}).join('');
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
function updateShares(ticker, val) {
|
| 404 |
+
const h = holdings.find(h=>h.ticker===ticker);
|
| 405 |
+
if (h) { h.shares = parseFloat(val)||0; computeAndRender(); }
|
| 406 |
+
}
|
| 407 |
+
function updateAvgCost(ticker, val) {
|
| 408 |
+
const h = holdings.find(h=>h.ticker===ticker);
|
| 409 |
+
if (h) { h.avgCost = parseFloat(val)||0; computeAndRender(); }
|
| 410 |
+
}
|
| 411 |
+
function removeHolding(ticker) {
|
| 412 |
+
holdings = holdings.filter(h=>h.ticker!==ticker);
|
| 413 |
+
computeAndRender();
|
| 414 |
+
showToast(`${ticker} removed from tracker`);
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
function sortBy(field) {
|
| 418 |
+
if (sortField === field) sortAsc = !sortAsc;
|
| 419 |
+
else { sortField = field; sortAsc = false; }
|
| 420 |
+
renderTable();
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
function setPeriod(days) {
|
| 424 |
+
currentPeriod = days;
|
| 425 |
+
document.querySelectorAll('.period-tab').forEach(b => b.classList.remove('active'));
|
| 426 |
+
event.target.classList.add('active');
|
| 427 |
+
renderPerfChart(days);
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
function renderPerfChart(days) {
|
| 431 |
+
const totalCost = holdings.reduce((s,h)=>s+h.totalCost,0) || 10000;
|
| 432 |
+
const history = generateHistory(days, totalCost);
|
| 433 |
+
const ctx = document.getElementById('perfChart').getContext('2d');
|
| 434 |
+
if (perfChart) perfChart.destroy();
|
| 435 |
+
|
| 436 |
+
const labels = history.filter((_,i)=> days<=30 ? i%2===0 : days<=90 ? i%6===0 : i%14===0).map(d=>d.date);
|
| 437 |
+
const values = history.filter((_,i)=> days<=30 ? i%2===0 : days<=90 ? i%6===0 : i%14===0).map(d=>d.value);
|
| 438 |
+
const isUp = values[values.length-1] >= values[0];
|
| 439 |
+
|
| 440 |
+
const grad = ctx.createLinearGradient(0,0,0,220);
|
| 441 |
+
grad.addColorStop(0, isUp ? 'rgba(16,185,129,0.25)' : 'rgba(244,63,94,0.25)');
|
| 442 |
+
grad.addColorStop(1, 'rgba(0,0,0,0)');
|
| 443 |
+
|
| 444 |
+
perfChart = new Chart(ctx, {
|
| 445 |
+
type:'line',
|
| 446 |
+
data: { labels, datasets:[{
|
| 447 |
+
data: values,
|
| 448 |
+
borderColor: isUp ? '#10b981' : '#f43f5e',
|
| 449 |
+
backgroundColor: grad,
|
| 450 |
+
borderWidth: 2.5,
|
| 451 |
+
fill: true,
|
| 452 |
+
tension: 0.4,
|
| 453 |
+
pointRadius: 0,
|
| 454 |
+
pointHoverRadius: 5,
|
| 455 |
+
}]},
|
| 456 |
+
options: {
|
| 457 |
+
responsive:true, maintainAspectRatio:false,
|
| 458 |
+
plugins:{ legend:{display:false}, tooltip:{ callbacks:{ label: ctx => ' '+fmt$(ctx.raw) } } },
|
| 459 |
+
scales:{
|
| 460 |
+
x:{ grid:{display:false}, ticks:{font:{size:11}} },
|
| 461 |
+
y:{ grid:{color:'rgba(34,211,238,0.06)'}, ticks:{ callback: v => '$'+(v/1000).toFixed(0)+'K', font:{size:11} } }
|
| 462 |
+
},
|
| 463 |
+
interaction:{ intersect:false, mode:'index' }
|
| 464 |
+
}
|
| 465 |
+
});
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
function renderAllocChart() {
|
| 469 |
+
const ctx = document.getElementById('allocChart').getContext('2d');
|
| 470 |
+
if (allocChart) allocChart.destroy();
|
| 471 |
+
allocChart = new Chart(ctx, {
|
| 472 |
+
type:'doughnut',
|
| 473 |
+
data:{
|
| 474 |
+
labels: holdings.map(h=>h.ticker),
|
| 475 |
+
datasets:[{
|
| 476 |
+
data: holdings.map(h=>h.alloc),
|
| 477 |
+
backgroundColor: holdings.map(h=>h.color),
|
| 478 |
+
borderColor:'rgba(5,13,26,0.8)',
|
| 479 |
+
borderWidth:3,
|
| 480 |
+
hoverOffset:6
|
| 481 |
+
}]
|
| 482 |
+
},
|
| 483 |
+
options:{
|
| 484 |
+
responsive:true, maintainAspectRatio:false,
|
| 485 |
+
cutout:'68%',
|
| 486 |
+
plugins:{ legend:{ position:'right', labels:{ color:'#8faac8', font:{size:11}, boxWidth:12 } } }
|
| 487 |
+
}
|
| 488 |
+
});
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
+
function renderTopPerformers() {
|
| 492 |
+
const sorted = [...holdings].sort((a,b)=>b.gainPct-a.gainPct);
|
| 493 |
+
document.getElementById('top-performers').innerHTML = sorted.slice(0,5).map((h,i) => `
|
| 494 |
+
<div style="display:flex;align-items:center;gap:12px;padding:10px 0;border-bottom:1px solid rgba(34,211,238,0.06)">
|
| 495 |
+
<div style="font-size:16px">${i===0?'🥇':i===1?'🥈':i===2?'🥉':'📊'}</div>
|
| 496 |
+
<div class="ticker-logo" style="background:${h.color};width:30px;height:30px;font-size:8px">${h.ticker}</div>
|
| 497 |
+
<div style="flex:1">
|
| 498 |
+
<div style="font-weight:700;font-size:14px">${h.ticker}</div>
|
| 499 |
+
<div style="font-size:11px;color:var(--text2)">${fmt$(h.totalValue)}</div>
|
| 500 |
+
</div>
|
| 501 |
+
<div style="font-family:var(--font-mono);font-weight:700;color:${h.gainPct>=0?'var(--emerald)':'var(--rose)'}">
|
| 502 |
+
${fmtPct(h.gainPct)}
|
| 503 |
+
</div>
|
| 504 |
+
</div>
|
| 505 |
+
`).join('');
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
function toggleAddForm() {
|
| 509 |
+
document.getElementById('add-form').classList.toggle('hidden');
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
function addHolding() {
|
| 513 |
+
const ticker = document.getElementById('new-ticker').value;
|
| 514 |
+
const shares = parseFloat(document.getElementById('new-shares').value);
|
| 515 |
+
const cost = parseFloat(document.getElementById('new-cost').value);
|
| 516 |
+
if (!ticker || !shares || !cost) { showToast('Please fill all fields', 'error'); return; }
|
| 517 |
+
if (holdings.find(h=>h.ticker===ticker)) { showToast('Already in portfolio', 'error'); return; }
|
| 518 |
+
|
| 519 |
+
const mkt = MARKET_PRICES[ticker] || { price: cost };
|
| 520 |
+
const assetDef = { VOO:'Vanguard S&P 500 ETF', QQQ:'Invesco Nasdaq 100', NVDA:'NVIDIA Corp', AAPL:'Apple Inc.',
|
| 521 |
+
AMZN:'Amazon.com Inc.', TSLA:'Tesla Inc.', BND:'Vanguard Bond ETF', GLD:'SPDR Gold Trust',
|
| 522 |
+
WMT:'Walmart Inc.', MCD:"McDonald's Corp", VTI:'Vanguard Total Market' };
|
| 523 |
+
const typeMap = { VOO:'ETF', VTI:'ETF', QQQ:'ETF', BND:'Bond', GLD:'Commodity', SLV:'Commodity' };
|
| 524 |
+
|
| 525 |
+
holdings.push({
|
| 526 |
+
ticker,
|
| 527 |
+
name: assetDef[ticker] || ticker,
|
| 528 |
+
color: ASSET_COLORS[holdings.length % ASSET_COLORS.length],
|
| 529 |
+
type: typeMap[ticker] || 'Stock',
|
| 530 |
+
shares,
|
| 531 |
+
currentPrice: mkt.price,
|
| 532 |
+
avgCost: cost,
|
| 533 |
+
});
|
| 534 |
+
|
| 535 |
+
document.getElementById('new-ticker').value = '';
|
| 536 |
+
document.getElementById('new-shares').value = '';
|
| 537 |
+
document.getElementById('new-cost').value = '';
|
| 538 |
+
toggleAddForm();
|
| 539 |
+
computeAndRender();
|
| 540 |
+
showToast(`✅ ${ticker} added to tracker`);
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
document.getElementById('search-input').addEventListener('input', renderTable);
|
| 544 |
+
|
| 545 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 546 |
+
applyChartDefaults();
|
| 547 |
+
initHoldings();
|
| 548 |
+
});
|
| 549 |
+
</script>
|
| 550 |
+
</body>
|
| 551 |
+
</html>
|