Gonçalo Rodrigues 6acea3da31 feat(finance): inline help tips + guided empty states
- Global .help-tip / .help-popup CSS + click-toggle JS in base.html
- Global .setup-steps / .setup-step CSS for step-by-step guidance
- Dashboard: ? tooltips on Free Cash (formula), Savings Rate, Net Worth
- Goals: ? tooltips on Monthly Amount, At Current Rate, Free Cash After
- Goals empty state: 3-step guide (planner → commit → fund)
- Transactions empty state: 3-step guide (account → import → tag)
  with prominent Import / Add buttons replacing the inline text links

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 22:43:59 +01:00

474 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{{define "content"}}
{{$d := .}}
<div style="display:flex; align-items:baseline; justify-content:space-between; margin-bottom:24px; flex-wrap:wrap; gap:8px;">
<h1>{{$d.T.Get "dashboard.title"}}</h1>
<span class="text-muted">{{if $d.Email}}{{$d.Email}}{{end}}</span>
</div>
{{if $d.Alerts}}
<div style="display:flex; flex-direction:column; gap:8px; margin-bottom:16px;">
{{range $d.Alerts}}
<div style="display:flex; align-items:flex-start; gap:10px; padding:12px 16px; border-radius:10px; font-size:13px;
{{if eq .Level "danger"}}background:rgba(248,113,113,0.08); border:1px solid rgba(248,113,113,0.25); color:var(--red);
{{else if eq .Level "warn"}}background:rgba(245,158,11,0.08); border:1px solid rgba(245,158,11,0.25); color:#f59e0b;
{{else}}background:rgba(105,121,248,0.08); border:1px solid rgba(105,121,248,0.25); color:var(--accent);{{end}}">
<span style="flex-shrink:0; font-size:15px;">{{if eq .Level "danger"}}🔴{{else if eq .Level "warn"}}⚠{{else}}{{end}}</span>
<span>{{.Message}}</span>
</div>
{{end}}
</div>
{{end}}
<!-- HERO: interactive cash flow waterfall -->
<div class="card animate-on-scroll" style="margin-bottom:16px; padding:24px 28px;">
<div style="font-size:12px; color:var(--text2); text-transform:uppercase; letter-spacing:.5px; margin-bottom:4px;">
{{$d.T.Get "dashboard.waterfall.title"}}
</div>
<!-- Income row -->
{{if $d.IncomeCats}}
<div class="wf-section">
<button class="wf-row" onclick="wfToggle('income')" aria-expanded="false">
<span class="wf-chevron" id="wf-chev-income"></span>
<span class="wf-row-label">{{$d.T.Get "dashboard.waterfall.income"}}</span>
<span class="wf-row-amt positive">+€{{cents $d.WaterfallIncome}}</span>
</button>
<div class="wf-detail" id="wf-income" style="display:none;">
{{range $i, $row := $d.IncomeCats}}
<div class="wf-cat-section">
<button class="wf-cat-row" onclick="wfToggleCat('ic{{$i}}')">
{{if $row.Color}}<span class="cat-dot" style="background:{{$row.Color}};"></span>{{end}}
<span style="flex:1; font-size:12px;">{{$row.Name}}</span>
<span class="wf-cat-chev" id="wf-cc-ic{{$i}}"></span>
<span style="font-size:12px; font-weight:500; color:var(--green);">+€{{cents $row.Cents}}</span>
</button>
<div class="wf-txn-list" id="wf-ic{{$i}}" style="display:none;">
{{range index $d.IncomeCatTxns $row.Name}}
<div class="wf-txn-row">
<span class="wf-txn-date">{{dateShort .Date}}</span>
<span class="wf-txn-desc">{{.Description}}</span>
<span class="wf-txn-amt positive">+€{{cents .AmountCents}}</span>
</div>
{{end}}
</div>
</div>
{{end}}
</div>
</div>
{{else}}
<div class="wf-row" style="cursor:default;">
<span class="wf-row-label" style="padding-left:20px;">{{$d.T.Get "dashboard.waterfall.income"}}</span>
<span class="wf-row-amt positive">+€{{cents $d.WaterfallIncome}}</span>
</div>
{{end}}
<!-- Living expenses row -->
{{if $d.LivingCats}}
<div class="wf-section">
<button class="wf-row" onclick="wfToggle('living')" aria-expanded="false">
<span class="wf-chevron" id="wf-chev-living"></span>
<span class="wf-row-label">{{$d.T.Get "dashboard.waterfall.living"}}</span>
<span class="wf-row-amt" style="color:var(--red);">−€{{cents $d.WaterfallLiving}}</span>
</button>
<div class="wf-detail" id="wf-living" style="display:none;">
{{range $i, $row := $d.LivingCats}}
<div class="wf-cat-section">
<button class="wf-cat-row" onclick="wfToggleCat('lc{{$i}}')">
{{if $row.Color}}<span class="cat-dot" style="background:{{$row.Color}};"></span>{{end}}
<span style="flex:1; font-size:12px;">{{$row.Name}}</span>
<span class="wf-cat-chev" id="wf-cc-lc{{$i}}"></span>
<span style="font-size:12px; font-weight:500; color:var(--red);">−€{{cents $row.Cents}}</span>
</button>
<div class="wf-txn-list" id="wf-lc{{$i}}" style="display:none;">
{{range index $d.LivingCatTxns $row.Name}}
<div class="wf-txn-row">
<span class="wf-txn-date">{{dateShort .Date}}</span>
<span class="wf-txn-desc">{{.Description}}</span>
<span class="wf-txn-amt" style="color:var(--red);">−€{{cents (abs .AmountCents)}}</span>
</div>
{{end}}
</div>
</div>
{{end}}
</div>
</div>
{{else if gt $d.WaterfallLiving 0}}
<div class="wf-row" style="cursor:default;">
<span class="wf-row-label" style="padding-left:20px;">{{$d.T.Get "dashboard.waterfall.living"}}</span>
<span class="wf-row-amt" style="color:var(--red);">−€{{cents $d.WaterfallLiving}}</span>
</div>
{{end}}
<!-- Goal contributions row -->
{{if gt $d.WaterfallGoals 0}}
<div class="wf-section">
{{if $d.DashGoals}}
<button class="wf-row" onclick="wfToggle('goals')" aria-expanded="false">
<span class="wf-chevron" id="wf-chev-goals"></span>
<span class="wf-row-label">{{$d.T.Get "dashboard.waterfall.goals"}}</span>
<span class="wf-row-amt" style="color:var(--accent);">−€{{cents $d.WaterfallGoals}}</span>
</button>
<div class="wf-detail" id="wf-goals" style="display:none;">
{{range $d.DashGoals}}
{{$funded := index $d.GoalFundedThisMonth .ID}}
{{if gt $funded 0}}
<div class="wf-cat-row" style="cursor:default;">
<span style="font-size:11px;">{{if eq .Type "once"}}🎯{{else if eq .Type "deposit"}}🏠{{else if eq .Type "emergency"}}🛡️{{else}}📈{{end}}</span>
<span style="flex:1; font-size:12px;">{{.Name}}</span>
<a href="/goals" style="font-size:11px; color:var(--accent); text-decoration:none; margin-right:8px;">{{$d.T.Get "dashboard.waterfall.goals_link"}}</a>
<span style="font-size:12px; font-weight:500; color:var(--accent);">−€{{cents $funded}}</span>
</div>
{{end}}
{{end}}
</div>
{{else}}
<div class="wf-row" style="cursor:default;">
<span class="wf-row-label" style="padding-left:20px; display:flex; align-items:center; gap:8px;">
{{$d.T.Get "dashboard.waterfall.goals"}}
<a href="/goals" style="font-size:11px; color:var(--accent); text-decoration:none;">{{$d.T.Get "dashboard.waterfall.goals_link"}}</a>
</span>
<span class="wf-row-amt" style="color:var(--accent);">−€{{cents $d.WaterfallGoals}}</span>
</div>
{{end}}
</div>
{{end}}
<!-- Free cash total -->
<div style="display:flex; justify-content:space-between; align-items:center; padding:14px 0 0 0; margin-top:4px; border-top:1px solid var(--border);">
<span style="font-size:14px; font-weight:600; color:var(--text);">{{$d.T.Get "dashboard.waterfall.free_cash"}}<span class="help-tip">?<div class="help-popup"><strong>Free Cash</strong>What's left after paying for life and funding your goals. Positive means you have room to save or spend more.<code>Income Living expenses Goal contributions</code></div></span></span>
<div class="animate-counter {{if lt $d.WaterfallFreeCash 0}}negative{{else}}positive{{end}}"
style="font-size:32px; font-weight:500; letter-spacing:-1px; line-height:1;"
data-target="{{$d.WaterfallFreeCash}}" data-prefix="€">€0</div>
</div>
<!-- Month progress bar -->
<div style="margin-top:16px; padding-top:14px; border-top:1px solid var(--border);">
<div style="display:flex; justify-content:space-between; font-size:11px; color:var(--text3); margin-bottom:5px;">
<span>{{$d.T.Get "dashboard.waterfall.month_progress"}}</span>
<span>{{$d.MonthProgressPct}}%</span>
</div>
<div style="background:var(--bg3); border-radius:99px; height:4px; overflow:hidden;">
<div style="height:100%; border-radius:99px; width:{{$d.MonthProgressPct}}%;
background:var(--text3); transition:width 1s ease;"></div>
</div>
</div>
<!-- What now? prompt — underfunded committed goals -->
{{if $d.DashGoals}}
<div style="margin-top:16px; padding-top:14px; border-top:1px solid var(--border);">
<div style="font-size:11px; font-weight:700; color:var(--muted); text-transform:uppercase; letter-spacing:0.06em; margin-bottom:8px;">{{$d.T.Get "dashboard.waterfall.what_now"}}</div>
<div id="wf-what-now-list">
{{range $d.DashGoals}}
{{if .Committed}}
{{$funded := index $d.GoalFundedThisMonth .ID}}
{{$needed := .MonthlyCents}}
{{if lt $funded $needed}}
{{$shortfall := sub $needed $funded}}
<div style="display:flex; justify-content:space-between; align-items:center; padding:5px 0; font-size:13px;">
<span style="color:var(--text2);">{{.Name}} <span style="color:var(--red);">−€{{cents $shortfall}}</span></span>
<a href="/transactions?fund_goal={{.ID}}" style="font-size:12px; color:var(--accent); text-decoration:none; font-weight:600;">{{$d.T.Get "dashboard.waterfall.fund_link"}} →</a>
</div>
{{end}}
{{end}}
{{end}}
</div>
<div id="wf-all-funded" style="display:none; font-size:13px; color:var(--green);">{{$d.T.Get "dashboard.waterfall.all_funded"}}</div>
</div>
{{end}}
</div>
<style>
.wf-section { border-bottom: 1px solid var(--border); }
.wf-section:last-of-type { border-bottom: none; }
.wf-row {
display: flex; align-items: center; gap: 8px;
width: 100%; padding: 10px 0;
background: none; border: none; cursor: pointer; text-align: left;
color: var(--text);
}
.wf-row:hover .wf-row-label { color: var(--text); }
.wf-chevron {
font-size: 14px; color: var(--text3); width: 14px; flex-shrink: 0;
transition: transform 0.18s ease; display: inline-block;
transform-origin: center 45%;
}
.wf-chevron.open { transform: rotate(90deg); }
.wf-row-label { flex: 1; font-size: 13px; color: var(--text2); }
.wf-row-amt { font-size: 15px; font-weight: 600; white-space: nowrap; }
.wf-detail { padding: 4px 0 8px 22px; }
.wf-cat-section { }
.wf-cat-row {
display: flex; align-items: center; gap: 7px;
width: 100%; padding: 6px 0;
background: none; border: none; cursor: pointer; text-align: left;
color: var(--text2);
}
.wf-cat-chev {
font-size: 11px; color: var(--text3); width: 10px;
transition: transform 0.15s ease; display: inline-block;
transform-origin: center 45%;
}
.wf-cat-chev.open { transform: rotate(90deg); }
.cat-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.wf-txn-list { padding: 2px 0 6px 14px; }
.wf-txn-row {
display: flex; align-items: center; gap: 8px;
padding: 5px 0; border-bottom: 1px solid var(--border);
font-size: 12px;
}
.wf-txn-row:last-child { border-bottom: none; }
.wf-txn-date { color: var(--text3); white-space: nowrap; min-width: 44px; }
.wf-txn-desc { flex: 1; color: var(--text2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wf-txn-amt { white-space: nowrap; font-weight: 500; }
</style>
<script>
function wfToggle(id) {
const detail = document.getElementById('wf-' + id);
const chev = document.getElementById('wf-chev-' + id);
const open = detail.style.display !== 'none';
detail.style.display = open ? 'none' : 'block';
if (chev) chev.classList.toggle('open', !open);
}
function wfToggleCat(id) {
const list = document.getElementById('wf-' + id);
const chev = document.getElementById('wf-cc-' + id);
const open = list.style.display !== 'none';
list.style.display = open ? 'none' : 'block';
if (chev) chev.classList.toggle('open', !open);
}
(function() {
const list = document.getElementById('wf-what-now-list');
const allFunded = document.getElementById('wf-all-funded');
if (list && allFunded && !list.children.length) allFunded.style.display = '';
})();
</script>
<!-- 3 diagnostic cards -->
<div class="grid" style="margin-bottom:16px;">
<div class="card value-card animate-on-scroll">
<h2>{{$d.T.Get "dashboard.cards.savings_rate"}}<span class="help-tip">?<div class="help-popup"><strong>Savings Rate</strong>The share of your income you're keeping. Above 20% is healthy; below 0% means you spent more than you earned.<code>(Income Expenses) ÷ Income × 100</code></div></span></h2>
<div class="value {{if gt $d.SavingsRatePct 0}}positive{{else}}negative{{end}}">{{$d.SavingsRatePct}}%</div>
{{if $d.LastMonthSavingsRatePct}}
<p style="font-size:12px; margin-top:6px; {{if gt $d.SavingsRatePct $d.LastMonthSavingsRatePct}}color:var(--green){{else}}color:var(--red){{end}};">
{{if gt $d.SavingsRatePct $d.LastMonthSavingsRatePct}}↑{{else}}↓{{end}} {{$d.T.Get "dashboard.alerts.vs_last_month_up"}} ({{$d.LastMonthSavingsRatePct}}%)
</p>
{{end}}
</div>
<div class="card value-card animate-on-scroll">
<h2>{{$d.T.Get "dashboard.cards.net_worth"}}<span class="help-tip">?<div class="help-popup"><strong>Net Worth</strong>Everything you own minus everything you owe. Tracking this monthly is the single best measure of financial health.<code>Cash + Investments + Property equity Debts</code></div></span></h2>
<div class="value animate-counter {{if lt $d.NetWorthCents 0}}negative{{else}}positive{{end}}"
data-target="{{$d.NetWorthCents}}" data-prefix="€">€0.00</div>
<p style="font-size:12px; color:var(--text3); margin-top:6px;"><a href="/networth" style="color:var(--accent);">{{$d.T.Get "dashboard.cards.net_worth_link"}}</a></p>
</div>
<div class="card value-card animate-on-scroll">
<h2>{{$d.T.Get "dashboard.cards.portfolio_today"}}</h2>
{{if $d.PortfolioHoldings}}
<div class="value animate-counter" data-target="{{$d.PortfolioValueCents}}" data-prefix="€" style="color:var(--text);">€0.00</div>
{{if $d.PortfolioPricesAvailable}}
<p style="font-size:12px; margin-top:6px; {{if ge $d.PortfolioPCLCents 0}}color:var(--green){{else}}color:var(--red){{end}};">
{{if ge $d.PortfolioPCLCents 0}}+{{else}}{{end}}€{{cents (centsAbs $d.PortfolioPCLCents)}} total P&L
</p>
{{else}}
<p style="font-size:12px; color:var(--text3); margin-top:6px;">{{$d.T.Get "dashboard.cards.portfolio_cost_basis"}}</p>
{{end}}
{{else}}
<div class="value" style="color:var(--text3); font-size:18px;">{{$d.T.Get "dashboard.cards.portfolio_no_trades"}}</div>
<p style="font-size:12px; color:var(--text3); margin-top:6px;"><a href="/import" style="color:var(--accent);">{{$d.T.Get "dashboard.cards.portfolio_import_link"}}</a></p>
{{end}}
</div>
</div>
<!-- Stocks + spending breakdown -->
<div class="grid-2" style="margin-bottom:16px;">
<div class="card animate-on-scroll">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
<h2>{{$d.T.Get "dashboard.stocks.section_title"}}</h2>
<a href="/portfolio" style="font-size:12px; color:var(--text3);">{{$d.T.Get "dashboard.stocks.portfolio_link"}}</a>
</div>
{{if $d.PortfolioHoldings}}
<div style="display:flex; flex-direction:column;">
{{range $d.PortfolioHoldings}}
<div style="display:flex; align-items:center; justify-content:space-between; padding:9px 0; border-bottom:1px solid var(--border);">
<div>
<div style="font-size:13px; font-weight:600; color:var(--text);">{{.Name}}</div>
<div style="font-size:11px; color:var(--text3);">{{printf "%.4f" .SharesOwned}} {{$d.T.Get "dashboard.stocks.shares_label"}}</div>
</div>
<div style="text-align:right;">
{{if $d.PortfolioPricesAvailable}}
<div style="font-size:13px; font-weight:500; color:var(--text);">€{{cents .CurrentValueCents}}</div>
<div style="font-size:12px; {{if ge .UnrealizedPCLCents 0}}color:var(--green){{else}}color:var(--red){{end}};">
{{pctSign .UnrealizedPCLPct}}{{printf "%.1f" .UnrealizedPCLPct}}%
</div>
{{else}}
<div style="font-size:13px; font-weight:500; color:var(--text);">€{{cents .TotalCostCents}}</div>
<div style="font-size:11px; color:var(--text3);">{{$d.T.Get "dashboard.stocks.cost_basis"}}</div>
{{end}}
</div>
</div>
{{end}}
<div style="display:flex; justify-content:space-between; align-items:center; padding:12px 0 0 0;">
<span style="font-size:13px; font-weight:500; color:var(--text);">{{$d.T.Get "dashboard.stocks.total_label"}}{{if not $d.PortfolioPricesAvailable}} {{$d.T.Get "dashboard.stocks.total_invested"}}{{end}}</span>
<span class="animate-counter" style="font-size:15px; font-weight:600; color:var(--text);"
data-target="{{$d.PortfolioValueCents}}" data-prefix="€">€0.00</span>
</div>
</div>
{{else}}
<div class="empty-state" style="padding:24px;">
<p>{{$d.T.Get "dashboard.stocks.no_holdings_msg"}}<br><a href="/import" style="color:var(--accent);">{{$d.T.Get "dashboard.stocks.import_link"}}</a></p>
</div>
{{end}}
</div>
</div>
<!-- Budget health + recent activity -->
<div class="grid-2">
{{if $d.CategoryBudgets}}
<div class="card animate-on-scroll">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
<h2>{{$d.T.Get "dashboard.budget_health.section_title"}}</h2>
<a href="/categories" style="font-size:12px; color:var(--text3);">{{$d.T.Get "dashboard.budget_health.categories_link"}}</a>
</div>
<div style="display:flex; flex-direction:column; gap:10px;">
{{range $cat, $budget := $d.CategoryBudgets}}
{{$spent := index $d.ThisMonth.ByCategory $cat}}
{{$spentAbs := centsAbs $spent}}
{{$color := index $d.CategoryColors $cat}}
{{$over := isOver $spentAbs $budget}}
{{$pct := clampPct $spentAbs $budget}}
<div>
<div style="display:flex; justify-content:space-between; margin-bottom:5px;">
<span style="font-size:12px; color:var(--text2); display:flex; align-items:center; gap:6px;">
{{if $color}}<span style="width:7px;height:7px;border-radius:50%;background:{{$color}};display:inline-block;"></span>{{end}}
{{$cat}}
</span>
<span style="font-size:11px; {{if $over}}color:var(--red); font-weight:600;{{else}}color:var(--text3);{{end}}">
{{$pct}}%{{if $over}} ⚠{{end}}
</span>
</div>
<div style="background:var(--bg3); border-radius:99px; height:5px; overflow:hidden;">
<div style="height:100%; border-radius:99px; width:{{$pct}}%;
background:{{if $over}}var(--red){{else if $color}}{{$color}}{{else}}var(--accent){{end}};
transition:width 1s ease;"></div>
</div>
</div>
{{end}}
</div>
</div>
{{end}}
<div class="card animate-on-scroll">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
<h2>{{$d.T.Get "dashboard.recent.section_title"}}</h2>
<a href="/transactions" style="font-size:12px; color:var(--text3);">{{$d.T.Get "dashboard.recent.all_txns_link"}}</a>
</div>
{{if $d.RecentTxns}}
<div style="display:flex; flex-direction:column;">
{{range $d.RecentTxns}}
{{$color := index $d.CategoryColors .Category}}
<div style="display:flex; align-items:center; justify-content:space-between; padding:9px 0; border-bottom:1px solid var(--border);">
<div style="display:flex; align-items:center; gap:10px; min-width:0;">
<div style="width:8px; height:8px; border-radius:50%; flex-shrink:0;
background:{{if $color}}{{$color}}{{else}}var(--text3){{end}};"></div>
<div style="min-width:0;">
<div style="font-size:13px; color:var(--text); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:180px;">{{.Description}}</div>
<div style="font-size:11px; color:var(--text3);">{{dateShort .Date}}</div>
</div>
</div>
<div style="font-size:13px; font-weight:500; white-space:nowrap; margin-left:12px;
{{if lt .AmountCents 0}}color:var(--red){{else}}color:var(--green){{end}};">
{{if lt .AmountCents 0}}{{else}}+{{end}}€{{cents (centsAbs .AmountCents)}}
</div>
</div>
{{end}}
</div>
{{else}}
<div class="empty-state" style="padding:24px;">
{{$d.T.Get "dashboard.recent.no_txns_msg"}} <a href="/import" style="color:var(--accent);">{{$d.T.Get "dashboard.recent.import_link"}}</a>
</div>
{{end}}
</div>
</div>
{{if $d.DashGoals}}
<div class="card animate-on-scroll" style="margin-top:16px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
<h2>{{$d.T.Get "dashboard.goals.section_title"}}</h2>
<a href="/goals" style="font-size:12px; color:var(--text3);">{{$d.T.Get "dashboard.goals.all_goals_link"}}</a>
</div>
<div style="display:flex; flex-direction:column; gap:18px;">
{{range $d.DashGoals}}
{{$funded := index $d.GoalFundedThisMonth .ID}}
{{$needed := .MonthlyCents}}
{{$met := ge $funded $needed}}
{{$partial := and (gt $funded 0) (not $met)}}
{{$monthPct := 0}}
{{if gt $needed 0}}{{$monthPct = clampPct $funded $needed}}{{end}}
<div>
<!-- name + overall progress -->
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:5px;">
<div style="display:flex; align-items:center; gap:8px; min-width:0;">
<span style="font-size:14px; flex-shrink:0;">{{if eq .Type "once"}}🎯{{else if eq .Type "deposit"}}🏠{{else if eq .Type "emergency"}}🛡️{{else}}📈{{end}}</span>
<span style="font-size:13px; font-weight:500; color:var(--text); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">{{.Name}}</span>
</div>
<div style="display:flex; align-items:center; gap:10px; flex-shrink:0; margin-left:12px;">
<span style="font-size:11px; color:var(--text3);">{{.MonthsLeft}}{{$d.T.Get "dashboard.goals.months_left"}}</span>
<span style="font-size:12px; font-weight:600; color:{{if .Feasible}}var(--green){{else}}var(--red){{end}};">{{.ProgressPct}}%</span>
</div>
</div>
<!-- overall progress bar -->
<div style="background:var(--bg3); border-radius:99px; height:4px; overflow:hidden; margin-bottom:3px;">
<div style="height:100%; border-radius:99px; width:{{.ProgressPct}}%;
background:{{if .Feasible}}var(--green){{else}}var(--accent){{end}};
transition:width 1s ease;"></div>
</div>
<div style="font-size:11px; color:var(--text3); margin-bottom:10px;">
€{{cents .SavedCents}} {{$d.T.Get "dashboard.goals.of"}} €{{cents .TargetCents}}
</div>
<!-- this-month funding status -->
{{if gt $needed 0}}
<div style="background:var(--bg2); border-radius:8px; padding:8px 12px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:6px;">
<span style="font-size:11px; color:var(--text3); text-transform:uppercase; letter-spacing:.04em;">{{$d.T.Get "dashboard.goals.this_month"}}</span>
{{if $met}}
<span style="font-size:11px; font-weight:600; color:var(--green);">✓ {{$d.T.Get "dashboard.goals.funded"}}</span>
{{else if $partial}}
<a href="/transactions?fund_goal={{.ID}}" style="font-size:11px; color:var(--accent); text-decoration:none;">{{$d.T.Get "dashboard.goals.fund_link"}} →</a>
{{else}}
<a href="/transactions?fund_goal={{.ID}}" style="font-size:11px; color:var(--red); text-decoration:none; font-weight:500;">{{$d.T.Get "dashboard.goals.fund_link"}} →</a>
{{end}}
</div>
<div style="background:var(--bg3); border-radius:99px; height:3px; overflow:hidden; margin-bottom:5px;">
<div style="height:100%; border-radius:99px; width:{{$monthPct}}%;
background:{{if $met}}var(--green){{else if $partial}}var(--accent){{else}}var(--red){{end}};
transition:width 1s ease;"></div>
</div>
<div style="font-size:11px; {{if $met}}color:var(--green){{else if $partial}}color:var(--text2){{else}}color:var(--red){{end}};">
{{if $met}}
€{{cents $funded}} {{$d.T.Get "dashboard.goals.of"}} €{{cents $needed}} {{$d.T.Get "dashboard.goals.needed"}}
{{else if $partial}}
€{{cents $funded}} {{$d.T.Get "dashboard.goals.of"}} €{{cents $needed}} — €{{cents (sub $needed $funded)}} {{$d.T.Get "dashboard.goals.left_to_fund"}}
{{else}}
€{{cents $needed}} {{$d.T.Get "dashboard.goals.needed_this_month"}}
{{end}}
</div>
</div>
{{end}}
</div>
{{end}}
</div>
</div>
{{end}}
{{end}}