AndyKandy26 commited on
Commit
b2e988f
·
verified ·
1 Parent(s): 3fac8c0

Delete portfolio.html

Browse files
Files changed (1) hide show
  1. portfolio.html +0 -673
portfolio.html DELETED
@@ -1,673 +0,0 @@
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>