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

620 lines
40 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:20px; flex-wrap:wrap; gap:8px;">
<h1>{{$d.T.Get "goals.title"}}</h1>
</div>
<!-- Tab bar -->
<div style="display:flex; gap:4px; margin-bottom:24px; border-bottom:1px solid var(--border); padding-bottom:0;">
<a href="/goals?tab=goals"
style="padding:8px 16px; font-size:14px; font-weight:500; border-radius:6px 6px 0 0; text-decoration:none;
{{if eq $d.Tab "goals"}}background:var(--bg2); color:var(--text1); border:1px solid var(--border); border-bottom:1px solid var(--bg2); margin-bottom:-1px;{{else}}color:var(--text3);{{end}}">
{{$d.T.Get "goals.tab_committed"}}
</a>
<a href="/goals?tab=planner"
style="padding:8px 16px; font-size:14px; font-weight:500; border-radius:6px 6px 0 0; text-decoration:none;
{{if eq $d.Tab "planner"}}background:var(--bg2); color:var(--text1); border:1px solid var(--border); border-bottom:1px solid var(--bg2); margin-bottom:-1px;{{else}}color:var(--text3);{{end}}">
{{$d.T.Get "goals.tab_planner"}}
</a>
</div>
{{if eq $d.Tab "goals"}}
{{/* ─── GOALS TAB ─────────────────────────────────────────────────────── */}}
<div style="display:flex; gap:10px; margin-bottom:20px; flex-wrap:wrap;">
{{if $d.AvgMonthlySavings}}
<div class="card value-card animate-on-scroll" style="flex:1; min-width:150px;">
<h2>{{$d.T.Get "goals.summary_cards.avg_monthly_savings"}}</h2>
<div class="value positive animate-counter" data-target="{{$d.AvgMonthlySavings}}" data-prefix="€">€0</div>
<p style="font-size:12px; color:var(--text3); margin-top:4px;">{{$d.T.Get "goals.summary_cards.last_3_months"}}</p>
</div>
{{end}}
<div class="card value-card animate-on-scroll" style="flex:1; min-width:150px;">
<h2>{{$d.T.Get "goals.summary_cards.income"}}</h2>
<div class="value positive animate-counter" data-target="{{$d.WaterfallIncome}}" data-prefix="€">€0</div>
<p style="font-size:12px; color:var(--text3); margin-top:4px;">{{$d.T.Get "goals.summary_cards.this_month"}}</p>
</div>
<div class="card value-card animate-on-scroll" style="flex:1; min-width:150px;">
<h2>{{$d.T.Get "goals.summary_cards.goal_funded"}}</h2>
<div class="value animate-counter" style="color:var(--accent);" data-target="{{$d.WaterfallGoals}}" data-prefix="€">€0</div>
<p style="font-size:12px; color:var(--text3); margin-top:4px;">{{$d.T.Get "goals.summary_cards.tagged_transactions"}}</p>
</div>
<div class="card value-card animate-on-scroll" style="flex:1; min-width:150px;">
<h2>{{$d.T.Get "goals.summary_cards.free_cash"}}</h2>
<div class="value animate-counter {{if lt $d.WaterfallFreeCash 0}}negative{{else}}positive{{end}}"
data-target="{{$d.WaterfallFreeCash}}" data-prefix="€">€0</div>
<p style="font-size:12px; color:var(--text3); margin-top:4px;">{{$d.T.Get "goals.summary_cards.after_goals"}}</p>
</div>
</div>
{{if $d.Goals}}
<div style="display:flex; flex-direction:column; gap:14px;">
{{range $d.Goals}}
<div class="card animate-on-scroll">
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:12px; flex-wrap:wrap;">
<div style="flex:1; min-width:200px;">
<div style="display:flex; align-items:center; gap:10px; margin-bottom:4px;">
<span style="font-size:18px;">{{if eq .Type "once"}}🎯{{else if eq .Type "deposit"}}🏠{{else if eq .Type "emergency"}}🛡️{{else}}📈{{end}}</span>
<div>
<div style="font-size:15px; font-weight:600; color:var(--text);">{{.Name}}</div>
<div style="font-size:11px; color:var(--text3); text-transform:uppercase; letter-spacing:.4px;">
{{if eq .Type "once"}}{{$d.T.Get "goals.goal_card.type_once"}}{{else if eq .Type "deposit"}}{{$d.T.Get "goals.goal_card.type_deposit"}}{{else if eq .Type "emergency"}}{{$d.T.Get "goals.goal_card.type_emergency"}}{{else}}{{$d.T.Get "goals.goal_card.type_recurring"}}{{end}}
</div>
</div>
</div>
<div style="margin-top:12px;">
<div style="display:flex; justify-content:space-between; margin-bottom:5px;">
<span style="font-size:12px; color:var(--text2);">€{{cents .SavedCents}} {{$d.T.Get "goals.goal_card.saved_of"}} €{{cents .TargetCents}}</span>
<span style="font-size:12px; color:var(--text2);">{{.ProgressPct}}%</span>
</div>
<div style="background:var(--bg3); border-radius:99px; height:6px; overflow:hidden;">
<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>
</div>
<div style="display:flex; gap:24px; flex-wrap:wrap; align-items:flex-start;">
<div style="text-align:center;">
<div style="font-size:11px; color:var(--text3); margin-bottom:3px;">{{$d.T.Get "goals.goal_card.need_per_month"}}<span class="help-tip">?<div class="help-popup"><strong>Monthly amount needed</strong>How much to set aside each month to hit your target by the deadline.<code>(Target Saved) ÷ Months remaining</code></div></span></div>
<div class="animate-counter" style="font-size:18px; font-weight:600; color:var(--text);"
data-target="{{.MonthlyCents}}" data-prefix="€">€0</div>
</div>
<div style="text-align:center;">
<div style="font-size:11px; color:var(--text3); margin-bottom:3px;">{{$d.T.Get "goals.goal_card.months_left"}}</div>
<div style="font-size:18px; font-weight:600; color:var(--text);">{{.MonthsLeft}}</div>
</div>
<div style="text-align:center;">
<div style="font-size:11px; color:var(--text3); margin-bottom:3px;">{{$d.T.Get "goals.goal_card.at_current_rate"}}<span class="help-tip">?<div class="help-popup"><strong>At current rate</strong>How many months it would actually take based on your average monthly savings. Green = on time, red = late.</div></span></div>
{{if gt .MonthsAtCurrentRate 0}}
<div style="font-size:18px; font-weight:600;
{{if .Feasible}}color:var(--green){{else}}color:var(--red){{end}};">
{{.MonthsAtCurrentRate}}mo
</div>
{{else}}
<div style="font-size:14px; color:var(--text3);"></div>
{{end}}
</div>
<div style="text-align:center;">
<div style="font-size:11px; color:var(--text3); margin-bottom:3px;">{{$d.T.Get "goals.goal_card.disposable_after"}}<span class="help-tip">?<div class="help-popup"><strong>Free cash after this goal</strong>Your estimated monthly free cash if you commit to this goal. Red means you'd need to cut spending elsewhere.<code>Income Living All committed goals This goal</code></div></span></div>
<div class="animate-counter" style="font-size:18px; font-weight:600;
{{if ge .ImpactOnDisposable 0}}color:var(--green){{else}}color:var(--red){{end}};"
data-target="{{.ImpactOnDisposable}}" data-prefix="€">€0</div>
</div>
</div>
</div>
<!-- feasibility banner -->
<div style="margin-top:14px; padding:10px 14px; border-radius:8px; font-size:13px;
background:{{if .Feasible}}rgba(74,222,128,0.08){{else}}rgba(248,113,113,0.08){{end}};
border:1px solid {{if .Feasible}}rgba(74,222,128,0.2){{else}}rgba(248,113,113,0.2){{end}};
color:{{if .Feasible}}var(--green){{else}}var(--red){{end}}; display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;">
<span>
{{if .Feasible}}
✓ On track — your current savings rate covers €{{cents .MonthlyCents}}/month with {{.MonthsLeft}} months to go.
{{else}}
⚠ At your current rate (€{{cents $d.AvgMonthlySavings}}/mo) you'd reach this in {{.MonthsAtCurrentRate}} months — {{sub .MonthsAtCurrentRate .MonthsLeft}} months late.
{{end}}
</span>
{{if and (not .Feasible) (gt .MonthsAtCurrentRate 0)}}
<form method="POST" action="/goals" style="margin:0;">
<input type="hidden" name="action" value="adjust_deadline">
<input type="hidden" name="id" value="{{.ID}}">
<input type="hidden" name="months" value="{{.MonthsAtCurrentRate}}">
<button type="submit" class="btn btn-outline btn-sm" style="color:var(--red); border-color:var(--red)44; white-space:nowrap;">
{{$d.T.Get "goals.goal_card.btn_adjust_deadline"}}
</button>
</form>
{{end}}
</div>
<!-- commit / uncommit / delete / fund -->
<div style="display:flex; justify-content:space-between; align-items:center; margin-top:14px; flex-wrap:wrap; gap:8px;">
<div style="display:flex; gap:8px; flex-wrap:wrap;">
<form method="POST" action="/goals">
<input type="hidden" name="action" value="{{if .Committed}}uncommit{{else}}commit{{end}}">
<input type="hidden" name="id" value="{{.ID}}">
{{if .Committed}}
<button type="submit" class="btn btn-outline btn-sm" style="color:var(--green); border-color:var(--green)55;">
{{$d.T.Get "goals.goal_card.btn_committed"}}
</button>
{{else}}
<button type="submit" class="btn btn-primary btn-sm">{{$d.T.Get "goals.goal_card.btn_commit"}}</button>
{{end}}
</form>
<a href="/transactions?fund_goal={{.ID}}" class="btn btn-outline btn-sm" style="color:var(--accent); border-color:var(--accent)55;">
{{$d.T.Get "goals.goal_card.btn_fund"}}
</a>
</div>
<form method="POST" action="/goals">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="{{.ID}}">
<button type="submit" class="btn btn-outline btn-sm" style="color:var(--red); border-color:var(--red)33;"
onclick="return confirm('{{$d.T.Get "goals.goal_card.confirm_remove"}}')">{{$d.T.Get "goals.goal_card.btn_remove"}}</button>
</form>
</div>
<!-- funding history -->
<div style="margin-top:14px; padding-top:14px; border-top:1px solid var(--border);">
<div style="font-size:12px; font-weight:600; color:var(--text3); text-transform:uppercase; letter-spacing:0.05em; margin-bottom:8px;">
{{$d.T.Get "goals.goal_card.funding_history"}}
<span style="font-weight:400; color:var(--accent); font-size:11px; margin-left:4px;">€{{cents .SavedCents}} {{$d.T.Get "goals.goal_card.saved_of"}} €{{cents .TargetCents}}</span>
</div>
{{if .FundingTxns}}
<div style="display:flex; flex-direction:column; gap:4px;">
{{range .FundingTxns}}
<div style="display:flex; justify-content:space-between; align-items:center; font-size:12px; padding:5px 0; border-bottom:1px solid var(--border);">
<span style="color:var(--text2);">{{.Date.Format "02 Jan"}}</span>
<span style="flex:1; padding:0 10px; color:var(--text);">{{.Description}}</span>
<span style="color:var(--accent); font-weight:600;">€{{cents (abs .AmountCents)}}</span>
</div>
{{end}}
</div>
{{else}}
<p style="font-size:12px; color:var(--text3); margin:0;">{{$d.T.Get "goals.goal_card.no_funding_yet"}}</p>
{{end}}
</div>
</div>
{{end}}
</div>
{{else}}
<div class="card empty-state animate-on-scroll">
<div style="font-size:48px; margin-bottom:16px;">🎯</div>
<h3>{{$d.T.Get "goals.empty.title"}}</h3>
<p style="margin-bottom:24px; color:var(--text3);">{{$d.T.Get "goals.empty.desc"}}</p>
<div class="setup-steps">
<div class="setup-step">
<span class="setup-step-num">1</span>
<div>
<strong>Open the Goal Planner</strong>
<p>Simulate how much you need to save monthly and by when. Try different deadlines to find a realistic target.</p>
</div>
</div>
<div class="setup-step">
<span class="setup-step-num">2</span>
<div>
<strong>Commit to the goal</strong>
<p>Mark a goal as committed so it appears in your waterfall and "what now?" prompts on the dashboard.</p>
</div>
</div>
<div class="setup-step">
<span class="setup-step-num">3</span>
<div>
<strong>Fund it every month</strong>
<p>Add expense transactions tagged to this goal, or link a spending category in Settings to auto-tag them.</p>
</div>
</div>
</div>
<a href="/goals?tab=planner" class="btn btn-primary">{{$d.T.Get "goals.empty.btn_open_planner"}}</a>
</div>
{{end}}
{{else}}
{{/* ─── PLANNER TAB ─────────────────────────────────────────────────────── */}}
{{$r := $d.PlanResult}}
{{$pr := $d.PurchaseResult}}
<!-- Type selector -->
<div class="card animate-on-scroll" style="margin-bottom:20px;">
<h2 style="margin-bottom:14px;">{{$d.T.Get "goals.planner.what_kind"}}</h2>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
<a href="/goals?tab=planner&planner_type=purchase"
style="display:block; padding:16px 18px; border-radius:var(--radius); border:2px solid {{if eq $d.PlannerType "purchase"}}var(--accent){{else}}var(--border){{end}}; text-decoration:none; transition:border-color 0.15s;">
<div style="font-size:22px; margin-bottom:8px;">🛒</div>
<div style="font-weight:600; font-size:14px; color:var(--text1); margin-bottom:4px;">{{$d.T.Get "goals.planner.purchase_title"}}</div>
<div style="font-size:12px; color:var(--text3);">{{$d.T.Get "goals.planner.purchase_desc"}}</div>
</a>
<a href="/goals?tab=planner&planner_type=transition"
style="display:block; padding:16px 18px; border-radius:var(--radius); border:2px solid {{if eq $d.PlannerType "transition"}}var(--accent){{else}}var(--border){{end}}; text-decoration:none; transition:border-color 0.15s;">
<div style="font-size:22px; margin-bottom:8px;">🔄</div>
<div style="font-weight:600; font-size:14px; color:var(--text1); margin-bottom:4px;">{{$d.T.Get "goals.planner.transition_title"}}</div>
<div style="font-size:12px; color:var(--text3);">{{$d.T.Get "goals.planner.transition_desc"}}</div>
</a>
</div>
</div>
{{if eq $d.PlannerType "purchase"}}
<!-- ── PURCHASE FORM ──────────────────────────────────────────────────────── -->
<div class="card animate-on-scroll" style="margin-bottom:20px;">
<h2 style="margin-bottom:16px;">{{$d.T.Get "goals.planner.purchase.form_title"}}</h2>
<form method="GET" action="/goals">
<input type="hidden" name="tab" value="planner">
<input type="hidden" name="planner_type" value="purchase">
<input type="hidden" name="run" value="1">
<div style="display:flex; flex-direction:column; gap:14px; margin-bottom:16px;">
<div>
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.purchase.label_name"}}</label>
<input name="name" placeholder="{{$d.T.Get "goals.planner.purchase.placeholder_name"}}"
value="{{if $d.HasPurchaseResult}}{{$pr.Name}}{{end}}"
style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px; box-sizing:border-box;">
</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
<div>
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.purchase.label_target"}}</label>
<input type="number" name="target" min="0" step="100"
value="{{if $d.HasPurchaseResult}}{{div $pr.TargetCents 100}}{{end}}"
placeholder="{{$d.T.Get "goals.planner.purchase.placeholder_target"}}"
style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px; box-sizing:border-box;">
</div>
<div>
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.purchase.label_monthly_savings"}}</label>
<input type="number" name="monthly_savings" min="0" step="50"
value="{{if $d.HasPurchaseResult}}{{div $pr.MonthlySavingsCents 100}}{{end}}"
placeholder="{{$d.T.Get "goals.planner.purchase.placeholder_monthly"}}"
style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px; box-sizing:border-box;">
</div>
</div>
<div>
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.purchase.label_deadline"}}</label>
<input type="month" name="deadline"
value="{{if and $d.HasPurchaseResult $pr.HasDeadline}}{{$pr.DeadlineDate.Format "2006-01"}}{{end}}"
style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px; box-sizing:border-box;">
</div>
</div>
<button type="submit" class="btn btn-primary" style="width:100%;">{{$d.T.Get "goals.planner.purchase.btn_calculate"}}</button>
</form>
</div>
{{if $d.HasPurchaseResult}}
<!-- Purchase result -->
<div class="grid" style="margin-bottom:20px;">
<div class="card value-card animate-on-scroll">
<h2>{{$d.T.Get "goals.planner.purchase_result.card_at_rate"}}</h2>
{{if gt $pr.MonthsNeeded 0}}
<div class="value positive">{{$pr.YearsNeeded}}y {{$pr.RemMonths}}m</div>
<p style="font-size:12px; color:var(--text3); margin-top:6px;">{{$d.T.Get "goals.planner.purchase_result.reach_label"}} {{dateShort $pr.ReachDate}}</p>
{{else}}
<div class="value" style="color:var(--text3); font-size:18px;"></div>
<p style="font-size:12px; color:var(--text3); margin-top:6px;">{{$d.T.Get "goals.planner.purchase_result.enter_monthly"}}</p>
{{end}}
</div>
<div class="card value-card animate-on-scroll">
<h2>{{$d.T.Get "goals.planner.purchase_result.card_monthly_needed"}}</h2>
{{if $pr.HasDeadline}}
<div class="value {{if $pr.Feasible}}positive{{else}}negative{{end}}">€{{cents $pr.MonthlyNeededForDeadline}}</div>
<p style="font-size:12px; color:var(--text3); margin-top:6px;">{{$d.T.Get "goals.planner.purchase_result.to_hit_deadline"}} {{dateShort $pr.DeadlineDate}}</p>
{{else}}
<div class="value" style="color:var(--text3); font-size:18px;"></div>
<p style="font-size:12px; color:var(--text3); margin-top:6px;">{{$d.T.Get "goals.planner.purchase_result.set_target_date"}}</p>
{{end}}
</div>
<div class="card value-card animate-on-scroll">
<h2>{{$d.T.Get "goals.planner.purchase_result.card_target"}}</h2>
<div class="value positive">€{{cents $pr.TargetCents}}</div>
<p style="font-size:12px; color:var(--text3); margin-top:6px;">{{$d.T.Get "goals.planner.purchase_result.your_goal_amount"}}</p>
</div>
</div>
{{if $pr.HasDeadline}}
<div style="padding:12px 16px; border-radius:var(--radius); margin-bottom:20px; font-size:13px;
background:{{if $pr.Feasible}}rgba(74,222,128,0.08){{else}}rgba(248,113,113,0.08){{end}};
border:1px solid {{if $pr.Feasible}}rgba(74,222,128,0.25){{else}}rgba(248,113,113,0.25){{end}};
color:{{if $pr.Feasible}}var(--green){{else}}var(--red){{end}};">
{{if $pr.Feasible}}
✓ On track — €{{cents $pr.MonthlySavingsCents}}/mo covers the required €{{cents $pr.MonthlyNeededForDeadline}}/mo.
{{else}}
⚠ You need €{{cents $pr.MonthlyNeededForDeadline}}/mo to hit the deadline but you're saving €{{cents $pr.MonthlySavingsCents}}/mo. At your current rate you'll get there in {{$pr.MonthsNeeded}} months ({{dateShort $pr.ReachDate}}).
{{end}}
</div>
{{end}}
<!-- Save as goal -->
<div class="card animate-on-scroll">
<h2 style="margin-bottom:4px;">{{$d.T.Get "goals.planner.purchase_result.save_as_goal_title"}}</h2>
<p style="font-size:13px; color:var(--text3); margin-bottom:14px;">{{$d.T.Get "goals.planner.purchase_result.save_as_goal_desc"}}</p>
<form method="POST" action="/goals" style="display:flex; gap:10px; flex-wrap:wrap; align-items:flex-end;">
<input type="hidden" name="type" value="once">
<input type="hidden" name="target_euros" value="{{div $pr.TargetCents 100}}">
<input type="hidden" name="deadline" value="{{if $pr.HasDeadline}}{{$pr.DeadlineDate.Format "2006-01"}}{{else}}{{$pr.ReachDate.Format "2006-01"}}{{end}}">
<div style="flex:1; min-width:200px;">
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.purchase_result.label_goal_name"}}</label>
<input name="name" required value="{{$pr.Name}}" placeholder="{{$d.T.Get "goals.planner.purchase_result.placeholder_goal_name"}}"
style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px; box-sizing:border-box;">
</div>
<button type="submit" class="btn btn-primary" style="white-space:nowrap;">{{$d.T.Get "goals.planner.purchase_result.btn_save_goal"}}</button>
</form>
</div>
{{end}}
{{else if eq $d.PlannerType "transition"}}
<!-- ── TRANSITION FORM ───────────────────────────────────────────────────── -->
<div class="card animate-on-scroll" style="margin-bottom:20px;">
<h2 style="margin-bottom:16px;">{{$d.T.Get "goals.planner.transition.form_title"}}</h2>
<form method="GET" action="/goals">
<input type="hidden" name="tab" value="planner">
<input type="hidden" name="planner_type" value="transition">
<input type="hidden" name="run" value="1">
<div class="grid" style="gap:14px; margin-bottom:16px;">
<div>
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.transition.label_current_asset"}}</label>
<select name="property_id" style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px;">
<option value="">{{$d.T.Get "goals.planner.transition.option_none_asset"}}</option>
{{range $d.PlanProperties}}
<option value="{{.ID}}" {{if and $d.HasPlanResult (eq $d.PlanForm.PropertyID .ID)}}selected{{end}}>{{.Name}} (€{{cents .CurrentValueCents}})</option>
{{end}}
</select>
</div>
<div>
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.transition.label_current_loan"}}</label>
<select name="loan_id" style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px;">
<option value="">{{$d.T.Get "goals.planner.transition.option_none_loan"}}</option>
{{range $d.PlanLoans}}
<option value="{{.ID}}" {{if and $d.HasPlanResult (eq $d.PlanForm.LoanID .ID)}}selected{{end}}>{{.Name}} (€{{cents .BalanceCents}} left)</option>
{{end}}
</select>
</div>
<div>
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.transition.label_dream_cost"}}</label>
<input type="number" name="dream_cost" min="0" step="1000"
value="{{if $d.HasPlanResult}}{{div $d.PlanForm.DreamCostCents 100}}{{end}}"
placeholder="{{$d.T.Get "goals.planner.transition.placeholder_dream_cost"}}"
style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px; box-sizing:border-box;">
</div>
<div>
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.transition.label_down_pct"}}</label>
<input type="number" name="down_pct" min="0" max="100" step="1"
value="{{if $d.HasPlanResult}}{{round $d.PlanForm.DownPaymentPct}}{{else}}20{{end}}"
style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px; box-sizing:border-box;">
</div>
<div>
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.transition.label_loan_rate"}}</label>
<input type="number" name="const_rate" min="0" max="30" step="0.1"
value="{{if $d.HasPlanResult}}{{$d.PlanForm.ConstructionRatePct}}{{else}}4.0{{end}}"
style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px; box-sizing:border-box;">
</div>
<div>
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.transition.label_loan_term"}}</label>
<input type="number" name="const_term" min="1" max="40" step="1"
value="{{if $d.HasPlanResult}}{{$d.PlanForm.ConstructionTermYears}}{{else}}30{{end}}"
style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px; box-sizing:border-box;">
</div>
<div>
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.transition.label_build_months"}}</label>
<input type="number" name="build_months" min="1" max="60" step="1"
value="{{if $d.HasPlanResult}}{{$d.PlanForm.BuildMonths}}{{else}}18{{end}}"
style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px; box-sizing:border-box;">
</div>
<div>
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.transition.label_monthly_savings"}}</label>
<input type="number" name="monthly_savings" min="0" step="100"
value="{{if $d.HasPlanResult}}{{div $d.PlanForm.MonthlySavingsCents 100}}{{end}}"
placeholder="{{$d.T.Get "goals.planner.transition.placeholder_savings"}}"
style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px; box-sizing:border-box;">
</div>
<div>
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.transition.label_sale_price"}}</label>
<input type="number" name="sale_price" min="0" step="1000"
value="{{if $d.HasPlanResult}}{{div $d.PlanForm.ExpectedSalePriceCents 100}}{{end}}"
placeholder="{{$d.T.Get "goals.planner.transition.placeholder_sale_price"}}"
style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px; box-sizing:border-box;">
</div>
</div>
<button type="submit" class="btn btn-primary" style="width:100%;">{{$d.T.Get "goals.planner.transition.btn_run"}}</button>
</form>
</div>
{{if $d.HasPlanResult}}
{{if $r.Warning}}
<div style="background:rgba(251,191,36,0.12); border:1px solid rgba(251,191,36,0.4); border-radius:var(--radius); padding:12px 16px; margin-bottom:16px; font-size:13px; color:var(--text2);">
⚠️ {{$r.Warning}}
</div>
{{end}}
<!-- Summary cards -->
<div class="grid" style="margin-bottom:20px;">
<div class="card value-card animate-on-scroll">
<h2>{{$d.T.Get "goals.planner.transition_result.card_total_timeline"}}</h2>
<div class="value positive">{{$r.TotalYears}}y {{$r.TotalRemMonths}}m</div>
<p style="font-size:12px; color:var(--text3); margin-top:6px;">{{$d.T.Get "goals.planner.transition_result.until_paid_off"}}</p>
</div>
<div class="card value-card animate-on-scroll">
<h2>{{$d.T.Get "goals.planner.transition_result.card_final_monthly"}}</h2>
<div class="value positive">€{{cents $r.Phase4MonthlyCents}}</div>
<p style="font-size:12px; color:var(--text3); margin-top:6px;">{{$d.T.Get "goals.planner.transition_result.after_selling"}}</p>
</div>
<div class="card value-card animate-on-scroll">
<h2>{{$d.T.Get "goals.planner.transition_result.card_total_interest"}}</h2>
<div class="value" style="color:var(--red);">€{{cents $r.TotalInterestCents}}</div>
<p style="font-size:12px; color:var(--text3); margin-top:6px;">{{$d.T.Get "goals.planner.transition_result.across_both_loans"}}</p>
</div>
<div class="card value-card animate-on-scroll">
<h2>{{$d.T.Get "goals.planner.transition_result.card_free_by"}}</h2>
<div class="value positive" style="font-size:24px;">{{dateShort $r.FinalDate}}</div>
<p style="font-size:12px; color:var(--text3); margin-top:6px;">{{$d.T.Get "goals.planner.transition_result.fully_paid_off"}}</p>
</div>
</div>
<!-- Phase timeline -->
<div class="card animate-on-scroll" style="margin-bottom:20px;">
<h2 style="margin-bottom:20px;">{{$d.T.Get "goals.planner.transition_result.roadmap_title"}}</h2>
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:0; position:relative;">
<div style="position:absolute; top:20px; left:12.5%; right:12.5%; height:3px; background:var(--border); z-index:0; border-radius:2px;"></div>
<!-- Phase 1 -->
<div style="text-align:center; position:relative; padding:0 8px;">
<div style="width:40px; height:40px; border-radius:50%; background:var(--accent); color:#fff; display:flex; align-items:center; justify-content:center; font-size:18px; margin:0 auto 12px; position:relative; z-index:1;">1</div>
<div style="font-weight:600; font-size:13px; color:var(--text1); margin-bottom:6px;">{{$d.T.Get "goals.planner.transition_result.phase1_title"}}</div>
{{if gt $r.Phase1Months 0}}
<div style="font-size:22px; font-weight:500; color:var(--accent); margin-bottom:4px;">{{$r.Phase1Months}}mo</div>
<div style="font-size:11px; color:var(--text3);">until {{dateShort $r.Phase1EndDate}}</div>
<div style="margin-top:10px; font-size:12px; color:var(--text2); background:var(--bg3); border-radius:var(--radius-sm); padding:8px 10px; text-align:left;">
<div>{{$d.T.Get "goals.planner.transition_result.phase1_target"}} <strong>€{{cents $r.DownPaymentCents}}</strong></div>
<div>{{$d.T.Get "goals.planner.transition_result.phase1_already_have"}} <strong>€{{cents $r.AlreadyHaveCents}}</strong></div>
<div>{{$d.T.Get "goals.planner.transition_result.phase1_still_need"}} <strong>€{{cents $r.StillNeededCents}}</strong></div>
<div>{{$d.T.Get "goals.planner.transition_result.phase1_saving"}} <strong>€{{cents $r.Form.MonthlySavingsCents}}/mo</strong></div>
</div>
{{else}}
<div style="font-size:16px; font-weight:600; color:var(--green); margin-bottom:4px;">{{$d.T.Get "goals.planner.transition_result.phase1_ready"}}</div>
<div style="font-size:11px; color:var(--text3);">{{$d.T.Get "goals.planner.transition_result.phase1_equity_covers"}}</div>
<div style="margin-top:10px; font-size:12px; color:var(--text2); background:var(--bg3); border-radius:var(--radius-sm); padding:8px 10px; text-align:left;">
<div>{{$d.T.Get "goals.planner.transition_result.phase1_down_payment"}} <strong>€{{cents $r.DownPaymentCents}}</strong></div>
<div>{{$d.T.Get "goals.planner.transition_result.phase1_your_equity"}} <strong style="color:var(--green);">€{{cents $r.AlreadyHaveCents}}</strong></div>
</div>
{{end}}
</div>
<!-- Phase 2 -->
<div style="text-align:center; position:relative; padding:0 8px;">
<div style="width:40px; height:40px; border-radius:50%; background:#f97316; color:#fff; display:flex; align-items:center; justify-content:center; font-size:18px; margin:0 auto 12px; position:relative; z-index:1;">2</div>
<div style="font-weight:600; font-size:13px; color:var(--text1); margin-bottom:6px;">{{$d.T.Get "goals.planner.transition_result.phase2_title"}}</div>
<div style="font-size:22px; font-weight:500; color:#f97316; margin-bottom:4px;">{{$r.Phase2Months}}mo</div>
<div style="font-size:11px; color:var(--text3);">until {{dateShort $r.Phase2EndDate}}</div>
<div style="margin-top:10px; font-size:12px; color:var(--text2); background:var(--bg3); border-radius:var(--radius-sm); padding:8px 10px; text-align:left;">
<div>{{$d.T.Get "goals.planner.transition_result.phase2_new_loan"}} <strong>€{{cents $r.ConstructionLoanCents}}</strong></div>
{{if $r.CurrentLoan}}<div>{{$d.T.Get "goals.planner.transition_result.phase2_existing_loan"}} <strong>€{{cents $r.CurrentMonthlyCents}}/mo</strong></div>{{end}}
<div>{{$d.T.Get "goals.planner.transition_result.phase2_new_emi"}} <strong>€{{cents $r.ConstructionMonthly}}/mo</strong></div>
<div style="border-top:1px solid var(--border); margin-top:6px; padding-top:6px;">{{$d.T.Get "goals.planner.transition_result.phase2_total_burden"}} <strong style="color:#f97316;">€{{cents $r.Phase2MonthlyCents}}/mo</strong></div>
</div>
</div>
<!-- Phase 3 -->
<div style="text-align:center; position:relative; padding:0 8px;">
<div style="width:40px; height:40px; border-radius:50%; background:#14b8a6; color:#fff; display:flex; align-items:center; justify-content:center; font-size:18px; margin:0 auto 12px; position:relative; z-index:1;">3</div>
<div style="font-weight:600; font-size:13px; color:var(--text1); margin-bottom:6px;">{{$d.T.Get "goals.planner.transition_result.phase3_title"}}</div>
<div style="font-size:16px; font-weight:600; color:#14b8a6; margin-bottom:4px;">{{$d.T.Get "goals.planner.transition_result.phase3_one_time"}}</div>
<div style="font-size:11px; color:var(--text3);">{{$d.T.Get "goals.planner.transition_result.phase3_after_acquisition"}}</div>
<div style="margin-top:10px; font-size:12px; color:var(--text2); background:var(--bg3); border-radius:var(--radius-sm); padding:8px 10px; text-align:left;">
<div>{{$d.T.Get "goals.planner.transition_result.phase3_sale_price"}} <strong>€{{cents $r.SalePriceCents}}</strong></div>
<div>{{$d.T.Get "goals.planner.transition_result.phase3_pay_off"}} <strong style="color:var(--red);">-€{{cents $r.MortgagePayoffCents}}</strong></div>
<div style="border-top:1px solid var(--border); margin-top:6px; padding-top:6px;">{{$d.T.Get "goals.planner.transition_result.phase3_net_proceeds"}} <strong style="color:var(--green);">€{{cents $r.NetProceedsCents}}</strong></div>
<div>{{$d.T.Get "goals.planner.transition_result.phase3_applied"}}</div>
</div>
</div>
<!-- Phase 4 -->
<div style="text-align:center; position:relative; padding:0 8px;">
<div style="width:40px; height:40px; border-radius:50%; background:var(--green); color:#fff; display:flex; align-items:center; justify-content:center; font-size:18px; margin:0 auto 12px; position:relative; z-index:1;">4</div>
<div style="font-weight:600; font-size:13px; color:var(--text1); margin-bottom:6px;">{{$d.T.Get "goals.planner.transition_result.phase4_title"}}</div>
{{if gt $r.Phase4Months 0}}
<div style="font-size:22px; font-weight:500; color:var(--green); margin-bottom:4px;">{{$r.Phase4Months}}mo</div>
<div style="font-size:11px; color:var(--text3);">paid off {{dateShort $r.Phase4EndDate}}</div>
<div style="margin-top:10px; font-size:12px; color:var(--text2); background:var(--bg3); border-radius:var(--radius-sm); padding:8px 10px; text-align:left;">
<div>{{$d.T.Get "goals.planner.transition_result.phase4_remaining_loan"}} <strong>€{{cents $r.RemainingBalanceCents}}</strong></div>
<div>{{$d.T.Get "goals.planner.transition_result.phase4_monthly"}} <strong style="color:var(--green);">€{{cents $r.Phase4MonthlyCents}}/mo</strong></div>
</div>
{{else}}
<div style="font-size:16px; font-weight:600; color:var(--green); margin-bottom:4px;">{{$d.T.Get "goals.planner.transition_result.phase4_fully_paid"}}</div>
<div style="font-size:11px; color:var(--text3);">{{$d.T.Get "goals.planner.transition_result.phase4_sale_cleared"}}</div>
<div style="margin-top:10px; font-size:12px; color:var(--text2); background:var(--bg3); border-radius:var(--radius-sm); padding:8px 10px; text-align:left;">
<div style="color:var(--green); font-weight:600;">{{$d.T.Get "goals.planner.transition_result.phase4_no_remaining"}}</div>
</div>
{{end}}
</div>
</div>
</div>
<!-- Monthly cost chart -->
<div class="card animate-on-scroll" style="margin-bottom:20px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
<h2>{{$d.T.Get "goals.planner.transition_result.chart_title"}}</h2>
<span style="font-size:11px; color:var(--text3);">{{$d.T.Get "goals.planner.transition_result.chart_subtitle"}}</span>
</div>
<canvas id="cost-chart" height="160"></canvas>
</div>
<script>
(function() {
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
const style = getComputedStyle(document.documentElement);
const accent = style.getPropertyValue('--accent').trim() || '#6979f8';
const green = style.getPropertyValue('--green').trim() || '#4ade80';
const phase1Months = {{$r.Phase1Months}};
const phase2Months = {{$r.Phase2Months}};
const phase4Months = {{$r.Phase4Months}};
const existingMonthly = {{$r.CurrentMonthlyCents}} / 100;
const phase2Monthly = {{$r.Phase2MonthlyCents}} / 100;
const phase4Monthly = {{$r.Phase4MonthlyCents}} / 100;
const labels = [], costs = [], colors = [];
for (let i = 0; i < phase1Months; i++) { labels.push('Save '+(i+1)); costs.push(existingMonthly); colors.push(accent); }
for (let i = 0; i < phase2Months; i++) { labels.push('Acquire '+(i+1)); costs.push(phase2Monthly); colors.push('#f97316'); }
for (let i = 0; i < phase4Months; i++) { labels.push('Goal '+(i+1)); costs.push(phase4Monthly); colors.push(green); }
const step = Math.ceil(labels.length / 24);
const displayLabels = labels.map((l, i) => i % step === 0 ? l : '');
new Chart(document.getElementById('cost-chart').getContext('2d'), {
type: 'bar',
data: { labels: displayLabels, datasets: [{ data: costs, backgroundColor: colors, borderRadius: 3, borderSkipped: false }] },
options: {
responsive: true,
plugins: {
legend: { display: false },
tooltip: { callbacks: { title: i => labels[i[0].dataIndex], label: c => '€'+c.parsed.y.toLocaleString('pt-PT',{minimumFractionDigits:2})+'/mo' } }
},
scales: {
x: { grid: { display: false }, ticks: { color: isDark?'#5c6585':'#9fa8c7', font:{size:10} } },
y: { grid: { color: isDark?'rgba(255,255,255,0.04)':'rgba(0,0,0,0.05)' }, ticks: { color: isDark?'#5c6585':'#9fa8c7', callback: v=>'€'+(v/1000).toFixed(0)+'k' } }
}
}
});
})();
</script>
<!-- Save as goal (transition) -->
<div class="card animate-on-scroll">
<h2 style="margin-bottom:4px;">{{$d.T.Get "goals.planner.transition_result.save_as_goal_title"}}</h2>
<p style="font-size:13px; color:var(--text3); margin-bottom:14px;">{{$d.T.Get "goals.planner.transition_result.save_as_goal_desc"}}</p>
<form method="POST" action="/goals" style="display:flex; gap:10px; flex-wrap:wrap; align-items:flex-end;">
<input type="hidden" name="type" value="deposit">
<input type="hidden" name="target_euros" value="{{div $r.Form.DreamCostCents 100}}">
<input type="hidden" name="deadline" value="{{$r.FinalDate.Format "2006-01"}}">
<div style="flex:1; min-width:200px;">
<label style="font-size:12px; color:var(--text3); display:block; margin-bottom:5px;">{{$d.T.Get "goals.planner.transition_result.label_goal_name"}}</label>
<input name="name" required placeholder="{{$d.T.Get "goals.planner.transition_result.placeholder_goal_name"}}"
style="width:100%; padding:8px 10px; border:1px solid var(--border); border-radius:var(--radius-sm); background:var(--bg2); color:var(--text1); font-size:14px; box-sizing:border-box;">
</div>
<button type="submit" class="btn btn-primary" style="white-space:nowrap;">{{$d.T.Get "goals.planner.transition_result.btn_save_goal"}}</button>
</form>
</div>
{{end}}{{/* end HasPlanResult */}}
{{end}}{{/* end planner_type transition */}}
{{end}}{{/* end tab */}}
{{end}}