Skip to content

Repository files navigation

Fogg Behavior Scorer

A single-page tool that scores a described product feature against the Fogg Behavior Model (B = M · A ≥ k), plots where it lands relative to the action line, and proposes AI-generated fixes you can toggle to watch the point move in real time.

All scoring is done by your own LLM — bring your own Anthropic, OpenAI, or local Ollama key. No backend, no database, no server-side secrets. Credentials live only in sessionStorage and are cleared when the tab closes.


The model

BJ Fogg's Behavior Model states that a behavior occurs only when Motivation, Ability, and a Prompt converge at the same moment:

B = MAP

The model is primarily descriptive. Fogg identifies a curved trade-off boundary — the action line — on a Motivation × Ability plane and argues that a prompt will only trigger a behavior when the user's position is above it. He does not publish a closed-form equation for this curve; the shape he draws is hyperbolic.

Key references

  • BJ Fogg's Behavior Modelbehaviormodel.org
  • Fogg's Stanford Behavior Design Labbehaviordesign.stanford.edu
  • Tiny Habits (Fogg, 2019) — tinyhabits.com — the book that operationalises the model for product and habit design
  • Hooked (Nir Eyal, 2014) — a complementary model focused on the trigger–action–reward–investment loop
  • COM-B model (Michie et al., 2011) — a parallel behavioral framework from health psychology: Capability, Opportunity, Motivation → Behavior
  • EAST framework (Behavioural Insights Team, 2014) — four conditions for behavior change: Easy, Attractive, Social, Timely

Scoring function

Operationalisation

Because Fogg does not specify a formula, we operationalise the model as:

score  =  M × Aᵞ
passes =  score ≥ k

With γ = 1 (the default), this simplifies to M × A ≥ k. The code lives in lib/fogg.ts:

export const score  = (M, A, gamma = 1) => M * Math.pow(A, gamma);
export const passes = (M, A, k, gamma = 1) => score(M, A, gamma) >= k;

Why multiplicative?

Three natural ways to combine M and A:

Form Boundary shape Zero on one axis
M + A ≥ k Linear Behavior can pass with zero motivation if ability is high enough — contradicts Fogg
min(M, A) ≥ k L-shaped Both factors must independently clear k — too strict
M × A ≥ k Hyperbolic Zero on either axis → zero score, regardless of the other

The multiplicative form is the only one that produces a rectangular hyperbola — which is exactly what Fogg draws. It encodes two core claims of the model:

  1. Zero kills: if a user has no motivation, or the action requires superhuman effort, no prompt will ever work. The product is zero, not a penalty.
  2. Compensatory trade-off: high motivation can make hard actions achievable; extreme ease can partially offset weak motivation. But the exchange rate is convex — doubling ability does not double the score if motivation is already high.

The action line is defined as the curve where score = k exactly:

M = k / Aᵞ

Points above this curve satisfy M × Aᵞ > k and will respond to a prompt. Points below will not.

The gamma parameter

γ controls the curvature of the action line and how steeply it rises as ability decreases:

  • γ = 1 (default): symmetric. Halving ability requires halving motivation to stay on the line.
  • γ > 1: ability becomes the binding constraint faster. The curve steepens — useful for modelling habitual behaviors where even small friction causes dropout.
  • γ < 1: the line flattens. Motivation dominates; ability contributes less.

We fix γ = 1 for v1 to keep scores interpretable and to avoid introducing a parameter the LLM cannot reliably estimate. It is exposed as a named constant so it can be surfaced as an advanced toggle in a future version.

The threshold k

Unlike the standard Fogg diagram — which shows a single fixed action line — we make k variable per action. The LLM estimates it based on the action's stakes:

k range Typical action
0.05 – 0.15 Trivial, reversible, low-stakes (one-tap reorder, notification dismiss)
0.15 – 0.30 Some commitment or unfamiliarity (sign-up, form submission)
0.30 – 0.45 Meaningful friction or exposure (payment, permission grant)
0.45 – 0.60 Risky, irreversible, or high-commitment (identity verification, wallet approval)

After parsing, k is hard-clamped to [0.05, 0.60] to prevent degenerate curves: below 0.05 the action line sits so low that almost everything passes; above 0.60 it sits so high that nothing does.

Suggestion projection

When you toggle improvement suggestions, the tool projects a new position:

for (const suggestion of applied) {
  if (suggestion.lever === "motivation") M = clamp(M + suggestion.delta, 0, 1);
  else                                   A = clamp(A + suggestion.delta, 0, 1);
}

This is a linear first-order approximation: deltas are applied additively, one per lever, each clamped to [0, 1]. It is optimistic — real-world improvements rarely stack independently, and a delta of 0.15 on ability does not combine with another 0.15 ability delta to give 0.30 in practice. Treat the projected point as a directional signal, not a precise forecast.

Caveats

  • M × A ≥ k is our quantitative interpretation of Fogg's qualitative framework, not a formula he published.
  • The model assumes M and A are independent. In practice, perceived ease can raise motivation (and vice versa), so the factors interact.
  • All three numbers (M, A, k) are LLM estimates from a text description. They are structured heuristics, not measurements. Treat them as a prompt for discussion, not as ground truth.
  • Suggestion deltas are estimated order-of-magnitude lifts. Real A/B results will differ; the projection is useful for comparing directions, not magnitudes.

LLM estimation

The LLM estimates each variable for a typical target user at the moment they are prompted:

Variable Range What drives it
motivation 0 – 1 Desirability and immediacy of the outcome. High when payoff is concrete and felt now; low when abstract or delayed.
ability 0 – 1 Ease of the action. Starts at 1 and is reduced for each of: time cost, money cost, mental effort, number of steps, physical effort, unfamiliarity.
threshold k 0.05 – 0.6 Stakes of the action. Higher for irreversible, risky, or high-commitment behaviors.
confidence 0 – 1 Specificity of the input description. Vague one-liners score below 0.45; precise step-by-step descriptions can reach above 0.75.

It then proposes 2–4 concrete improvements targeting either the ability lever (remove friction) or the motivation lever (surface the payoff), each with an estimated delta of 0.05–0.35.


Features

  • Interactive action-line chart — recharts ComposedChart with a live "now" dot and an "after" projected dot that moves as you toggle suggestions
  • Three providers — Anthropic (with assistant prefill for JSON), OpenAI (json_object mode with retry), Ollama (local, CORS-aware)
  • Draggable split pane — resizable two-column layout on desktop (≥ 860px), ratio persisted in sessionStorage; stacks on mobile
  • Five built-in examples — from trivial one-tap reorders to six-step KYC flows and Web3 onboarding, covering the full range of the model
  • Keyboard-accessible tooltips — every (?) label is focusable and screen-reader-annotated
  • Full client-side privacy — no API routes, no server env vars, no telemetry. The only outbound request is directly to the chosen provider

Stack

Layer Choice
Framework Next.js 15 (App Router, TypeScript)
Chart recharts 3 — ComposedChart, Line, ReferenceDot
Validation zod 3 — strict schema + threshold clamping
Styling Plain inline styles + CSS custom properties (light theme only)
Deployment Vercel (zero env config)

Providers

Anthropic

Uses /v1/messages directly from the browser with the anthropic-dangerous-direct-browser-access: true header. JSON output is elicited via assistant-turn prefill ({) and parsed with fallback for models that don't support prefill.

OpenAI

Uses /v1/chat/completions with response_format: { type: "json_object" }. Retries once without response_format if the model returns 400.

Ollama (local)

Uses http://localhost:11434/api/chat with format: "json" and stream: false. A deployed page calling localhost is cross-origin — see Ollama + deployed URL below.


Getting started

Prerequisites

  • Node.js 18+
  • An API key for Anthropic or OpenAI, or a locally running Ollama instance

Run locally

git clone https://github.com/barancan/fogg-score.git
cd fogg-score
npm install
npm run dev

Open http://localhost:3000, select a provider, paste your key, pick a model, and connect.

Build for production

npm run build
npm start

Deploy to Vercel

Option A — CLI:

npm i -g vercel
vercel

Option B — GitHub import: Push to GitHub → vercel.com → "Add New Project" → import → Deploy.

No environment variables are required. The build produces a fully static client bundle.


Ollama + deployed URL

When the app is running on Vercel, a call to http://localhost:11434 is cross-origin and blocked by the browser unless Ollama was started with the right OLLAMA_ORIGINS value.

Start Ollama with:

OLLAMA_ORIGINS=https://your-app.vercel.app ollama serve

For local development against localhost:3000 at the same time:

OLLAMA_ORIGINS=http://localhost:3000,https://your-app.vercel.app ollama serve

Privacy model

What Where it lives When it's cleared
API key / base URL sessionStorage only Tab close or "Disconnect"
Selected model sessionStorage Tab close or "Disconnect"
Feature text Component state (RAM) Page reload
Split-pane ratio sessionStorage Tab close or "Disconnect"

No data is ever sent to any server other than the provider you choose. There are no Next.js API routes in this project. You can verify this in DevTools → Network: the only outbound request during analysis is to api.anthropic.com, api.openai.com, or your Ollama host.


Project structure

app/
  layout.tsx          — HTML shell, imports globals.css
  page.tsx            — sessionStorage hydration, Onboarding ↔ Workspace switch
  globals.css         — CSS custom properties (design tokens), base resets
components/
  Onboarding.tsx      — Provider cards → credentials → model picker → connect
  Workspace.tsx       — Analysis state, provider call, desktop/mobile layout
  ResizableSplit.tsx  — Draggable divider, ratio persisted to sessionStorage
  LeftPane.tsx        — Textarea, example cycler, suggestion toggles, sticky footer
  RightPane.tsx       — Verdict banner, confidence chip, stats grid, chart
  ActionLineChart.tsx — recharts ComposedChart: action line, now/after dots, connector
  Tooltip.tsx         — Keyboard-accessible (?) tooltip (aria-describedby)
lib/
  fogg.ts             — actionLine(), score(), passes(), project(), confidenceLabel()
  schema.ts           — zod schemas; threshold clamped to [0.05, 0.6] post-parse
  prompt.ts           — System prompt shared across all three providers
  providers.ts        — Anthropic / OpenAI / Ollama adapters (analyze + listModels)
  storage.ts          — sessionStorage get/set/clear for creds and split ratio
  types.ts            — Provider, Creds, AppError

Design tokens

ink      #1a1a17   body text
muted    #6b6a63   secondary text
faint    #9b9a90   placeholder / disabled
border   #e7e5dc   dividers
panel    #faf9f5   left pane background
paper    #ffffff   cards
page-bg  #f1efe8   page background

pass     #1D9E75   above the action line
fail     #E24B4A   below the action line
line     #639922   the action line itself (the one bold element)

ability badge    bg #E6F1FB  text #0C447C
motivation badge bg #FAEEDA  text #854F0B

Numbers (scores, deltas, k) render in ui-monospace. Everything else is system sans-serif. Light theme only.


License

MIT

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages