Saldo

A 30-day budget simulator for a ₹10,000 month. Every purchase, subscription, cancellation, and surprise is previewed before it is applied.

Role
Design & engineering, solo
Year
2026
Category
Product
Status
Live
Saldo landing page with a live purchase simulation panel showing safe-to-spend and a 30-day forecast
01

Overview

Saldo is a single-page budget companion for a ₹10,000 / 30-day month. It answers one question before every purchase: if I buy this, what exactly changes?

Most budgeting tools are rear-view mirrors. They categorise what has already happened. The decision that matters happens before the transaction, when the cost is still abstract. A ₹1,499 purchase is easy to justify until you see what it does to the next nineteen days.

02

Goals

  • Preview every decision — purchase, subscription, cancellation, unexpected expense, goal transfer — before committing it.
  • Keep one calculation engine so the preview and the applied result can never disagree.
  • Make the money model legible without relying on charts alone or colour-only status.
  • Ship a small, fast, keyboard-accessible demo with no backend and no account.
03

My contribution

  • Product framing and the simulation model
  • Interface design and the responsive system
  • Domain logic, state management, accessibility, and performance
  • Unit, accessibility, and production-build checks
04

Design & engineering decisions

01

Preview is a real run of the engine

A draft decision is passed through the same `applyDecision` function used by Apply, but against a copy of state. The preview reads the result without touching the log. Apply commits that exact output. There is no second formula to drift.

02

No router, no chart library

The demo uses hash routes and hand-built SVG. The production bundle is about 83KB of JavaScript gzip and 7.9KB of CSS gzip. Removing dependencies made the interface faster and the chart behaviour easier to control.

03

Risk is never colour alone

Every risk state pairs a label and a plain-language reason with its colour. The same rule applies to the health score: four named parts, a plain mean, and no implication that it is a credit score.

04

Explain the cascade in place

Applying a decision changes balance, safe-to-spend, goal date, forecast, and steadiness at once. The UI previews those five values in the same positions they will occupy after Apply, so the change is understood before it happens.

05

Architecture

domain/Pure TypeScript: formulas, simulation, health, risk, and decision copy. No React imports.
state/A reducer store with a draft layer, undo history, and staggered transition timers.
features/Landing, goals, subscriptions, unexpected expenses, decision log, forecast, and risk.
styles/Design tokens, type scale, and motion rules, all aware of reduced-motion preferences.
React 19TypeScript (strict)Vite 8Tailwind CSS v4Vitest + axePlaywright
  • The demo is deliberately local: no login, no backend, no real money. State resets on reload.
  • The breakdown dialog traps focus, closes on Escape, and returns focus to the number that opened it.
  • Balance and safe-to-spend live in polite live regions; a hidden region announces each apply, discard, or reset.
06

Implementation

  • A single `applyDecision(state, decision) → { state, ok }` function powers preview, apply, and undo.
  • The forecast and month strip are hand-built SVG with accessible titles and a screen-reader summary.
  • Inputs keep local state so typing does not re-render the full decision tree.
  • Every interactive target is at least 44px; focus rings are permanent and visible.
07

Challenges

01

Keeping a demo honest

The numbers are mock data, so the interface says so in the footer and in the demo copy. The goal was a convincing product story without implying a real financial service.

02

Making a cascade feel calm

Five numbers move after Apply. Staggering the transitions and keeping the values in fixed positions turns a potentially confusing update into a readable sequence. Reduced-motion users get the final state instantly.

08

Outcome

Live at saldo-ebon.vercel.app. The production build measures roughly 83KB of JavaScript gzip and 7.9KB of CSS gzip. Lighthouse 13.4 reported 97 / 100 / 100 / 100 on mobile and 100 across all four categories on desktop. axe reported zero violations on both views.

09

What I learned

A single source of truth for calculations is also an interface decision.

When preview and apply share one engine, the product can explain itself without hedging.