Back to All Projects
Personal Finance  ·  PWA  ·  Single-File  ·  2026

RafiFinance

A complete personal finance tracker built as a single HTML file. No server, no database, no framework. Transactions, budgets, saving goals, health score, 6-month analysis, works offline as a PWA.

Vanilla JS Single HTML File PWA localStorage Service Worker Cloudflare Pages
Status
Completed · Live
Year
2026
Type
Personal Tool · v4
File Size
1 HTML file
01 Project Overview

RafiFinance v4 is a finance tracker I built because every other option was either too complicated, required an account, or did something weird with my data. So I made my own, in a single HTML file.

So I built my own under one constraint: a single HTML file — no npm, no build step, no server, no framework. Data stays on the device in localStorage under rfv4, exportable as JSON at any time.

The single-file constraint made me better at JavaScript. When you can't hide behind imports and abstractions, you actually have to know what you're doing. Every feature had to earn its place, nothing's in there by accident.

Deployed on Cloudflare Pages at finance.rafiarsya.com, free hosting, global CDN, automatic HTTPS, zero config. The PWA service worker is generated as a Blob URL at runtime, so even the offline capability requires no external files.

02 Screens

The whole app is one HTML file, installed to the home screen as a PWA. These are the screens I actually use day to day.

HomeDashboard
AddTransaction entry
CategoriesCategory breakdown
TrendsSpending over time
ScoreFinancial health score
BudgetsBudget targets
SettingsSettings and data export
03 Features
Transactions
Log income and expenses with category, date, and notes. Full filter, search, and sort across history. Real-time running balance.
Budget Tracking
Set monthly budgets per category with visual progress bars. Over-budget alerts and spend rate indicators. Resets automatically each month.
Saving Goals with Smart Estimation
Set a target amount and a deadline date. The app calculates the required monthly savings to hit the goal on time, and tracks progress in real time as you add transactions.
Recurring Reminders
Set browser notifications for bills, subscriptions, and recurring payments. Configurable frequency (daily, weekly, monthly). Uses the Notifications API, works offline.
6-Month Trend Analysis
Visual income vs expense comparison across 6 months. Spending breakdown by category. Identifies which months were over-budget and by how much.
Financial Health Score (0–100)
A real-time composite score based on savings rate, budget adherence, and goal progress. Updates live as you add data, no manual calculation needed.
04 Financial Health Score, The Math

The health score is a weighted composite of three independent signals about your financial habits:

01Health Score Formula
score = (savingsRate × 0.4)
      + (budgetAdherence × 0.35)
      + (goalProgress × 0.25)

-- savingsRate (0–100):
   savings    = totalIncome - totalExpenses
   rate       = savings / totalIncome × 100
   normalized = clamp(rate / 20 × 100, 0, 100)
   -- saving 20% of income = 100 points

-- budgetAdherence (0–100):
   for each category with a budget:
     adherence_i = clamp(1 - overspend_i/budget_i, 0, 1)
   budgetAdherence = mean(adherence_i) × 100

-- goalProgress (0–100):
   for each saving goal:
     progress_i = saved_i / target_i
   goalProgress = mean(progress_i) × 100

score = clamp(score, 0, 100)

The weights reflect real priorities. Savings rate carries the most (40%) because a buffer is the foundation. Budget adherence (35%) prevents spending creep. Goal progress (25%) weighs least — having no active goal shouldn't tank an otherwise healthy score.

# Monthly income RM 3,000, expenses RM 2,400
savings = 3000 - 2400 = 600
rate = 600/3000 × 100 = 20%
savingsRate (normalized) = clamp(20/20 × 100) = 100

# Budgets: Food RM400 (spent 380), Transport RM150 (spent 165)
adherence_food      = clamp(1 - 0/400, 0, 1)   = 1.00
adherence_transport = clamp(1 - 15/150, 0, 1)  = 0.90
budgetAdherence = mean(1.00, 0.90) × 100 = 95

# One goal: 1200/5000 saved
goalProgress = (1200/5000) × 100 = 24

score = 100×0.4 + 95×0.35 + 24×0.25
      = 40 + 33.25 + 6
      = 79.25  →  79 / 100
02Saving Goal: Monthly Target Calculation
monthsRemaining = (deadline.year - today.year) × 12
                + (deadline.month - today.month)

monthlyRequired = (targetAmount - savedSoFar) / monthsRemaining

The calculation updates live as transactions land. Overshoot one month and the next target drops; fall behind and the remaining months absorb a slightly higher target. It always shows the shortest path to the goal, not a plan fixed once and forgotten.

# Goal: Save RM 5,000 for a laptop
savedSoFar  = RM 1,200
deadline    = 8 months from today

monthlyRequired = (5000 - 1200) / 8
                = 3800 / 8
                = RM 475 / month

# After month 1, saved an extra RM 600 (RM 1,075 total):
monthsRemaining = 7
monthlyRequired = (5000 - 1075) / 7
                = RM 560.71 / month  ← recalculated automatically
05 Single-File Architecture & PWA

The entire app, HTML structure, ~800 lines of CSS, ~1800 lines of vanilla JavaScript, lives in one HTML file. All state is managed with a single global object persisted to localStorage:

03localStorage Schema (key: rfv4)
{
  "transactions": [
    { "id": "t_1710000000000",
      "type": "expense",         // "income" | "expense"
      "amount": 45.50,
      "category": "Food",
      "date": "2026-06-01",
      "note": "Lunch at cafe"
    }
  ],
  "budgets": {
    "Food": 400,
    "Transport": 150,
    "Entertainment": 100
  },
  "goals": [
    { "id": "g_1",
      "title": "New Laptop",
      "target": 5000,
      "deadline": "2026-12-01",
      "saved": 1200
    }
  ],
  "reminders": [ ... ],
  "settings": { "currency": "MYR", "theme": "dark" }
}

The whole state serialises to one JSON string — export is JSON.stringify(state), import is JSON.parse(fileContent). One-line backup and restore, no server round-trip, no account. The data never leaves the device unless you export it.

# Export button click:
const blob = new Blob(
  [JSON.stringify(state, null, 2)],
  { type: 'application/json' }
);
// → downloads "rafifinance-backup-2026-06-19.json"

# Import on a new device:
const state = JSON.parse(fileContent);
localStorage.setItem('rfv4', JSON.stringify(state));
location.reload();
// → all transactions, budgets, goals restored instantly

The PWA manifest is embedded inline as a data: URL. The service worker is generated from a Blob URL at runtime, no external sw.js file. Everything self-contained:

1
Manifest injected inline
<link rel="manifest" href="data:application/json,{...}">, the manifest JSON is encoded directly into the HTML, no separate file needed.
2
Service Worker from Blob
SW code is defined as a JS string, converted to a Blob, then registered via URL.createObjectURL(blob), no separate sw.js file on the server.
3
Cache-first offline strategy
On first load, the SW caches the HTML file. Subsequent loads serve from cache instantly. Works fully offline after the first visit.
06 Design System
Color Palette
Background
#080810
Card Surface
#1a1a2e
Accent (Purple)
#6c5ce7
Primary Text
#f5f5f5
Muted Text
#888888
Typography
RafiFinance
Inter · 700 · Headings
Transaction history, labels
Inter · 400 · Body
RM 1,234.56
JetBrains Mono · Numbers
07 Use Cases
Student Budgeting
Track allowance, food, transport, and entertainment against monthly budgets. No account signup, works on any device.
Private: No Account
Financial data never leaves your device. No signup, no cloud sync, no company storing your spending habits.
Installable PWA
Install to your home screen on Android or iOS. Works offline after the first load. Feels like a native app.
Goal-Oriented Saving
Set saving goals with deadlines and let the app tell you exactly how much to save each month to hit the target.
Health Score Demo
75
Financial Health
Good · Keep saving
Savings Rate×0.40
Budget Adherence×0.35
Goal Progress×0.25
Tech Stack
Vanilla JS HTML5 CSS3 PWA localStorage Service Worker Notifications API Lucide Icons CDN Cloudflare Pages
One file. Full finance tracker.
No signup, no cloud, no dependencies. Open the URL, use it immediately. Your data stays on your device, exportable as JSON whenever you want it.
Open RafiFinance