Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

117 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NBMEcalc — Free NBME Score Calculator

Use the free NBME Score Calculator →

NBMEcalc is a free USMLE Step score predictor for combining NBME, UWSA, Free 120, AMBOSS, and CMS results. It returns an independent planning estimate with a 95% confidence interval instead of presenting a single score as certain.

Live Tools

NBMEcalc is an independent educational tool and is not affiliated with or endorsed by NBME, FSMB, or USMLE.

Tech Stack

  • Framework: Next.js 15 (App Router) + TypeScript
  • Styling: Tailwind CSS + custom design tokens (mint #34D399 brand)
  • Components: Custom shadcn/ui-style primitives + Radix UI
  • Fonts: Plus Jakarta Sans (display + body), JetBrains Mono (numbers)
  • Hosting: Cloudflare Pages (edge runtime, @cloudflare/next-on-pages)
  • Database: Cloudflare D1 (Drizzle ORM)
  • PDF: Cloudflare Browser Rendering Worker, with an edge-safe fallback PDF
  • Payments: Stripe Checkout
  • Email: Postal HTTP API for magic-link email delivery
  • Tests: Vitest (pure-logic units in lib/**)

Quick Start

# 1. Install dependencies
npm install

# 2. Run dev server
npm run dev

# 3. Open browser
http://localhost:3000

Project Structure

nbmecalc/
├── app/
│   ├── layout.tsx          # Root layout with fonts + metadata
│   ├── page.tsx            # Homepage (composes all sections)
│   └── globals.css         # Tailwind + design tokens
├── components/
│   ├── ui/                 # shadcn primitives (Button, Input, Accordion, Badge)
│   ├── logo.tsx            # Bell-curve SVG logo (Logo + LogoMark)
│   ├── iphone-mockup.tsx   # iPhone 17 Pro CSS mockup
│   └── sections/
│       ├── nav.tsx         # 1. Top navigation
│       ├── hero.tsx        # 2. Hero with iPhone mockup
│       ├── logo-wall.tsx   # 3. Trust school list
│       ├── value-props.tsx # 4. 3-column value cards
│       ├── calculator.tsx  # 5. Live predictor (CORE)
│       ├── reviews.tsx     # 6. Reddit quotes
│       ├── stats.tsx       # 7. Stats with count-up
│       ├── how-it-works.tsx# 8. 3-step explainer
│       ├── comparison.tsx  # 9. Predictor comparison table
│       ├── resource-hub.tsx# 10. Featured guides
│       ├── reviewers.tsx   # 11. Physician reviewers
│       ├── faq.tsx         # 12. FAQ accordion
│       ├── blog-grid.tsx   # 13. 6 blog cards
│       ├── footer.tsx      # 14. Footer + legal
│       └── cookie-banner.tsx # First-visit disclaimer
├── lib/
│   ├── utils.ts            # cn() helper
│   ├── data.ts             # NBME conversion logic + types
│   └── email.ts            # Postal HTTP email delivery
├── workers/
│   └── pdf-renderer/       # Cloudflare Browser Rendering PDF Worker
├── public/
│   └── placeholders/       # AI image placeholders + prompts
├── tailwind.config.ts      # Mint color scale + animations
└── package.json

Design System

Token Value
Brand color #34D399 (mint-500)
Font Plus Jakarta Sans
Radius rounded-full (buttons), rounded-3xl (cards)
CTA Black button, white text

AI Image Generation (Evolink.ai Z Image Turbo)

All real-photo locations use <Image src="/images/[name].jpg" /> pointing at AI-generated files. The pictures are generated by the npm run gen:images script which calls the Evolink.ai Z Image Turbo API.

One-time setup

# 1. Get an API key at: https://evolink.ai/dashboard/keys
# 2. Copy the template and fill in your key
cp .env.example .env.local
# Then open .env.local and set EVOLINK_API_KEY=sk-xxxxx

Generate all 12 images

npm run gen:images          # only missing ones
npm run gen:images:force    # regenerate everything

# Or one specific image:
node scripts/gen-images.mjs reviewer-1
node scripts/gen-images.mjs --force blog-cover-nbme

Output: /public/images/*.jpg (already wired to all components).

Prompts live in /public/placeholders/*.prompt.txt — edit them to tweak style, then re-run with --force.

Image manifest

Component File Aspect Prompt file
Reviewers (×3) reviewer-1/2/3.jpg 1:1 reviewer-1/2/3.prompt.txt
Resource Hub (×3) blog-cover-nbme/ci/cram.jpg 4:3 blog-cover-*.prompt.txt
Blog Grid (×6) blog-cram/most-tested/...jpg 16:9 blog-*.prompt.txt

Pricing Tiers

  • Free: Single prediction, 95% CI, basic subject preview
  • Single Report — $14.99: Full PDF + 14-day plan + complete subject map
  • Lifetime — $34.99 one-time: Unlimited re-runs + Step 1/2/3 tracking + live timeline
  • Founding Lifetime — $19.99 one-time: Current founding-stage price with no automatic member cap or deadline

All sales are final. No refunds (digital product, delivered immediately).

Database (Cloudflare D1)

The app degrades gracefully without D1 (next dev: predictions still compute, just aren't persisted), so you can do most UI work without touching Cloudflare. Wire up D1 when you need to test the funnel or the rate limiter end-to-end.

Local D1 (one-time)

# 1. Apply the committed migrations against a local SQLite shadow DB.
npx wrangler d1 migrations apply nbmecalc-prod --local

# 2. Optionally peek at the schema.
npx wrangler d1 execute nbmecalc-prod --local --command "SELECT name FROM sqlite_schema WHERE type='table';"

wrangler keeps the local SQLite under .wrangler/state/v3/d1/ — it's git-ignored. To exercise the real edge runtime + D1 binding locally, use npm run preview instead of npm run dev.

Production D1 (one-time, before first deploy)

# 1. Create the database. Wrangler prints a database_id.
npx wrangler d1 create nbmecalc-prod

# 2. Paste the returned id into wrangler.toml (replaces REPLACE_ME_AFTER_FIRST_D1_CREATE).

# 3. Apply migrations to the remote DB.
npx wrangler d1 migrations apply nbmecalc-prod --remote

Adding a new table / column

# 1. Edit lib/db/schema.ts.
# 2. Generate a migration. Pick a descriptive --name.
npx drizzle-kit generate --name add_users_table
# 3. Apply locally (and later remote).
npx wrangler d1 migrations apply nbmecalc-prod --local

Migrations are committed to lib/db/migrations/ so production deploys don't need drizzle-kit on the server — they just wrangler d1 migrations apply --remote.

Lifetime launch order

Migration 0006_lifetime.sql creates the durable Lifetime entitlement table. The Founding offer is controlled by one deployment setting rather than a counter or timer:

# 1. Create two one-time Stripe Prices and configure them in Pages.
STRIPE_PRICE_LIFETIME_FOUNDING=price_... # $19.99
STRIPE_PRICE_LIFETIME_REGULAR=price_...  # $34.99
NEXT_PUBLIC_LIFETIME_FOUNDING_OFFER_ENABLED=true

# 2. Apply the entitlement migration and deploy.
npx wrangler d1 migrations apply nbmecalc-prod --remote
npm run deploy

To end the Founding offer for future purchases, set NEXT_PUBLIC_LIFETIME_FOUNDING_OFFER_ENABLED=false and redeploy. Checkout then selects and verifies the $34.99 Stripe Price server-side. Existing Lifetime entitlements are never changed by this switch.

For the GitHub Actions deployment, create or update the repository variable at Settings > Secrets and variables > Actions > Variables. If the variable is absent, the workflow intentionally defaults to true.

The buyer count remains available internally without appearing on the page:

npx wrangler d1 execute nbmecalc-prod --remote --command "SELECT COUNT(*) AS founding_members FROM lifetime_entitlements WHERE status = 'active' AND promotion_applied = 1;"

The Stripe webhook endpoint must receive checkout.session.completed, checkout.session.async_payment_succeeded, charge.refunded, and charge.dispute.created. charge.refunded is retained as an entitlement-safety event: it revokes access if an operator or payment network records a full refund despite the no-refund policy. It does not advertise or create a customer refund right.

PDF Renderer Worker

Premium report downloads use a separate Cloudflare Worker at workers/pdf-renderer. The Worker is routed at /api/_pdf-renderer/* and uses Cloudflare Browser Rendering (@cloudflare/puppeteer) to open the styled report page and export a color PDF with printBackground: true.

GitHub Actions deploys the Worker and the Pages app from this repository. It derives a shared PDF_RENDERER_SECRET from the existing CLOUDFLARE_API_TOKEN and writes it to both Cloudflare targets during deployment. If the Worker is unavailable at runtime, /api/report/[session_id]/pdf falls back to the edge-safe text PDF generator instead of failing the purchase flow.

The Cloudflare API token used by GitHub Actions needs permissions for:

  • Cloudflare Pages edit
  • Workers Scripts edit
  • Workers Browser Rendering
  • Zone route edit for nbmecalc.com/api/_pdf-renderer
  • Account read

Tests

npm test           # one-shot
npm run test:watch # watch mode

Vitest covers lib/data.ts (prediction + personalized analytics) and lib/rate-limit.ts (fail-open + bucket counting). The DB and Next route handlers are exercised end-to-end via npm run preview (wrangler) — there are no jsdom/React-side unit tests yet.

Email Delivery (Postal HTTP API)

Magic-link login emails are sent through Postal's HTTP API. Configure these variables in local .env.local and in Cloudflare Pages environment variables for production.

POSTAL_API_URL=https://mail.removexif.com/api/v1/send/message
POSTAL_API_KEY=your-postal-api-key
SMTP_FROM=noreply@nbmecalc.com

Use Cloudflare secret variables for POSTAL_API_KEY. Keep SMTP_FROM aligned with the current project domain.

SEO Targets (US, DataForSEO + GSC, 2026-06-28)

Keyword Volume/mo Status
nbme score converter 880 /nbme-score-conversion
nbme score conversion 880 /nbme-score-conversion
nbme score calculator 480 /
step 2 score predictor 480 /step-2-predictor
free 120 step 2 score conversion 480 /free-120-predictor
step 3 score predictor 320 /step-3-predictor
nbme cms forms 320 /cms-converter

Volumes are US monthly averages and close variants may share Google Ads volume. Do not sum variants as independent demand.

Disclaimer

nbmecalc is not affiliated with NBME, FSMB, USMLE, USMLE-Rx, AMBOSS, UWorld, or Kaplan. Predictions are statistical estimates for educational purposes only.

Roadmap

  • Cloudflare D1 schema + write API (/api/predict, predictions / reports / rate_limits / events)
  • Stripe Checkout for Single Report + Lifetime (server pricing, durable entitlement, signed webhook)
  • PDF report generation (@react-pdf/renderer on edge)
  • Personalized analytics layer (trajectory / source insight / target gap / postpone / weak)
  • Magic Link auth + user accounts
  • /dashboard with saved predictions timeline
  • Webhook → KV entitlement / D1 reports row
  • Publish individual NBME form pages only after each page has verified form-to-exam mapping, unique evidence, and enough information gain to pass the sitemap admission checklist.
  • Compare pages (/compare/vs-predictmystepscore, ...)
  • Blog MDX pipeline
  • Dynamic OG images

Contact

Reddit: u/nbmecalc · Email: hello@nbmecalc.com

About

Free NBME score calculator and USMLE Step score predictor with confidence intervals.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages