The show template carried a 17-line `if/elsif` chain computing
`projection_summary` inline, plus a `Money.new([…, 0].max, …)`
expression building the catch-up delta on the fly. CLAUDE.md's
"skinny controllers, fat models" convention pushes both onto Goal.
- `Goal#projection_summary`: returns the localized,
`html_safe`-aware string for the chart subtitle and the chart's
`aria-description`. Memoized so the two callsites in show.html.erb
share one computation.
- `Goal#catch_up_delta_money`: clamped-at-zero monthly delta between
pace and the required monthly target. Used by the catch-up
callout body. Previously the view computed
`Money.new([req - pace, 0].max, currency)` — same math, but
duplicated inline.
show.html.erb drops both blocks and reads `@goal.projection_summary`
/ `@goal.catch_up_delta_money` directly.
Also: V15 — the celebration card used `bg-green-500/10` directly.
Swap to `bg-success/10` (DS semantic token, same Tailwind-4 alpha
syntax DS::Alert already uses) so the celebration palette tracks
the rest of the success surface.