v1.0 — Satire as a Service
A scientific calculator that locks every result behind a paywall. Built with vanilla HTML, CSS, and JavaScript. No frameworks, no backend.
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.
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
-> 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
Add, subtract, multiply, divide, modulo, and exponentiation. Operator chaining handled cleanly.
Toggle the sign of the current value with ±. Smart backspace removes one character at a time.
C (clear all), CE (clear current entry only). Decimal-point validation prevents double dots.
Numbers, operators, Enter, Backspace, Escape, parentheses — every key is wired up natively.
sin, cos, tan with a DEG/RAD toggle. Switch units instantly without losing the current expression.
Base-10 log and natural ln. Plus 10^x for exponential calculations.
Square root, square (x²), generic exponent (x^y), inverse (1/x), absolute value.
π and e built in. Factorial (n!) for integers up to 170 (above that = Infinity).
MC (clear), MR (recall), MS (store), M+ (add), M− (subtract). Active-memory indicator on the display.
Last 50 calculations stored in a scrollable side panel. Click any item to reuse the result.
One-click reset for the entire history list. Toggle the panel on or off as needed.
Mobile-first design. History panel collapses gracefully on small screens.
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.
Opens a fake banner ad — "Buy Premium Synthetic Grass!" — with a 5-second countdown. Skip button disables until the timer ends. Click-bait aesthetic.
Instant unlock, but the trial counter decrements. Once the counter hits zero, the option auto-disables with the label "Trial Ended".
The blocked result shows the user's expression and a blurred placeholder. They know what they're missing.
Pressing the close button (or clicking the backdrop) cancels the unlock. The result stays locked but the expression remains.
Auto-dismissing toasts confirm every successful unlock. Subtle, non-intrusive, with smooth fade animations.
The footer shows remaining trial usage in real time. Once exhausted, it switches to "Trial Ended" in red.
// 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(); }
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});`)(); }
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); }
# Clone or download the three files cd kalkulator-web/ ls # index.html style.css script.js
# No build step. No npm install. Just open the file. start index.html # Windows open index.html # macOS xdg-open index.html # Linux
# Type any expression: 2 + 2 # Press = (or Enter) # See the paywall # Pick a tier — or close the modal to give up