← Back to projects
Overview

What it does

A fully functional scientific calculator with a twist: every time you press "=", a paywall modal blocks the result.

The user picks one of three "unlock" tiers — Premium (fake payment flow with progress bar and stage transitions), Watch an Ad (5-second countdown banner with skip button, Indonesian style), or Free Trial (3 free calculations, then the option disables itself).

Built as a satire on modern SaaS UX patterns — feature gates, artificial friction, and dark patterns disguised as choice. The disclaimer in the modal footer reminds users they can always close the modal to skip.

Architecture
index.html    # Calculator UI + 3 modals (paywall, ad player, payment)
style.css     # Dark theme · gradient accents · animated transitions
script.js     # Expression parser · safe evaluator · state machine · paywall flow
Engineering choices
-> Safe expression evaluator (regex-validated, no eval() of user input)
-> State machine for pending results (locked until unlocked)
-> Non-blocking modal flows using setInterval for countdowns
-> Toast notifications for unlock confirmations
-> Persistent trial counter via in-memory state
Capabilities

Calculator features

Basic Operations

Standard Arithmetic

Add, subtract, multiply, divide, modulo, and exponentiation. Operator chaining handled cleanly.

🔄

Negate & Backspace

Toggle the sign of the current value with ±. Smart backspace removes one character at a time.

🧹

Clear Modes

C (clear all), CE (clear current entry only). Decimal-point validation prevents double dots.

⌨️

Keyboard Support

Numbers, operators, Enter, Backspace, Escape, parentheses — every key is wired up natively.

Scientific Mode
📐

Trigonometry

sin, cos, tan with a DEG/RAD toggle. Switch units instantly without losing the current expression.

📊

Logarithms

Base-10 log and natural ln. Plus 10^x for exponential calculations.

Roots & Powers

Square root, square (x²), generic exponent (x^y), inverse (1/x), absolute value.

🔢

Constants & Factorial

π and e built in. Factorial (n!) for integers up to 170 (above that = Infinity).

Memory & History
💾

5 Memory Functions

MC (clear), MR (recall), MS (store), M+ (add), M− (subtract). Active-memory indicator on the display.

📜

History Panel

Last 50 calculations stored in a scrollable side panel. Click any item to reuse the result.

🗑️

Clear History

One-click reset for the entire history list. Toggle the panel on or off as needed.

📱

Responsive Layout

Mobile-first design. History panel collapses gracefully on small screens.

The Twist

The paywall system

The Three Tiers
💎

Premium (Rp99,000)

Triggers a fake "Processing Payment..." modal with a progress bar, gateway-style stage transitions ("Connecting to gateway", "Verifying card"), and a green checkmark on success. ~2.5 seconds total.

📺

Watch an Ad (Free)

Opens a fake banner ad — "Buy Premium Synthetic Grass!" — with a 5-second countdown. Skip button disables until the timer ends. Click-bait aesthetic.

🍽

Trial (3x Free)

Instant unlock, but the trial counter decrements. Once the counter hits zero, the option auto-disables with the label "Trial Ended".

Smart Behavior
🔒

Locked Result Preview

The blocked result shows the user's expression and a blurred placeholder. They know what they're missing.

Always Skippable

Pressing the close button (or clicking the backdrop) cancels the unlock. The result stays locked but the expression remains.

🍞

Toast Notifications

Auto-dismissing toasts confirm every successful unlock. Subtle, non-intrusive, with smooth fade animations.

😤

Trial Counter Footer

The footer shows remaining trial usage in real time. Once exhausted, it switches to "Trial Ended" in red.

Under the Hood

Code snippets

1. Hijacking the equals button
// Pre-validate first so we don't show paywall on syntax errors
function evaluateExpr() {
  if (state.expr === '') return;
  let result;
  try {
    result = evaluate(state.expr);
    if (!isFinite(result)) { setError('Math Error'); return; }
  } catch { setError('Syntax Error'); return; }

  // Stash result and show paywall — actual reveal waits for unlock
  pending.result = result;
  pending.exprDisplay = state.exprDisplay;
  showPaywall();
}
2. Safe expression evaluator
function evaluate(expr) {
  // Whitelist: only digits, operators, parens, dot
  if (!/^[\d+\-*/().%^ ]+$/.test(expr)) throw new Error('bad chars');
  // Convert ^ to ** for JavaScript exponentiation
  const js = expr.replace(/\^/g, '**');
  return Function(`"use strict"; return (${js});`)();
}
3. Ad countdown state machine
function unlockWithAd() {
  const TOTAL = 5000, TICK = 100;
  let elapsed = 0;
  paywall.adInterval = setInterval(() => {
    elapsed += TICK;
    fill.style.width = (elapsed / TOTAL) * 100 + '%';
    counter.textContent = Math.ceil((TOTAL - elapsed) / 1000);
    if (elapsed >= TOTAL) {
      clearInterval(paywall.adInterval);
      skipBtn.disabled = false;
    }
  }, TICK);
}
Get Started

Quick start

1. Get the Files
# Clone or download the three files
cd kalkulator-web/
ls
# index.html  style.css  script.js
2. Open It
# No build step. No npm install. Just open the file.
start index.html       # Windows
open index.html        # macOS
xdg-open index.html    # Linux
3. Try It
# Type any expression: 2 + 2
# Press = (or Enter)
# See the paywall
# Pick a tier — or close the modal to give up
▶ Launch the live demo
📁 Source projects/kalkulator-web/
🌐 Tech HTML5 · CSS3 · Vanilla JavaScript
📦 Dependencies None. Zero. Nada.
💾 Storage In-memory state only (refresh resets the trial)
🎯 Use case UX satire · paywall pattern study · weekend project