Muhammad Rafi Arsya
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 with a deliberate architectural constraint: one HTML file. No npm. No build step. No server. No framework. Open it in a browser and it works. Your data stays on your device in localStorage under the key rfv4, exportable as a single JSON file 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 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.
03 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)
scroll for full derivation

The weights reflect real financial priorities: savings rate has the largest impact (40%) because building a buffer is the foundation of financial health. Budget adherence (35%) prevents spending creep. Goal progress (25%) rewards intentional saving behavior — it carries less weight than the other two because not having an active goal yet 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
scroll to see full computation
02Saving Goal — Monthly Target Calculation
monthsRemaining = (deadline.year - today.year) × 12
                + (deadline.month - today.month)

monthlyRequired = (targetAmount - savedSoFar) / monthsRemaining
scroll for full derivation

This calculation updates live as transactions come in. If you overshoot in a month, the next month's target drops automatically — the system always shows the shortest path to the goal rather than a fixed plan set once and forgotten. Falling behind one month means the remaining months simply absorb a slightly higher target, recalculated the moment the app reopens.

# 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
scroll to see full computation
04 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" }
}
scroll for full derivation

The entire state serializes to a single JSON string. Export = JSON.stringify(state) → file download. Import = JSON.parse(fileContent) → merge into state. One-line backup/restore, with no server round-trip and no account system required — your financial data never leaves the device unless you explicitly 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
scroll to see full computation

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.
05 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
06 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
By the Numbers
1
HTML File
0
Dependencies
v4
Version
100%
Offline Ready
Completion
Transactions100%
Budget Tracking100%
Saving Goals100%
Health Score100%
PWA / Offline100%
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