Playbook

How to build a bidding website

This site is one vertical: best reads. The product underneath is a pay-to-rank board. Same engine works for best SaaS, best YouTube video, best Mac app, best CMS — you only change the noun, the rules, and the theme.

The idea

Attention is ranked by money, in public. No accounts. No likes. The listing with the most paid bids sits on top until someone pays more. That is the whole loop.

  1. 01

    Someone drops a thing

    A URL, an app, a video, a CMS — whatever the vertical is. You preview it so the board looks real.

  2. 02

    They bid money

    Minimum $1. The input can start at the current #1 bid. They can type any amount at or above the floor.

  3. 03

    They pay

    You create a pending checkout, send them to Stripe, Polar, or Dodo, then fulfill only after payment succeeds.

  4. 04

    The board re-ranks

    Rank is cumulative. A second person can bid on the same listing and the total stacks. Highest total stays first.

Rules that make it work

Every prompt below assumes Next.js App Router, TypeScript, Tailwind, and shadcn/ui. Paste it into Cursor, Claude, or Codex and let the agent scaffold the repo.

Keys and official links

Each prompt includes these dashboards so the agent (and you) know exactly where to create API keys, products, and webhooks.

database

Firebase / Firestore

  • NEXT_PUBLIC_FIREBASE_API_KEY

    Firebase Console → Project settings → Your apps → Web app → apiKey

  • NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN

    Same page → authDomain

  • NEXT_PUBLIC_FIREBASE_PROJECT_ID

    Same page → projectId

  • NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET

    Same page → storageBucket

  • NEXT_PUBLIC_FIREBASE_APP_ID

    Same page → appId

  • FIREBASE_SERVICE_ACCOUNT_JSON

    Project settings → Service accounts → Generate new private key. Store the JSON as one env string (server only).

payments

Stripe

  • STRIPE_SECRET_KEY

    https://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.

  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY

    Same page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.

  • STRIPE_WEBHOOK_SECRET

    https://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe

payments

Dodo Payments

  • DODO_PAYMENTS_API_KEY

    https://app.dodopayments.com/developer/api-keys → Add API key (Bearer). Server only.

  • DODO_PAYMENTS_WEBHOOK_KEY

    https://app.dodopayments.com/developer/webhooks → endpoint Overview → signing secret

  • DODO_PAYMENTS_PRODUCT_ID

    Dashboard → Products → create a one-time Pay What You Want product with $1 minimum

  • DODO_PAYMENTS_ENVIRONMENT

    Must be test_mode or live_mode. Never the string "live".

AI prompts by stack

Pick your database and payment provider. Copy the prompt. The agent should give you a running board the same day you add keys.

Firebase Firestore · Stripe Checkout

Firebase + Stripe

Fastest Google stack. Use Admin SDK on the server only.

Next.js + shadcn + Tailwind

  • NEXT_PUBLIC_SITE_URLYour public origin, e.g. http://localhost:3000 or https://yourdomain.com

Firebase / Firestore

  • NEXT_PUBLIC_FIREBASE_API_KEYFirebase Console → Project settings → Your apps → Web app → apiKey
  • NEXT_PUBLIC_FIREBASE_AUTH_DOMAINSame page → authDomain
  • NEXT_PUBLIC_FIREBASE_PROJECT_IDSame page → projectId
  • NEXT_PUBLIC_FIREBASE_STORAGE_BUCKETSame page → storageBucket
  • NEXT_PUBLIC_FIREBASE_APP_IDSame page → appId
  • FIREBASE_SERVICE_ACCOUNT_JSONProject settings → Service accounts → Generate new private key. Store the JSON as one env string (server only).

Stripe

  • STRIPE_SECRET_KEYhttps://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYSame page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
  • STRIPE_WEBHOOK_SECREThttps://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe
Follow official docs only. Use the URLs, npm packages, and key locations in PLATFORM DETAILS. Do not invent SDK method names — look them up from those links.

### Next.js + shadcn + Tailwind
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Next.js App Router: https://nextjs.org/docs/app
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- Environment variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- shadcn install (Next.js): https://ui.shadcn.com/docs/installation/next
- shadcn components: https://ui.shadcn.com/docs/components
- Tailwind v3 + Next.js: https://v3.tailwindcss.com/docs/guides/nextjs
- Vercel deploy: https://vercel.com/docs/getting-started-with-vercel
- Vercel env vars: https://vercel.com/docs/projects/environment-variables

NPM PACKAGES
- next
- react
- tailwindcss
- class-variance-authority
- lucide-react

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SITE_URL: Your public origin, e.g. http://localhost:3000 or https://yourdomain.com

SETUP STEPS
npx create-next-app@latest . --ts --tailwind --app --src-dir
npx shadcn@latest init
npx shadcn@latest add button input card badge

### Firebase / Firestore
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Firebase Console: https://console.firebase.google.com/
- Create a web app: https://firebase.google.com/docs/web/setup
- Firestore: https://firebase.google.com/docs/firestore
- Firestore security rules: https://firebase.google.com/docs/firestore/security/get-started
- Admin / service account: https://firebase.google.com/docs/admin/setup
- firebase npm: https://www.npmjs.com/package/firebase
- firebase-admin npm: https://www.npmjs.com/package/firebase-admin

NPM PACKAGES
- firebase
- firebase-admin

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_FIREBASE_API_KEY: Firebase Console → Project settings → Your apps → Web app → apiKey
- NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: Same page → authDomain
- NEXT_PUBLIC_FIREBASE_PROJECT_ID: Same page → projectId
- NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: Same page → storageBucket
- NEXT_PUBLIC_FIREBASE_APP_ID: Same page → appId
- FIREBASE_SERVICE_ACCOUNT_JSON: Project settings → Service accounts → Generate new private key. Store the JSON as one env string (server only).

SETUP STEPS
1. https://console.firebase.google.com/ → Create project
2. Build → Firestore Database → Create (test mode only for local, then lock rules)
3. Project settings (gear) → General → Your apps → Add web app → copy the firebaseConfig object
4. Project settings → Service accounts → Generate new private key → FIREBASE_SERVICE_ACCOUNT_JSON
5. Never put the service account in NEXT_PUBLIC_*

### Stripe
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Stripe Dashboard: https://dashboard.stripe.com
- API keys: https://dashboard.stripe.com/apikeys
- Webhooks: https://dashboard.stripe.com/webhooks
- Checkout Sessions: https://docs.stripe.com/payments/checkout/how-checkout-works
- Create a Checkout Session API: https://docs.stripe.com/api/checkout/sessions/create
- stripe npm: https://www.npmjs.com/package/stripe
- Stripe CLI (local webhooks): https://docs.stripe.com/stripe-cli
- Test cards: https://docs.stripe.com/testing#cards

NPM PACKAGES
- stripe

API KEYS — name and exactly where to create them
- STRIPE_SECRET_KEY: https://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: Same page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
- STRIPE_WEBHOOK_SECRET: https://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe

SETUP STEPS
1. https://dashboard.stripe.com → sign up, stay in Test mode
2. Developers → API keys → STRIPE_SECRET_KEY
3. Create Checkout Session: mode=payment, line_items price_data.unit_amount = bid cents, metadata.pending_id
4. Success URL: {SITE}/success?checkout_id={pending_id}
5. Webhook endpoint: {SITE}/api/webhooks/stripe event checkout.session.completed
6. Local: stripe listen --forward-to localhost:3000/api/webhooks/stripe → STRIPE_WEBHOOK_SECRET
7. Test card: 4242 4242 4242 4242

---

You are scaffolding a pay-to-rank bidding board (same product as ihatereading.lol).

PRODUCT
A public leaderboard. Anyone pastes a URL (or listing), types a bid of $1+, pays, and that listing rises by CUMULATIVE money. Highest total bid is #1. No user accounts. Anonymous is fine.

USER JOURNEY
1. Land on / — hero + bid form + live board
2. Paste URL → preview title/image/description (Open Graph)
3. Set bid (number stepper, min $1 / 100 cents). Default the input to the current highest board bid.
4. Pay → redirect to provider checkout
5. Return /success?checkout_id=... → poll status until live → show rank + confetti
6. Board shows rank, thumbnail, title, total $, bid count. Click a row → /listing/[id] to add another bid (stack).

DOMAIN (use these names, do not invent a different model)
- listings: id, url, type, title, description, thumbnail_url, favicon_url, author_name, submitted_by_name, total_bid_cents, bid_count, bid_cents_last_24h, status (pending|live|rejected), created_at, last_bid_at
- bids: id, listing_id, amount_cents, bidder_name, provider, checkout_id, payment_id, customer_email, created_at
- pending_checkouts: id, url, preview fields, bid_amount_cents, existing_listing_id (null = new URL), provider_session_id, created_at
- Optional: presence + stats counters for "N online" / views

RULES (must implement)
- Min bid $1 (400 if amount_cents < 100)
- Normalize URLs (https, strip tracking params). Same URL cannot create a second listing — return 409 with existing_listing_id; user must stack a bid instead
- Stacking: paying against existing_listing_id adds amount to total_bid_cents and bid_count
- Rank all-time: ORDER BY total_bid_cents DESC, last_bid_at DESC
- Rank trending: ORDER BY bid_cents_last_24h DESC
- Payments are idempotent: never insert the same payment_id twice
- Flow: create pending_checkout → create provider checkout with metadata { pending_id, bid_amount_cents } → webhook OR success-page poll fulfills → create/update listing + insert bid
- Server-only writes for money (never let the browser increment totals)
- No auth required
- Admin page optional: password gate to reject a listing

PAGES
- / landing (form + board)
- /listing/[id] detail + stack bid + bid history
- /success payment confirmation
- /stats simple counters
- shadcn/ui + Tailwind. Light theme. Mobile-first: stack board cards on small screens.

APIS
- POST /api/preview { url }
- POST /api/checkout { url, preview, bid_amount_cents, existing_listing_id? }
- GET /api/status?checkout_id=
- POST /api/webhooks/[provider]
- GET /api/board?sort=all_time|trending

SHIP
- .env.example with every key AND a comment above each key: what it is + the exact dashboard URL to create it
- README: numbered setup using the PLATFORM DETAILS links below
- Seed + clear scripts
- TypeScript strict. Do not add extra features (auth, AI ranking, comments).


STACK (do not substitute)
- Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn/ui, React Query or SWR
- Database: Firebase Firestore
- Payments: Stripe Checkout (Pay What You Want / adaptive pricing)

ADAPTER RULES
Firestore collections: listings, bids, pending_checkouts.
Use firebase-admin in API routes for all writes. Client may read listings for the live board.
Stripe: checkout.sessions.create with mode=payment, line_items price_data unit_amount = bid_amount_cents, metadata.pending_id.
Webhook: checkout.session.completed. Fulfill from session.metadata.pending_id.
Success URL: {SITE}/success?checkout_id={pending_id}
Idempotency key = Stripe session.id / payment_intent.

ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- NEXT_PUBLIC_FIREBASE_API_KEY
- NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN
- NEXT_PUBLIC_FIREBASE_PROJECT_ID
- NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET
- NEXT_PUBLIC_FIREBASE_APP_ID
- FIREBASE_SERVICE_ACCOUNT_JSON
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET

Copy every official link from PLATFORM DETAILS into README.md under Setup.
Build the complete runnable app now. After files exist, print: install, env, migrate/seed, webhook listen, npm run dev.

Firebase Firestore · Polar.sh Checkout

Firebase + Polar

Indie-friendly billing. Polar checkout + Firestore.

Next.js + shadcn + Tailwind

  • NEXT_PUBLIC_SITE_URLYour public origin, e.g. http://localhost:3000 or https://yourdomain.com

Firebase / Firestore

  • NEXT_PUBLIC_FIREBASE_API_KEYFirebase Console → Project settings → Your apps → Web app → apiKey
  • NEXT_PUBLIC_FIREBASE_AUTH_DOMAINSame page → authDomain
  • NEXT_PUBLIC_FIREBASE_PROJECT_IDSame page → projectId
  • NEXT_PUBLIC_FIREBASE_STORAGE_BUCKETSame page → storageBucket
  • NEXT_PUBLIC_FIREBASE_APP_IDSame page → appId
  • FIREBASE_SERVICE_ACCOUNT_JSONProject settings → Service accounts → Generate new private key. Store the JSON as one env string (server only).

Polar

  • POLAR_ACCESS_TOKENPolar org settings → Organization Access Token (polar_oat_… or polar_pat_…). Server only.
  • POLAR_WEBHOOK_SECRETOrg settings → Webhooks → Add endpoint → generate signing secret
  • POLAR_PRODUCT_IDProducts → create a one-time product → copy the product id
  • POLAR_SERVEROptional. sandbox vs production. See Polar Next.js guide.
Follow official docs only. Use the URLs, npm packages, and key locations in PLATFORM DETAILS. Do not invent SDK method names — look them up from those links.

### Next.js + shadcn + Tailwind
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Next.js App Router: https://nextjs.org/docs/app
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- Environment variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- shadcn install (Next.js): https://ui.shadcn.com/docs/installation/next
- shadcn components: https://ui.shadcn.com/docs/components
- Tailwind v3 + Next.js: https://v3.tailwindcss.com/docs/guides/nextjs
- Vercel deploy: https://vercel.com/docs/getting-started-with-vercel
- Vercel env vars: https://vercel.com/docs/projects/environment-variables

NPM PACKAGES
- next
- react
- tailwindcss
- class-variance-authority
- lucide-react

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SITE_URL: Your public origin, e.g. http://localhost:3000 or https://yourdomain.com

SETUP STEPS
npx create-next-app@latest . --ts --tailwind --app --src-dir
npx shadcn@latest init
npx shadcn@latest add button input card badge

### Firebase / Firestore
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Firebase Console: https://console.firebase.google.com/
- Create a web app: https://firebase.google.com/docs/web/setup
- Firestore: https://firebase.google.com/docs/firestore
- Firestore security rules: https://firebase.google.com/docs/firestore/security/get-started
- Admin / service account: https://firebase.google.com/docs/admin/setup
- firebase npm: https://www.npmjs.com/package/firebase
- firebase-admin npm: https://www.npmjs.com/package/firebase-admin

NPM PACKAGES
- firebase
- firebase-admin

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_FIREBASE_API_KEY: Firebase Console → Project settings → Your apps → Web app → apiKey
- NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: Same page → authDomain
- NEXT_PUBLIC_FIREBASE_PROJECT_ID: Same page → projectId
- NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: Same page → storageBucket
- NEXT_PUBLIC_FIREBASE_APP_ID: Same page → appId
- FIREBASE_SERVICE_ACCOUNT_JSON: Project settings → Service accounts → Generate new private key. Store the JSON as one env string (server only).

SETUP STEPS
1. https://console.firebase.google.com/ → Create project
2. Build → Firestore Database → Create (test mode only for local, then lock rules)
3. Project settings (gear) → General → Your apps → Add web app → copy the firebaseConfig object
4. Project settings → Service accounts → Generate new private key → FIREBASE_SERVICE_ACCOUNT_JSON
5. Never put the service account in NEXT_PUBLIC_*

### Polar
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Polar: https://polar.sh
- Polar docs: https://polar.sh/docs
- Next.js guide: https://polar.sh/docs/guides/nextjs
- Access tokens: https://polar.sh/docs/integrate/oat
- Webhook endpoints: https://polar.sh/docs/integrate/webhooks/endpoints
- Checkout API: https://polar.sh/docs/api-reference/checkouts
- @polar-sh/nextjs: https://www.npmjs.com/package/@polar-sh/nextjs
- @polar-sh/sdk: https://www.npmjs.com/package/@polar-sh/sdk

NPM PACKAGES
- @polar-sh/sdk
- @polar-sh/nextjs

API KEYS — name and exactly where to create them
- POLAR_ACCESS_TOKEN: Polar org settings → Organization Access Token (polar_oat_… or polar_pat_…). Server only.
- POLAR_WEBHOOK_SECRET: Org settings → Webhooks → Add endpoint → generate signing secret
- POLAR_PRODUCT_ID: Products → create a one-time product → copy the product id
- POLAR_SERVER: Optional. sandbox vs production. See Polar Next.js guide.

SETUP STEPS
1. https://polar.sh → create organization (use sandbox while building)
2. Org settings → create Organization Access Token → POLAR_ACCESS_TOKEN
3. Products → one-time product, min $1 → POLAR_PRODUCT_ID
4. Settings → Webhooks → Add Endpoint → {SITE}/api/webhooks/polar → copy secret → POLAR_WEBHOOK_SECRET
5. Subscribe to order.paid (and checkout events you need)
6. Local tunnel: polar listen http://localhost:3000/  (https://polar.sh/docs/integrate/webhooks/endpoints)
7. Put pending_id in checkout metadata

---

You are scaffolding a pay-to-rank bidding board (same product as ihatereading.lol).

PRODUCT
A public leaderboard. Anyone pastes a URL (or listing), types a bid of $1+, pays, and that listing rises by CUMULATIVE money. Highest total bid is #1. No user accounts. Anonymous is fine.

USER JOURNEY
1. Land on / — hero + bid form + live board
2. Paste URL → preview title/image/description (Open Graph)
3. Set bid (number stepper, min $1 / 100 cents). Default the input to the current highest board bid.
4. Pay → redirect to provider checkout
5. Return /success?checkout_id=... → poll status until live → show rank + confetti
6. Board shows rank, thumbnail, title, total $, bid count. Click a row → /listing/[id] to add another bid (stack).

DOMAIN (use these names, do not invent a different model)
- listings: id, url, type, title, description, thumbnail_url, favicon_url, author_name, submitted_by_name, total_bid_cents, bid_count, bid_cents_last_24h, status (pending|live|rejected), created_at, last_bid_at
- bids: id, listing_id, amount_cents, bidder_name, provider, checkout_id, payment_id, customer_email, created_at
- pending_checkouts: id, url, preview fields, bid_amount_cents, existing_listing_id (null = new URL), provider_session_id, created_at
- Optional: presence + stats counters for "N online" / views

RULES (must implement)
- Min bid $1 (400 if amount_cents < 100)
- Normalize URLs (https, strip tracking params). Same URL cannot create a second listing — return 409 with existing_listing_id; user must stack a bid instead
- Stacking: paying against existing_listing_id adds amount to total_bid_cents and bid_count
- Rank all-time: ORDER BY total_bid_cents DESC, last_bid_at DESC
- Rank trending: ORDER BY bid_cents_last_24h DESC
- Payments are idempotent: never insert the same payment_id twice
- Flow: create pending_checkout → create provider checkout with metadata { pending_id, bid_amount_cents } → webhook OR success-page poll fulfills → create/update listing + insert bid
- Server-only writes for money (never let the browser increment totals)
- No auth required
- Admin page optional: password gate to reject a listing

PAGES
- / landing (form + board)
- /listing/[id] detail + stack bid + bid history
- /success payment confirmation
- /stats simple counters
- shadcn/ui + Tailwind. Light theme. Mobile-first: stack board cards on small screens.

APIS
- POST /api/preview { url }
- POST /api/checkout { url, preview, bid_amount_cents, existing_listing_id? }
- GET /api/status?checkout_id=
- POST /api/webhooks/[provider]
- GET /api/board?sort=all_time|trending

SHIP
- .env.example with every key AND a comment above each key: what it is + the exact dashboard URL to create it
- README: numbered setup using the PLATFORM DETAILS links below
- Seed + clear scripts
- TypeScript strict. Do not add extra features (auth, AI ranking, comments).


STACK (do not substitute)
- Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn/ui, React Query or SWR
- Database: Firebase Firestore
- Payments: Polar.sh Checkout

ADAPTER RULES
Firestore + firebase-admin for writes (same collections as above).
Polar: create a one-time product. checkout.create with product_id, custom amount in cents if supported, metadata pending_id.
Webhook: order.paid. Verify Polar signature with POLAR_WEBHOOK_SECRET.
Fulfill exactly once using Polar order id as payment_id.
Success URL includes your pending_id, not only Polar's checkout id.

ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- NEXT_PUBLIC_FIREBASE_API_KEY
- NEXT_PUBLIC_FIREBASE_PROJECT_ID
- FIREBASE_SERVICE_ACCOUNT_JSON
- POLAR_ACCESS_TOKEN
- POLAR_WEBHOOK_SECRET
- POLAR_PRODUCT_ID

Copy every official link from PLATFORM DETAILS into README.md under Setup.
Build the complete runnable app now. After files exist, print: install, env, migrate/seed, webhook listen, npm run dev.

Firebase Firestore · Dodo Payments

Firebase + Dodo

Same payments as ihatereading.lol. Closest clone.

Next.js + shadcn + Tailwind

  • NEXT_PUBLIC_SITE_URLYour public origin, e.g. http://localhost:3000 or https://yourdomain.com

Firebase / Firestore

  • NEXT_PUBLIC_FIREBASE_API_KEYFirebase Console → Project settings → Your apps → Web app → apiKey
  • NEXT_PUBLIC_FIREBASE_AUTH_DOMAINSame page → authDomain
  • NEXT_PUBLIC_FIREBASE_PROJECT_IDSame page → projectId
  • NEXT_PUBLIC_FIREBASE_STORAGE_BUCKETSame page → storageBucket
  • NEXT_PUBLIC_FIREBASE_APP_IDSame page → appId
  • FIREBASE_SERVICE_ACCOUNT_JSONProject settings → Service accounts → Generate new private key. Store the JSON as one env string (server only).

Dodo Payments

  • DODO_PAYMENTS_API_KEYhttps://app.dodopayments.com/developer/api-keys → Add API key (Bearer). Server only.
  • DODO_PAYMENTS_WEBHOOK_KEYhttps://app.dodopayments.com/developer/webhooks → endpoint Overview → signing secret
  • DODO_PAYMENTS_PRODUCT_IDDashboard → Products → create a one-time Pay What You Want product with $1 minimum
  • DODO_PAYMENTS_ENVIRONMENTMust be test_mode or live_mode. Never the string "live".
Follow official docs only. Use the URLs, npm packages, and key locations in PLATFORM DETAILS. Do not invent SDK method names — look them up from those links.

### Next.js + shadcn + Tailwind
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Next.js App Router: https://nextjs.org/docs/app
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- Environment variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- shadcn install (Next.js): https://ui.shadcn.com/docs/installation/next
- shadcn components: https://ui.shadcn.com/docs/components
- Tailwind v3 + Next.js: https://v3.tailwindcss.com/docs/guides/nextjs
- Vercel deploy: https://vercel.com/docs/getting-started-with-vercel
- Vercel env vars: https://vercel.com/docs/projects/environment-variables

NPM PACKAGES
- next
- react
- tailwindcss
- class-variance-authority
- lucide-react

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SITE_URL: Your public origin, e.g. http://localhost:3000 or https://yourdomain.com

SETUP STEPS
npx create-next-app@latest . --ts --tailwind --app --src-dir
npx shadcn@latest init
npx shadcn@latest add button input card badge

### Firebase / Firestore
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Firebase Console: https://console.firebase.google.com/
- Create a web app: https://firebase.google.com/docs/web/setup
- Firestore: https://firebase.google.com/docs/firestore
- Firestore security rules: https://firebase.google.com/docs/firestore/security/get-started
- Admin / service account: https://firebase.google.com/docs/admin/setup
- firebase npm: https://www.npmjs.com/package/firebase
- firebase-admin npm: https://www.npmjs.com/package/firebase-admin

NPM PACKAGES
- firebase
- firebase-admin

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_FIREBASE_API_KEY: Firebase Console → Project settings → Your apps → Web app → apiKey
- NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: Same page → authDomain
- NEXT_PUBLIC_FIREBASE_PROJECT_ID: Same page → projectId
- NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: Same page → storageBucket
- NEXT_PUBLIC_FIREBASE_APP_ID: Same page → appId
- FIREBASE_SERVICE_ACCOUNT_JSON: Project settings → Service accounts → Generate new private key. Store the JSON as one env string (server only).

SETUP STEPS
1. https://console.firebase.google.com/ → Create project
2. Build → Firestore Database → Create (test mode only for local, then lock rules)
3. Project settings (gear) → General → Your apps → Add web app → copy the firebaseConfig object
4. Project settings → Service accounts → Generate new private key → FIREBASE_SERVICE_ACCOUNT_JSON
5. Never put the service account in NEXT_PUBLIC_*

### Dodo Payments
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Dodo Dashboard: https://app.dodopayments.com/
- API keys: https://app.dodopayments.com/developer/api-keys
- Webhooks: https://app.dodopayments.com/developer/webhooks
- Integration guide: https://docs.dodopayments.com/developer-resources/integration-guide
- Checkout sessions: https://docs.dodopayments.com/developer-resources/checkout-sessions
- Webhook docs: https://docs.dodopayments.com/developer-resources/webhooks
- Test vs live mode: https://docs.dodopayments.com/miscellaneous/test-mode-vs-live-mode
- dodopayments npm: https://www.npmjs.com/package/dodopayments
- @dodopayments/nextjs: https://www.npmjs.com/package/@dodopayments/nextjs

NPM PACKAGES
- dodopayments
- @dodopayments/nextjs

API KEYS — name and exactly where to create them
- DODO_PAYMENTS_API_KEY: https://app.dodopayments.com/developer/api-keys → Add API key (Bearer). Server only.
- DODO_PAYMENTS_WEBHOOK_KEY: https://app.dodopayments.com/developer/webhooks → endpoint Overview → signing secret
- DODO_PAYMENTS_PRODUCT_ID: Dashboard → Products → create a one-time Pay What You Want product with $1 minimum
- DODO_PAYMENTS_ENVIRONMENT: Must be test_mode or live_mode. Never the string "live".

SETUP STEPS
1. https://app.dodopayments.com/ → create merchant account
2. Developer → API Keys → DODO_PAYMENTS_API_KEY
3. Products → one-time Pay What You Want, minimum $1 → DODO_PAYMENTS_PRODUCT_ID
4. checkoutSessions.create with product_cart[].amount in CENTS
5. Developer → Webhooks → URL {SITE}/api/webhooks/dodo → DODO_PAYMENTS_WEBHOOK_KEY
6. DODO_PAYMENTS_ENVIRONMENT=test_mode until you go live (then live_mode)
7. Return URL {SITE}/success?checkout_id={pending_id}
8. Also poll GET /api/status — webhooks can arrive after the browser returns

---

You are scaffolding a pay-to-rank bidding board (same product as ihatereading.lol).

PRODUCT
A public leaderboard. Anyone pastes a URL (or listing), types a bid of $1+, pays, and that listing rises by CUMULATIVE money. Highest total bid is #1. No user accounts. Anonymous is fine.

USER JOURNEY
1. Land on / — hero + bid form + live board
2. Paste URL → preview title/image/description (Open Graph)
3. Set bid (number stepper, min $1 / 100 cents). Default the input to the current highest board bid.
4. Pay → redirect to provider checkout
5. Return /success?checkout_id=... → poll status until live → show rank + confetti
6. Board shows rank, thumbnail, title, total $, bid count. Click a row → /listing/[id] to add another bid (stack).

DOMAIN (use these names, do not invent a different model)
- listings: id, url, type, title, description, thumbnail_url, favicon_url, author_name, submitted_by_name, total_bid_cents, bid_count, bid_cents_last_24h, status (pending|live|rejected), created_at, last_bid_at
- bids: id, listing_id, amount_cents, bidder_name, provider, checkout_id, payment_id, customer_email, created_at
- pending_checkouts: id, url, preview fields, bid_amount_cents, existing_listing_id (null = new URL), provider_session_id, created_at
- Optional: presence + stats counters for "N online" / views

RULES (must implement)
- Min bid $1 (400 if amount_cents < 100)
- Normalize URLs (https, strip tracking params). Same URL cannot create a second listing — return 409 with existing_listing_id; user must stack a bid instead
- Stacking: paying against existing_listing_id adds amount to total_bid_cents and bid_count
- Rank all-time: ORDER BY total_bid_cents DESC, last_bid_at DESC
- Rank trending: ORDER BY bid_cents_last_24h DESC
- Payments are idempotent: never insert the same payment_id twice
- Flow: create pending_checkout → create provider checkout with metadata { pending_id, bid_amount_cents } → webhook OR success-page poll fulfills → create/update listing + insert bid
- Server-only writes for money (never let the browser increment totals)
- No auth required
- Admin page optional: password gate to reject a listing

PAGES
- / landing (form + board)
- /listing/[id] detail + stack bid + bid history
- /success payment confirmation
- /stats simple counters
- shadcn/ui + Tailwind. Light theme. Mobile-first: stack board cards on small screens.

APIS
- POST /api/preview { url }
- POST /api/checkout { url, preview, bid_amount_cents, existing_listing_id? }
- GET /api/status?checkout_id=
- POST /api/webhooks/[provider]
- GET /api/board?sort=all_time|trending

SHIP
- .env.example with every key AND a comment above each key: what it is + the exact dashboard URL to create it
- README: numbered setup using the PLATFORM DETAILS links below
- Seed + clear scripts
- TypeScript strict. Do not add extra features (auth, AI ranking, comments).


STACK (do not substitute)
- Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn/ui, React Query or SWR
- Database: Firebase Firestore
- Payments: Dodo Payments (Pay What You Want product, min $1)

ADAPTER RULES
Match ihatereading.lol: checkoutSessions.create with product_cart[].amount in CENTS.
Environment must be test_mode or live_mode (never "live").
Webhook POST /api/webhooks/dodo using @dodopayments/nextjs onPaymentSucceeded.
Metadata: pending_id, bid_amount_cents. Return URL /success?checkout_id={pending_id}.
Also poll GET /api/status and retrieve payment if webhook is late.
Store provider fields as checkout_id / payment_id (not dodo_* names).

ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- NEXT_PUBLIC_FIREBASE_API_KEY
- NEXT_PUBLIC_FIREBASE_PROJECT_ID
- FIREBASE_SERVICE_ACCOUNT_JSON
- DODO_PAYMENTS_API_KEY
- DODO_PAYMENTS_PRODUCT_ID
- DODO_PAYMENTS_WEBHOOK_KEY
- DODO_PAYMENTS_ENVIRONMENT=test_mode

Copy every official link from PLATFORM DETAILS into README.md under Setup.
Build the complete runnable app now. After files exist, print: install, env, migrate/seed, webhook listen, npm run dev.

Supabase (Postgres + RLS) · Stripe Checkout

Supabase + Stripe

Best default for most buyers. SQL ranking + Stripe.

Next.js + shadcn + Tailwind

  • NEXT_PUBLIC_SITE_URLYour public origin, e.g. http://localhost:3000 or https://yourdomain.com

Supabase

  • NEXT_PUBLIC_SUPABASE_URLSupabase Dashboard → Project Settings → API → Project URL
  • NEXT_PUBLIC_SUPABASE_ANON_KEYSame page → Project API keys → anon / public
  • SUPABASE_SERVICE_ROLE_KEYSame page → service_role (secret). Server only. Bypasses RLS.

Stripe

  • STRIPE_SECRET_KEYhttps://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYSame page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
  • STRIPE_WEBHOOK_SECREThttps://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe
Follow official docs only. Use the URLs, npm packages, and key locations in PLATFORM DETAILS. Do not invent SDK method names — look them up from those links.

### Next.js + shadcn + Tailwind
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Next.js App Router: https://nextjs.org/docs/app
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- Environment variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- shadcn install (Next.js): https://ui.shadcn.com/docs/installation/next
- shadcn components: https://ui.shadcn.com/docs/components
- Tailwind v3 + Next.js: https://v3.tailwindcss.com/docs/guides/nextjs
- Vercel deploy: https://vercel.com/docs/getting-started-with-vercel
- Vercel env vars: https://vercel.com/docs/projects/environment-variables

NPM PACKAGES
- next
- react
- tailwindcss
- class-variance-authority
- lucide-react

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SITE_URL: Your public origin, e.g. http://localhost:3000 or https://yourdomain.com

SETUP STEPS
npx create-next-app@latest . --ts --tailwind --app --src-dir
npx shadcn@latest init
npx shadcn@latest add button input card badge

### Supabase
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Supabase Dashboard: https://supabase.com/dashboard
- API keys: https://supabase.com/docs/guides/api/api-keys
- Next.js guide: https://supabase.com/docs/guides/getting-started/quickstarts/nextjs
- Server-side Next.js: https://supabase.com/docs/guides/auth/server-side/nextjs
- Row Level Security: https://supabase.com/docs/guides/auth/row-level-security
- JS client: https://supabase.com/docs/reference/javascript/introduction
- @supabase/ssr npm: https://www.npmjs.com/package/@supabase/ssr

NPM PACKAGES
- @supabase/supabase-js
- @supabase/ssr

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SUPABASE_URL: Supabase Dashboard → Project Settings → API → Project URL
- NEXT_PUBLIC_SUPABASE_ANON_KEY: Same page → Project API keys → anon / public
- SUPABASE_SERVICE_ROLE_KEY: Same page → service_role (secret). Server only. Bypasses RLS.

SETUP STEPS
1. https://supabase.com/dashboard → New project
2. Project Settings → API → copy URL, anon key, service_role
3. SQL Editor: create listings, bids, pending_checkouts + indexes
4. RLS: public SELECT on live listings; writes only with service_role in Route Handlers

### Stripe
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Stripe Dashboard: https://dashboard.stripe.com
- API keys: https://dashboard.stripe.com/apikeys
- Webhooks: https://dashboard.stripe.com/webhooks
- Checkout Sessions: https://docs.stripe.com/payments/checkout/how-checkout-works
- Create a Checkout Session API: https://docs.stripe.com/api/checkout/sessions/create
- stripe npm: https://www.npmjs.com/package/stripe
- Stripe CLI (local webhooks): https://docs.stripe.com/stripe-cli
- Test cards: https://docs.stripe.com/testing#cards

NPM PACKAGES
- stripe

API KEYS — name and exactly where to create them
- STRIPE_SECRET_KEY: https://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: Same page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
- STRIPE_WEBHOOK_SECRET: https://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe

SETUP STEPS
1. https://dashboard.stripe.com → sign up, stay in Test mode
2. Developers → API keys → STRIPE_SECRET_KEY
3. Create Checkout Session: mode=payment, line_items price_data.unit_amount = bid cents, metadata.pending_id
4. Success URL: {SITE}/success?checkout_id={pending_id}
5. Webhook endpoint: {SITE}/api/webhooks/stripe event checkout.session.completed
6. Local: stripe listen --forward-to localhost:3000/api/webhooks/stripe → STRIPE_WEBHOOK_SECRET
7. Test card: 4242 4242 4242 4242

---

You are scaffolding a pay-to-rank bidding board (same product as ihatereading.lol).

PRODUCT
A public leaderboard. Anyone pastes a URL (or listing), types a bid of $1+, pays, and that listing rises by CUMULATIVE money. Highest total bid is #1. No user accounts. Anonymous is fine.

USER JOURNEY
1. Land on / — hero + bid form + live board
2. Paste URL → preview title/image/description (Open Graph)
3. Set bid (number stepper, min $1 / 100 cents). Default the input to the current highest board bid.
4. Pay → redirect to provider checkout
5. Return /success?checkout_id=... → poll status until live → show rank + confetti
6. Board shows rank, thumbnail, title, total $, bid count. Click a row → /listing/[id] to add another bid (stack).

DOMAIN (use these names, do not invent a different model)
- listings: id, url, type, title, description, thumbnail_url, favicon_url, author_name, submitted_by_name, total_bid_cents, bid_count, bid_cents_last_24h, status (pending|live|rejected), created_at, last_bid_at
- bids: id, listing_id, amount_cents, bidder_name, provider, checkout_id, payment_id, customer_email, created_at
- pending_checkouts: id, url, preview fields, bid_amount_cents, existing_listing_id (null = new URL), provider_session_id, created_at
- Optional: presence + stats counters for "N online" / views

RULES (must implement)
- Min bid $1 (400 if amount_cents < 100)
- Normalize URLs (https, strip tracking params). Same URL cannot create a second listing — return 409 with existing_listing_id; user must stack a bid instead
- Stacking: paying against existing_listing_id adds amount to total_bid_cents and bid_count
- Rank all-time: ORDER BY total_bid_cents DESC, last_bid_at DESC
- Rank trending: ORDER BY bid_cents_last_24h DESC
- Payments are idempotent: never insert the same payment_id twice
- Flow: create pending_checkout → create provider checkout with metadata { pending_id, bid_amount_cents } → webhook OR success-page poll fulfills → create/update listing + insert bid
- Server-only writes for money (never let the browser increment totals)
- No auth required
- Admin page optional: password gate to reject a listing

PAGES
- / landing (form + board)
- /listing/[id] detail + stack bid + bid history
- /success payment confirmation
- /stats simple counters
- shadcn/ui + Tailwind. Light theme. Mobile-first: stack board cards on small screens.

APIS
- POST /api/preview { url }
- POST /api/checkout { url, preview, bid_amount_cents, existing_listing_id? }
- GET /api/status?checkout_id=
- POST /api/webhooks/[provider]
- GET /api/board?sort=all_time|trending

SHIP
- .env.example with every key AND a comment above each key: what it is + the exact dashboard URL to create it
- README: numbered setup using the PLATFORM DETAILS links below
- Seed + clear scripts
- TypeScript strict. Do not add extra features (auth, AI ranking, comments).


STACK (do not substitute)
- Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn/ui, React Query or SWR
- Database: Supabase (Postgres + RLS)
- Payments: Stripe Checkout

ADAPTER RULES
SQL tables listings, bids, pending_checkouts with indexes on url and total_bid_cents.
RLS: public SELECT on live listings + bids; all inserts/updates via service role in API routes only.
Fulfillment must use a transaction: UPDATE listings SET total_bid_cents = total_bid_cents + $amount (no read-modify-write race).
Stripe Checkout + checkout.session.completed as above.
Optional: supabase realtime on listings for the board.

ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- NEXT_PUBLIC_SUPABASE_URL
- NEXT_PUBLIC_SUPABASE_ANON_KEY
- SUPABASE_SERVICE_ROLE_KEY
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET

Copy every official link from PLATFORM DETAILS into README.md under Setup.
Build the complete runnable app now. After files exist, print: install, env, migrate/seed, webhook listen, npm run dev.

Supabase (Postgres + RLS) · Polar.sh Checkout

Supabase + Polar

Postgres board with Polar for indie SaaS payments.

Next.js + shadcn + Tailwind

  • NEXT_PUBLIC_SITE_URLYour public origin, e.g. http://localhost:3000 or https://yourdomain.com

Supabase

  • NEXT_PUBLIC_SUPABASE_URLSupabase Dashboard → Project Settings → API → Project URL
  • NEXT_PUBLIC_SUPABASE_ANON_KEYSame page → Project API keys → anon / public
  • SUPABASE_SERVICE_ROLE_KEYSame page → service_role (secret). Server only. Bypasses RLS.

Polar

  • POLAR_ACCESS_TOKENPolar org settings → Organization Access Token (polar_oat_… or polar_pat_…). Server only.
  • POLAR_WEBHOOK_SECRETOrg settings → Webhooks → Add endpoint → generate signing secret
  • POLAR_PRODUCT_IDProducts → create a one-time product → copy the product id
  • POLAR_SERVEROptional. sandbox vs production. See Polar Next.js guide.
Follow official docs only. Use the URLs, npm packages, and key locations in PLATFORM DETAILS. Do not invent SDK method names — look them up from those links.

### Next.js + shadcn + Tailwind
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Next.js App Router: https://nextjs.org/docs/app
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- Environment variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- shadcn install (Next.js): https://ui.shadcn.com/docs/installation/next
- shadcn components: https://ui.shadcn.com/docs/components
- Tailwind v3 + Next.js: https://v3.tailwindcss.com/docs/guides/nextjs
- Vercel deploy: https://vercel.com/docs/getting-started-with-vercel
- Vercel env vars: https://vercel.com/docs/projects/environment-variables

NPM PACKAGES
- next
- react
- tailwindcss
- class-variance-authority
- lucide-react

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SITE_URL: Your public origin, e.g. http://localhost:3000 or https://yourdomain.com

SETUP STEPS
npx create-next-app@latest . --ts --tailwind --app --src-dir
npx shadcn@latest init
npx shadcn@latest add button input card badge

### Supabase
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Supabase Dashboard: https://supabase.com/dashboard
- API keys: https://supabase.com/docs/guides/api/api-keys
- Next.js guide: https://supabase.com/docs/guides/getting-started/quickstarts/nextjs
- Server-side Next.js: https://supabase.com/docs/guides/auth/server-side/nextjs
- Row Level Security: https://supabase.com/docs/guides/auth/row-level-security
- JS client: https://supabase.com/docs/reference/javascript/introduction
- @supabase/ssr npm: https://www.npmjs.com/package/@supabase/ssr

NPM PACKAGES
- @supabase/supabase-js
- @supabase/ssr

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SUPABASE_URL: Supabase Dashboard → Project Settings → API → Project URL
- NEXT_PUBLIC_SUPABASE_ANON_KEY: Same page → Project API keys → anon / public
- SUPABASE_SERVICE_ROLE_KEY: Same page → service_role (secret). Server only. Bypasses RLS.

SETUP STEPS
1. https://supabase.com/dashboard → New project
2. Project Settings → API → copy URL, anon key, service_role
3. SQL Editor: create listings, bids, pending_checkouts + indexes
4. RLS: public SELECT on live listings; writes only with service_role in Route Handlers

### Polar
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Polar: https://polar.sh
- Polar docs: https://polar.sh/docs
- Next.js guide: https://polar.sh/docs/guides/nextjs
- Access tokens: https://polar.sh/docs/integrate/oat
- Webhook endpoints: https://polar.sh/docs/integrate/webhooks/endpoints
- Checkout API: https://polar.sh/docs/api-reference/checkouts
- @polar-sh/nextjs: https://www.npmjs.com/package/@polar-sh/nextjs
- @polar-sh/sdk: https://www.npmjs.com/package/@polar-sh/sdk

NPM PACKAGES
- @polar-sh/sdk
- @polar-sh/nextjs

API KEYS — name and exactly where to create them
- POLAR_ACCESS_TOKEN: Polar org settings → Organization Access Token (polar_oat_… or polar_pat_…). Server only.
- POLAR_WEBHOOK_SECRET: Org settings → Webhooks → Add endpoint → generate signing secret
- POLAR_PRODUCT_ID: Products → create a one-time product → copy the product id
- POLAR_SERVER: Optional. sandbox vs production. See Polar Next.js guide.

SETUP STEPS
1. https://polar.sh → create organization (use sandbox while building)
2. Org settings → create Organization Access Token → POLAR_ACCESS_TOKEN
3. Products → one-time product, min $1 → POLAR_PRODUCT_ID
4. Settings → Webhooks → Add Endpoint → {SITE}/api/webhooks/polar → copy secret → POLAR_WEBHOOK_SECRET
5. Subscribe to order.paid (and checkout events you need)
6. Local tunnel: polar listen http://localhost:3000/  (https://polar.sh/docs/integrate/webhooks/endpoints)
7. Put pending_id in checkout metadata

---

You are scaffolding a pay-to-rank bidding board (same product as ihatereading.lol).

PRODUCT
A public leaderboard. Anyone pastes a URL (or listing), types a bid of $1+, pays, and that listing rises by CUMULATIVE money. Highest total bid is #1. No user accounts. Anonymous is fine.

USER JOURNEY
1. Land on / — hero + bid form + live board
2. Paste URL → preview title/image/description (Open Graph)
3. Set bid (number stepper, min $1 / 100 cents). Default the input to the current highest board bid.
4. Pay → redirect to provider checkout
5. Return /success?checkout_id=... → poll status until live → show rank + confetti
6. Board shows rank, thumbnail, title, total $, bid count. Click a row → /listing/[id] to add another bid (stack).

DOMAIN (use these names, do not invent a different model)
- listings: id, url, type, title, description, thumbnail_url, favicon_url, author_name, submitted_by_name, total_bid_cents, bid_count, bid_cents_last_24h, status (pending|live|rejected), created_at, last_bid_at
- bids: id, listing_id, amount_cents, bidder_name, provider, checkout_id, payment_id, customer_email, created_at
- pending_checkouts: id, url, preview fields, bid_amount_cents, existing_listing_id (null = new URL), provider_session_id, created_at
- Optional: presence + stats counters for "N online" / views

RULES (must implement)
- Min bid $1 (400 if amount_cents < 100)
- Normalize URLs (https, strip tracking params). Same URL cannot create a second listing — return 409 with existing_listing_id; user must stack a bid instead
- Stacking: paying against existing_listing_id adds amount to total_bid_cents and bid_count
- Rank all-time: ORDER BY total_bid_cents DESC, last_bid_at DESC
- Rank trending: ORDER BY bid_cents_last_24h DESC
- Payments are idempotent: never insert the same payment_id twice
- Flow: create pending_checkout → create provider checkout with metadata { pending_id, bid_amount_cents } → webhook OR success-page poll fulfills → create/update listing + insert bid
- Server-only writes for money (never let the browser increment totals)
- No auth required
- Admin page optional: password gate to reject a listing

PAGES
- / landing (form + board)
- /listing/[id] detail + stack bid + bid history
- /success payment confirmation
- /stats simple counters
- shadcn/ui + Tailwind. Light theme. Mobile-first: stack board cards on small screens.

APIS
- POST /api/preview { url }
- POST /api/checkout { url, preview, bid_amount_cents, existing_listing_id? }
- GET /api/status?checkout_id=
- POST /api/webhooks/[provider]
- GET /api/board?sort=all_time|trending

SHIP
- .env.example with every key AND a comment above each key: what it is + the exact dashboard URL to create it
- README: numbered setup using the PLATFORM DETAILS links below
- Seed + clear scripts
- TypeScript strict. Do not add extra features (auth, AI ranking, comments).


STACK (do not substitute)
- Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn/ui, React Query or SWR
- Database: Supabase (Postgres + RLS)
- Payments: Polar.sh Checkout

ADAPTER RULES
Same Supabase schema + RLS + transactional increment as Stripe variant.
Polar checkout + signed webhook. Map Polar order to bids.payment_id.
Keep pending_id in Polar metadata so fulfillment does not depend on query-string only.

ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- NEXT_PUBLIC_SUPABASE_URL
- NEXT_PUBLIC_SUPABASE_ANON_KEY
- SUPABASE_SERVICE_ROLE_KEY
- POLAR_ACCESS_TOKEN
- POLAR_WEBHOOK_SECRET
- POLAR_PRODUCT_ID

Copy every official link from PLATFORM DETAILS into README.md under Setup.
Build the complete runnable app now. After files exist, print: install, env, migrate/seed, webhook listen, npm run dev.

Supabase (Postgres + RLS) · Dodo Payments

Supabase + Dodo

SQL source of truth, Dodo for global cards.

Next.js + shadcn + Tailwind

  • NEXT_PUBLIC_SITE_URLYour public origin, e.g. http://localhost:3000 or https://yourdomain.com

Supabase

  • NEXT_PUBLIC_SUPABASE_URLSupabase Dashboard → Project Settings → API → Project URL
  • NEXT_PUBLIC_SUPABASE_ANON_KEYSame page → Project API keys → anon / public
  • SUPABASE_SERVICE_ROLE_KEYSame page → service_role (secret). Server only. Bypasses RLS.

Dodo Payments

  • DODO_PAYMENTS_API_KEYhttps://app.dodopayments.com/developer/api-keys → Add API key (Bearer). Server only.
  • DODO_PAYMENTS_WEBHOOK_KEYhttps://app.dodopayments.com/developer/webhooks → endpoint Overview → signing secret
  • DODO_PAYMENTS_PRODUCT_IDDashboard → Products → create a one-time Pay What You Want product with $1 minimum
  • DODO_PAYMENTS_ENVIRONMENTMust be test_mode or live_mode. Never the string "live".
Follow official docs only. Use the URLs, npm packages, and key locations in PLATFORM DETAILS. Do not invent SDK method names — look them up from those links.

### Next.js + shadcn + Tailwind
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Next.js App Router: https://nextjs.org/docs/app
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- Environment variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- shadcn install (Next.js): https://ui.shadcn.com/docs/installation/next
- shadcn components: https://ui.shadcn.com/docs/components
- Tailwind v3 + Next.js: https://v3.tailwindcss.com/docs/guides/nextjs
- Vercel deploy: https://vercel.com/docs/getting-started-with-vercel
- Vercel env vars: https://vercel.com/docs/projects/environment-variables

NPM PACKAGES
- next
- react
- tailwindcss
- class-variance-authority
- lucide-react

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SITE_URL: Your public origin, e.g. http://localhost:3000 or https://yourdomain.com

SETUP STEPS
npx create-next-app@latest . --ts --tailwind --app --src-dir
npx shadcn@latest init
npx shadcn@latest add button input card badge

### Supabase
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Supabase Dashboard: https://supabase.com/dashboard
- API keys: https://supabase.com/docs/guides/api/api-keys
- Next.js guide: https://supabase.com/docs/guides/getting-started/quickstarts/nextjs
- Server-side Next.js: https://supabase.com/docs/guides/auth/server-side/nextjs
- Row Level Security: https://supabase.com/docs/guides/auth/row-level-security
- JS client: https://supabase.com/docs/reference/javascript/introduction
- @supabase/ssr npm: https://www.npmjs.com/package/@supabase/ssr

NPM PACKAGES
- @supabase/supabase-js
- @supabase/ssr

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SUPABASE_URL: Supabase Dashboard → Project Settings → API → Project URL
- NEXT_PUBLIC_SUPABASE_ANON_KEY: Same page → Project API keys → anon / public
- SUPABASE_SERVICE_ROLE_KEY: Same page → service_role (secret). Server only. Bypasses RLS.

SETUP STEPS
1. https://supabase.com/dashboard → New project
2. Project Settings → API → copy URL, anon key, service_role
3. SQL Editor: create listings, bids, pending_checkouts + indexes
4. RLS: public SELECT on live listings; writes only with service_role in Route Handlers

### Dodo Payments
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Dodo Dashboard: https://app.dodopayments.com/
- API keys: https://app.dodopayments.com/developer/api-keys
- Webhooks: https://app.dodopayments.com/developer/webhooks
- Integration guide: https://docs.dodopayments.com/developer-resources/integration-guide
- Checkout sessions: https://docs.dodopayments.com/developer-resources/checkout-sessions
- Webhook docs: https://docs.dodopayments.com/developer-resources/webhooks
- Test vs live mode: https://docs.dodopayments.com/miscellaneous/test-mode-vs-live-mode
- dodopayments npm: https://www.npmjs.com/package/dodopayments
- @dodopayments/nextjs: https://www.npmjs.com/package/@dodopayments/nextjs

NPM PACKAGES
- dodopayments
- @dodopayments/nextjs

API KEYS — name and exactly where to create them
- DODO_PAYMENTS_API_KEY: https://app.dodopayments.com/developer/api-keys → Add API key (Bearer). Server only.
- DODO_PAYMENTS_WEBHOOK_KEY: https://app.dodopayments.com/developer/webhooks → endpoint Overview → signing secret
- DODO_PAYMENTS_PRODUCT_ID: Dashboard → Products → create a one-time Pay What You Want product with $1 minimum
- DODO_PAYMENTS_ENVIRONMENT: Must be test_mode or live_mode. Never the string "live".

SETUP STEPS
1. https://app.dodopayments.com/ → create merchant account
2. Developer → API Keys → DODO_PAYMENTS_API_KEY
3. Products → one-time Pay What You Want, minimum $1 → DODO_PAYMENTS_PRODUCT_ID
4. checkoutSessions.create with product_cart[].amount in CENTS
5. Developer → Webhooks → URL {SITE}/api/webhooks/dodo → DODO_PAYMENTS_WEBHOOK_KEY
6. DODO_PAYMENTS_ENVIRONMENT=test_mode until you go live (then live_mode)
7. Return URL {SITE}/success?checkout_id={pending_id}
8. Also poll GET /api/status — webhooks can arrive after the browser returns

---

You are scaffolding a pay-to-rank bidding board (same product as ihatereading.lol).

PRODUCT
A public leaderboard. Anyone pastes a URL (or listing), types a bid of $1+, pays, and that listing rises by CUMULATIVE money. Highest total bid is #1. No user accounts. Anonymous is fine.

USER JOURNEY
1. Land on / — hero + bid form + live board
2. Paste URL → preview title/image/description (Open Graph)
3. Set bid (number stepper, min $1 / 100 cents). Default the input to the current highest board bid.
4. Pay → redirect to provider checkout
5. Return /success?checkout_id=... → poll status until live → show rank + confetti
6. Board shows rank, thumbnail, title, total $, bid count. Click a row → /listing/[id] to add another bid (stack).

DOMAIN (use these names, do not invent a different model)
- listings: id, url, type, title, description, thumbnail_url, favicon_url, author_name, submitted_by_name, total_bid_cents, bid_count, bid_cents_last_24h, status (pending|live|rejected), created_at, last_bid_at
- bids: id, listing_id, amount_cents, bidder_name, provider, checkout_id, payment_id, customer_email, created_at
- pending_checkouts: id, url, preview fields, bid_amount_cents, existing_listing_id (null = new URL), provider_session_id, created_at
- Optional: presence + stats counters for "N online" / views

RULES (must implement)
- Min bid $1 (400 if amount_cents < 100)
- Normalize URLs (https, strip tracking params). Same URL cannot create a second listing — return 409 with existing_listing_id; user must stack a bid instead
- Stacking: paying against existing_listing_id adds amount to total_bid_cents and bid_count
- Rank all-time: ORDER BY total_bid_cents DESC, last_bid_at DESC
- Rank trending: ORDER BY bid_cents_last_24h DESC
- Payments are idempotent: never insert the same payment_id twice
- Flow: create pending_checkout → create provider checkout with metadata { pending_id, bid_amount_cents } → webhook OR success-page poll fulfills → create/update listing + insert bid
- Server-only writes for money (never let the browser increment totals)
- No auth required
- Admin page optional: password gate to reject a listing

PAGES
- / landing (form + board)
- /listing/[id] detail + stack bid + bid history
- /success payment confirmation
- /stats simple counters
- shadcn/ui + Tailwind. Light theme. Mobile-first: stack board cards on small screens.

APIS
- POST /api/preview { url }
- POST /api/checkout { url, preview, bid_amount_cents, existing_listing_id? }
- GET /api/status?checkout_id=
- POST /api/webhooks/[provider]
- GET /api/board?sort=all_time|trending

SHIP
- .env.example with every key AND a comment above each key: what it is + the exact dashboard URL to create it
- README: numbered setup using the PLATFORM DETAILS links below
- Seed + clear scripts
- TypeScript strict. Do not add extra features (auth, AI ranking, comments).


STACK (do not substitute)
- Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn/ui, React Query or SWR
- Database: Supabase (Postgres + RLS)
- Payments: Dodo Payments

ADAPTER RULES
Supabase tables + service-role writes only.
Dodo PWYW checkout (amount in cents), webhook + status poll fallback.
Use a Postgres transaction when applying a successful payment.

ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- NEXT_PUBLIC_SUPABASE_URL
- NEXT_PUBLIC_SUPABASE_ANON_KEY
- SUPABASE_SERVICE_ROLE_KEY
- DODO_PAYMENTS_API_KEY
- DODO_PAYMENTS_PRODUCT_ID
- DODO_PAYMENTS_WEBHOOK_KEY
- DODO_PAYMENTS_ENVIRONMENT=test_mode

Copy every official link from PLATFORM DETAILS into README.md under Setup.
Build the complete runnable app now. After files exist, print: install, env, migrate/seed, webhook listen, npm run dev.

Prisma + PostgreSQL · Stripe Checkout

Prisma + Stripe

Classic Next.js SaaS stack. Prisma schema you can own.

Next.js + shadcn + Tailwind

  • NEXT_PUBLIC_SITE_URLYour public origin, e.g. http://localhost:3000 or https://yourdomain.com

Prisma + PostgreSQL

  • DATABASE_URLPostgres connection string from Neon, Vercel Postgres, Supabase, or Railway. prisma+postgres://... or postgresql://...

Stripe

  • STRIPE_SECRET_KEYhttps://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYSame page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
  • STRIPE_WEBHOOK_SECREThttps://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe
Follow official docs only. Use the URLs, npm packages, and key locations in PLATFORM DETAILS. Do not invent SDK method names — look them up from those links.

### Next.js + shadcn + Tailwind
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Next.js App Router: https://nextjs.org/docs/app
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- Environment variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- shadcn install (Next.js): https://ui.shadcn.com/docs/installation/next
- shadcn components: https://ui.shadcn.com/docs/components
- Tailwind v3 + Next.js: https://v3.tailwindcss.com/docs/guides/nextjs
- Vercel deploy: https://vercel.com/docs/getting-started-with-vercel
- Vercel env vars: https://vercel.com/docs/projects/environment-variables

NPM PACKAGES
- next
- react
- tailwindcss
- class-variance-authority
- lucide-react

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SITE_URL: Your public origin, e.g. http://localhost:3000 or https://yourdomain.com

SETUP STEPS
npx create-next-app@latest . --ts --tailwind --app --src-dir
npx shadcn@latest init
npx shadcn@latest add button input card badge

### Prisma + PostgreSQL
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Prisma docs: https://www.prisma.io/docs
- Prisma + Next.js: https://www.prisma.io/docs/orm/more/help-and-troubleshooting/nextjs-guide
- Prisma schema: https://www.prisma.io/docs/orm/prisma-schema
- Transactions: https://www.prisma.io/docs/orm/prisma-client/queries/transactions
- Neon (hosted Postgres): https://console.neon.tech
- Neon connection string: https://neon.tech/docs/connect/connect-from-any-app
- Vercel Postgres: https://vercel.com/docs/storage/vercel-postgres
- prisma npm: https://www.npmjs.com/package/prisma

NPM PACKAGES
- prisma
- @prisma/client

API KEYS — name and exactly where to create them
- DATABASE_URL: Postgres connection string from Neon, Vercel Postgres, Supabase, or Railway. prisma+postgres://... or postgresql://...

SETUP STEPS
1. Create a Postgres database (Neon is fastest: https://console.neon.tech)
2. Copy the connection string → DATABASE_URL
3. npx prisma init && define Listing, Bid, PendingCheckout
4. npx prisma migrate dev --name init

### Stripe
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Stripe Dashboard: https://dashboard.stripe.com
- API keys: https://dashboard.stripe.com/apikeys
- Webhooks: https://dashboard.stripe.com/webhooks
- Checkout Sessions: https://docs.stripe.com/payments/checkout/how-checkout-works
- Create a Checkout Session API: https://docs.stripe.com/api/checkout/sessions/create
- stripe npm: https://www.npmjs.com/package/stripe
- Stripe CLI (local webhooks): https://docs.stripe.com/stripe-cli
- Test cards: https://docs.stripe.com/testing#cards

NPM PACKAGES
- stripe

API KEYS — name and exactly where to create them
- STRIPE_SECRET_KEY: https://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: Same page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
- STRIPE_WEBHOOK_SECRET: https://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe

SETUP STEPS
1. https://dashboard.stripe.com → sign up, stay in Test mode
2. Developers → API keys → STRIPE_SECRET_KEY
3. Create Checkout Session: mode=payment, line_items price_data.unit_amount = bid cents, metadata.pending_id
4. Success URL: {SITE}/success?checkout_id={pending_id}
5. Webhook endpoint: {SITE}/api/webhooks/stripe event checkout.session.completed
6. Local: stripe listen --forward-to localhost:3000/api/webhooks/stripe → STRIPE_WEBHOOK_SECRET
7. Test card: 4242 4242 4242 4242

---

You are scaffolding a pay-to-rank bidding board (same product as ihatereading.lol).

PRODUCT
A public leaderboard. Anyone pastes a URL (or listing), types a bid of $1+, pays, and that listing rises by CUMULATIVE money. Highest total bid is #1. No user accounts. Anonymous is fine.

USER JOURNEY
1. Land on / — hero + bid form + live board
2. Paste URL → preview title/image/description (Open Graph)
3. Set bid (number stepper, min $1 / 100 cents). Default the input to the current highest board bid.
4. Pay → redirect to provider checkout
5. Return /success?checkout_id=... → poll status until live → show rank + confetti
6. Board shows rank, thumbnail, title, total $, bid count. Click a row → /listing/[id] to add another bid (stack).

DOMAIN (use these names, do not invent a different model)
- listings: id, url, type, title, description, thumbnail_url, favicon_url, author_name, submitted_by_name, total_bid_cents, bid_count, bid_cents_last_24h, status (pending|live|rejected), created_at, last_bid_at
- bids: id, listing_id, amount_cents, bidder_name, provider, checkout_id, payment_id, customer_email, created_at
- pending_checkouts: id, url, preview fields, bid_amount_cents, existing_listing_id (null = new URL), provider_session_id, created_at
- Optional: presence + stats counters for "N online" / views

RULES (must implement)
- Min bid $1 (400 if amount_cents < 100)
- Normalize URLs (https, strip tracking params). Same URL cannot create a second listing — return 409 with existing_listing_id; user must stack a bid instead
- Stacking: paying against existing_listing_id adds amount to total_bid_cents and bid_count
- Rank all-time: ORDER BY total_bid_cents DESC, last_bid_at DESC
- Rank trending: ORDER BY bid_cents_last_24h DESC
- Payments are idempotent: never insert the same payment_id twice
- Flow: create pending_checkout → create provider checkout with metadata { pending_id, bid_amount_cents } → webhook OR success-page poll fulfills → create/update listing + insert bid
- Server-only writes for money (never let the browser increment totals)
- No auth required
- Admin page optional: password gate to reject a listing

PAGES
- / landing (form + board)
- /listing/[id] detail + stack bid + bid history
- /success payment confirmation
- /stats simple counters
- shadcn/ui + Tailwind. Light theme. Mobile-first: stack board cards on small screens.

APIS
- POST /api/preview { url }
- POST /api/checkout { url, preview, bid_amount_cents, existing_listing_id? }
- GET /api/status?checkout_id=
- POST /api/webhooks/[provider]
- GET /api/board?sort=all_time|trending

SHIP
- .env.example with every key AND a comment above each key: what it is + the exact dashboard URL to create it
- README: numbered setup using the PLATFORM DETAILS links below
- Seed + clear scripts
- TypeScript strict. Do not add extra features (auth, AI ranking, comments).


STACK (do not substitute)
- Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn/ui, React Query or SWR
- Database: Prisma + PostgreSQL
- Payments: Stripe Checkout

ADAPTER RULES
prisma/schema.prisma with Listing, Bid, PendingCheckout.
Unique index on Listing.url. Bid.paymentId unique.
Fulfill with prisma.$transaction and increment totalBidCents.
Stripe Checkout + webhook. Next.js Route Handlers only — no client DB writes.
Include prisma migrate + seed.

ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- DATABASE_URL
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET

Copy every official link from PLATFORM DETAILS into README.md under Setup.
Build the complete runnable app now. After files exist, print: install, env, migrate/seed, webhook listen, npm run dev.

Prisma + PostgreSQL · Polar.sh Checkout

Prisma + Polar

Prisma schema with Polar checkout for creator tools.

Next.js + shadcn + Tailwind

  • NEXT_PUBLIC_SITE_URLYour public origin, e.g. http://localhost:3000 or https://yourdomain.com

Prisma + PostgreSQL

  • DATABASE_URLPostgres connection string from Neon, Vercel Postgres, Supabase, or Railway. prisma+postgres://... or postgresql://...

Polar

  • POLAR_ACCESS_TOKENPolar org settings → Organization Access Token (polar_oat_… or polar_pat_…). Server only.
  • POLAR_WEBHOOK_SECRETOrg settings → Webhooks → Add endpoint → generate signing secret
  • POLAR_PRODUCT_IDProducts → create a one-time product → copy the product id
  • POLAR_SERVEROptional. sandbox vs production. See Polar Next.js guide.
Follow official docs only. Use the URLs, npm packages, and key locations in PLATFORM DETAILS. Do not invent SDK method names — look them up from those links.

### Next.js + shadcn + Tailwind
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Next.js App Router: https://nextjs.org/docs/app
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- Environment variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- shadcn install (Next.js): https://ui.shadcn.com/docs/installation/next
- shadcn components: https://ui.shadcn.com/docs/components
- Tailwind v3 + Next.js: https://v3.tailwindcss.com/docs/guides/nextjs
- Vercel deploy: https://vercel.com/docs/getting-started-with-vercel
- Vercel env vars: https://vercel.com/docs/projects/environment-variables

NPM PACKAGES
- next
- react
- tailwindcss
- class-variance-authority
- lucide-react

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SITE_URL: Your public origin, e.g. http://localhost:3000 or https://yourdomain.com

SETUP STEPS
npx create-next-app@latest . --ts --tailwind --app --src-dir
npx shadcn@latest init
npx shadcn@latest add button input card badge

### Prisma + PostgreSQL
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Prisma docs: https://www.prisma.io/docs
- Prisma + Next.js: https://www.prisma.io/docs/orm/more/help-and-troubleshooting/nextjs-guide
- Prisma schema: https://www.prisma.io/docs/orm/prisma-schema
- Transactions: https://www.prisma.io/docs/orm/prisma-client/queries/transactions
- Neon (hosted Postgres): https://console.neon.tech
- Neon connection string: https://neon.tech/docs/connect/connect-from-any-app
- Vercel Postgres: https://vercel.com/docs/storage/vercel-postgres
- prisma npm: https://www.npmjs.com/package/prisma

NPM PACKAGES
- prisma
- @prisma/client

API KEYS — name and exactly where to create them
- DATABASE_URL: Postgres connection string from Neon, Vercel Postgres, Supabase, or Railway. prisma+postgres://... or postgresql://...

SETUP STEPS
1. Create a Postgres database (Neon is fastest: https://console.neon.tech)
2. Copy the connection string → DATABASE_URL
3. npx prisma init && define Listing, Bid, PendingCheckout
4. npx prisma migrate dev --name init

### Polar
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Polar: https://polar.sh
- Polar docs: https://polar.sh/docs
- Next.js guide: https://polar.sh/docs/guides/nextjs
- Access tokens: https://polar.sh/docs/integrate/oat
- Webhook endpoints: https://polar.sh/docs/integrate/webhooks/endpoints
- Checkout API: https://polar.sh/docs/api-reference/checkouts
- @polar-sh/nextjs: https://www.npmjs.com/package/@polar-sh/nextjs
- @polar-sh/sdk: https://www.npmjs.com/package/@polar-sh/sdk

NPM PACKAGES
- @polar-sh/sdk
- @polar-sh/nextjs

API KEYS — name and exactly where to create them
- POLAR_ACCESS_TOKEN: Polar org settings → Organization Access Token (polar_oat_… or polar_pat_…). Server only.
- POLAR_WEBHOOK_SECRET: Org settings → Webhooks → Add endpoint → generate signing secret
- POLAR_PRODUCT_ID: Products → create a one-time product → copy the product id
- POLAR_SERVER: Optional. sandbox vs production. See Polar Next.js guide.

SETUP STEPS
1. https://polar.sh → create organization (use sandbox while building)
2. Org settings → create Organization Access Token → POLAR_ACCESS_TOKEN
3. Products → one-time product, min $1 → POLAR_PRODUCT_ID
4. Settings → Webhooks → Add Endpoint → {SITE}/api/webhooks/polar → copy secret → POLAR_WEBHOOK_SECRET
5. Subscribe to order.paid (and checkout events you need)
6. Local tunnel: polar listen http://localhost:3000/  (https://polar.sh/docs/integrate/webhooks/endpoints)
7. Put pending_id in checkout metadata

---

You are scaffolding a pay-to-rank bidding board (same product as ihatereading.lol).

PRODUCT
A public leaderboard. Anyone pastes a URL (or listing), types a bid of $1+, pays, and that listing rises by CUMULATIVE money. Highest total bid is #1. No user accounts. Anonymous is fine.

USER JOURNEY
1. Land on / — hero + bid form + live board
2. Paste URL → preview title/image/description (Open Graph)
3. Set bid (number stepper, min $1 / 100 cents). Default the input to the current highest board bid.
4. Pay → redirect to provider checkout
5. Return /success?checkout_id=... → poll status until live → show rank + confetti
6. Board shows rank, thumbnail, title, total $, bid count. Click a row → /listing/[id] to add another bid (stack).

DOMAIN (use these names, do not invent a different model)
- listings: id, url, type, title, description, thumbnail_url, favicon_url, author_name, submitted_by_name, total_bid_cents, bid_count, bid_cents_last_24h, status (pending|live|rejected), created_at, last_bid_at
- bids: id, listing_id, amount_cents, bidder_name, provider, checkout_id, payment_id, customer_email, created_at
- pending_checkouts: id, url, preview fields, bid_amount_cents, existing_listing_id (null = new URL), provider_session_id, created_at
- Optional: presence + stats counters for "N online" / views

RULES (must implement)
- Min bid $1 (400 if amount_cents < 100)
- Normalize URLs (https, strip tracking params). Same URL cannot create a second listing — return 409 with existing_listing_id; user must stack a bid instead
- Stacking: paying against existing_listing_id adds amount to total_bid_cents and bid_count
- Rank all-time: ORDER BY total_bid_cents DESC, last_bid_at DESC
- Rank trending: ORDER BY bid_cents_last_24h DESC
- Payments are idempotent: never insert the same payment_id twice
- Flow: create pending_checkout → create provider checkout with metadata { pending_id, bid_amount_cents } → webhook OR success-page poll fulfills → create/update listing + insert bid
- Server-only writes for money (never let the browser increment totals)
- No auth required
- Admin page optional: password gate to reject a listing

PAGES
- / landing (form + board)
- /listing/[id] detail + stack bid + bid history
- /success payment confirmation
- /stats simple counters
- shadcn/ui + Tailwind. Light theme. Mobile-first: stack board cards on small screens.

APIS
- POST /api/preview { url }
- POST /api/checkout { url, preview, bid_amount_cents, existing_listing_id? }
- GET /api/status?checkout_id=
- POST /api/webhooks/[provider]
- GET /api/board?sort=all_time|trending

SHIP
- .env.example with every key AND a comment above each key: what it is + the exact dashboard URL to create it
- README: numbered setup using the PLATFORM DETAILS links below
- Seed + clear scripts
- TypeScript strict. Do not add extra features (auth, AI ranking, comments).


STACK (do not substitute)
- Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn/ui, React Query or SWR
- Database: Prisma + PostgreSQL
- Payments: Polar.sh Checkout

ADAPTER RULES
Same Prisma models. Polar checkout + webhook verification.
paymentId unique on Bid. Transactional increment on Listing.

ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- DATABASE_URL
- POLAR_ACCESS_TOKEN
- POLAR_WEBHOOK_SECRET
- POLAR_PRODUCT_ID

Copy every official link from PLATFORM DETAILS into README.md under Setup.
Build the complete runnable app now. After files exist, print: install, env, migrate/seed, webhook listen, npm run dev.

Prisma + PostgreSQL · Dodo Payments

Prisma + Dodo

Prisma + the same payment provider as this live site.

Next.js + shadcn + Tailwind

  • NEXT_PUBLIC_SITE_URLYour public origin, e.g. http://localhost:3000 or https://yourdomain.com

Prisma + PostgreSQL

  • DATABASE_URLPostgres connection string from Neon, Vercel Postgres, Supabase, or Railway. prisma+postgres://... or postgresql://...

Dodo Payments

  • DODO_PAYMENTS_API_KEYhttps://app.dodopayments.com/developer/api-keys → Add API key (Bearer). Server only.
  • DODO_PAYMENTS_WEBHOOK_KEYhttps://app.dodopayments.com/developer/webhooks → endpoint Overview → signing secret
  • DODO_PAYMENTS_PRODUCT_IDDashboard → Products → create a one-time Pay What You Want product with $1 minimum
  • DODO_PAYMENTS_ENVIRONMENTMust be test_mode or live_mode. Never the string "live".
Follow official docs only. Use the URLs, npm packages, and key locations in PLATFORM DETAILS. Do not invent SDK method names — look them up from those links.

### Next.js + shadcn + Tailwind
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Next.js App Router: https://nextjs.org/docs/app
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- Environment variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- shadcn install (Next.js): https://ui.shadcn.com/docs/installation/next
- shadcn components: https://ui.shadcn.com/docs/components
- Tailwind v3 + Next.js: https://v3.tailwindcss.com/docs/guides/nextjs
- Vercel deploy: https://vercel.com/docs/getting-started-with-vercel
- Vercel env vars: https://vercel.com/docs/projects/environment-variables

NPM PACKAGES
- next
- react
- tailwindcss
- class-variance-authority
- lucide-react

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SITE_URL: Your public origin, e.g. http://localhost:3000 or https://yourdomain.com

SETUP STEPS
npx create-next-app@latest . --ts --tailwind --app --src-dir
npx shadcn@latest init
npx shadcn@latest add button input card badge

### Prisma + PostgreSQL
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Prisma docs: https://www.prisma.io/docs
- Prisma + Next.js: https://www.prisma.io/docs/orm/more/help-and-troubleshooting/nextjs-guide
- Prisma schema: https://www.prisma.io/docs/orm/prisma-schema
- Transactions: https://www.prisma.io/docs/orm/prisma-client/queries/transactions
- Neon (hosted Postgres): https://console.neon.tech
- Neon connection string: https://neon.tech/docs/connect/connect-from-any-app
- Vercel Postgres: https://vercel.com/docs/storage/vercel-postgres
- prisma npm: https://www.npmjs.com/package/prisma

NPM PACKAGES
- prisma
- @prisma/client

API KEYS — name and exactly where to create them
- DATABASE_URL: Postgres connection string from Neon, Vercel Postgres, Supabase, or Railway. prisma+postgres://... or postgresql://...

SETUP STEPS
1. Create a Postgres database (Neon is fastest: https://console.neon.tech)
2. Copy the connection string → DATABASE_URL
3. npx prisma init && define Listing, Bid, PendingCheckout
4. npx prisma migrate dev --name init

### Dodo Payments
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Dodo Dashboard: https://app.dodopayments.com/
- API keys: https://app.dodopayments.com/developer/api-keys
- Webhooks: https://app.dodopayments.com/developer/webhooks
- Integration guide: https://docs.dodopayments.com/developer-resources/integration-guide
- Checkout sessions: https://docs.dodopayments.com/developer-resources/checkout-sessions
- Webhook docs: https://docs.dodopayments.com/developer-resources/webhooks
- Test vs live mode: https://docs.dodopayments.com/miscellaneous/test-mode-vs-live-mode
- dodopayments npm: https://www.npmjs.com/package/dodopayments
- @dodopayments/nextjs: https://www.npmjs.com/package/@dodopayments/nextjs

NPM PACKAGES
- dodopayments
- @dodopayments/nextjs

API KEYS — name and exactly where to create them
- DODO_PAYMENTS_API_KEY: https://app.dodopayments.com/developer/api-keys → Add API key (Bearer). Server only.
- DODO_PAYMENTS_WEBHOOK_KEY: https://app.dodopayments.com/developer/webhooks → endpoint Overview → signing secret
- DODO_PAYMENTS_PRODUCT_ID: Dashboard → Products → create a one-time Pay What You Want product with $1 minimum
- DODO_PAYMENTS_ENVIRONMENT: Must be test_mode or live_mode. Never the string "live".

SETUP STEPS
1. https://app.dodopayments.com/ → create merchant account
2. Developer → API Keys → DODO_PAYMENTS_API_KEY
3. Products → one-time Pay What You Want, minimum $1 → DODO_PAYMENTS_PRODUCT_ID
4. checkoutSessions.create with product_cart[].amount in CENTS
5. Developer → Webhooks → URL {SITE}/api/webhooks/dodo → DODO_PAYMENTS_WEBHOOK_KEY
6. DODO_PAYMENTS_ENVIRONMENT=test_mode until you go live (then live_mode)
7. Return URL {SITE}/success?checkout_id={pending_id}
8. Also poll GET /api/status — webhooks can arrive after the browser returns

---

You are scaffolding a pay-to-rank bidding board (same product as ihatereading.lol).

PRODUCT
A public leaderboard. Anyone pastes a URL (or listing), types a bid of $1+, pays, and that listing rises by CUMULATIVE money. Highest total bid is #1. No user accounts. Anonymous is fine.

USER JOURNEY
1. Land on / — hero + bid form + live board
2. Paste URL → preview title/image/description (Open Graph)
3. Set bid (number stepper, min $1 / 100 cents). Default the input to the current highest board bid.
4. Pay → redirect to provider checkout
5. Return /success?checkout_id=... → poll status until live → show rank + confetti
6. Board shows rank, thumbnail, title, total $, bid count. Click a row → /listing/[id] to add another bid (stack).

DOMAIN (use these names, do not invent a different model)
- listings: id, url, type, title, description, thumbnail_url, favicon_url, author_name, submitted_by_name, total_bid_cents, bid_count, bid_cents_last_24h, status (pending|live|rejected), created_at, last_bid_at
- bids: id, listing_id, amount_cents, bidder_name, provider, checkout_id, payment_id, customer_email, created_at
- pending_checkouts: id, url, preview fields, bid_amount_cents, existing_listing_id (null = new URL), provider_session_id, created_at
- Optional: presence + stats counters for "N online" / views

RULES (must implement)
- Min bid $1 (400 if amount_cents < 100)
- Normalize URLs (https, strip tracking params). Same URL cannot create a second listing — return 409 with existing_listing_id; user must stack a bid instead
- Stacking: paying against existing_listing_id adds amount to total_bid_cents and bid_count
- Rank all-time: ORDER BY total_bid_cents DESC, last_bid_at DESC
- Rank trending: ORDER BY bid_cents_last_24h DESC
- Payments are idempotent: never insert the same payment_id twice
- Flow: create pending_checkout → create provider checkout with metadata { pending_id, bid_amount_cents } → webhook OR success-page poll fulfills → create/update listing + insert bid
- Server-only writes for money (never let the browser increment totals)
- No auth required
- Admin page optional: password gate to reject a listing

PAGES
- / landing (form + board)
- /listing/[id] detail + stack bid + bid history
- /success payment confirmation
- /stats simple counters
- shadcn/ui + Tailwind. Light theme. Mobile-first: stack board cards on small screens.

APIS
- POST /api/preview { url }
- POST /api/checkout { url, preview, bid_amount_cents, existing_listing_id? }
- GET /api/status?checkout_id=
- POST /api/webhooks/[provider]
- GET /api/board?sort=all_time|trending

SHIP
- .env.example with every key AND a comment above each key: what it is + the exact dashboard URL to create it
- README: numbered setup using the PLATFORM DETAILS links below
- Seed + clear scripts
- TypeScript strict. Do not add extra features (auth, AI ranking, comments).


STACK (do not substitute)
- Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn/ui, React Query or SWR
- Database: Prisma + PostgreSQL
- Payments: Dodo Payments

ADAPTER RULES
Prisma models + Dodo checkoutSessions (cents) + webhook + status poll.
Never store secrets in the client. live_mode vs test_mode only.

ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- DATABASE_URL
- DODO_PAYMENTS_API_KEY
- DODO_PAYMENTS_PRODUCT_ID
- DODO_PAYMENTS_WEBHOOK_KEY
- DODO_PAYMENTS_ENVIRONMENT=test_mode

Copy every official link from PLATFORM DETAILS into README.md under Setup.
Build the complete runnable app now. After files exist, print: install, env, migrate/seed, webhook listen, npm run dev.

Convex · Stripe Checkout

Convex + Stripe

Realtime board without rolling your own sockets.

Next.js + shadcn + Tailwind

  • NEXT_PUBLIC_SITE_URLYour public origin, e.g. http://localhost:3000 or https://yourdomain.com

Convex

  • CONVEX_DEPLOYMENTCreated by npx convex dev (written to .env.local)
  • NEXT_PUBLIC_CONVEX_URLConvex Dashboard → Settings → URL, or from npx convex dev

Stripe

  • STRIPE_SECRET_KEYhttps://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYSame page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
  • STRIPE_WEBHOOK_SECREThttps://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe
Follow official docs only. Use the URLs, npm packages, and key locations in PLATFORM DETAILS. Do not invent SDK method names — look them up from those links.

### Next.js + shadcn + Tailwind
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Next.js App Router: https://nextjs.org/docs/app
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- Environment variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- shadcn install (Next.js): https://ui.shadcn.com/docs/installation/next
- shadcn components: https://ui.shadcn.com/docs/components
- Tailwind v3 + Next.js: https://v3.tailwindcss.com/docs/guides/nextjs
- Vercel deploy: https://vercel.com/docs/getting-started-with-vercel
- Vercel env vars: https://vercel.com/docs/projects/environment-variables

NPM PACKAGES
- next
- react
- tailwindcss
- class-variance-authority
- lucide-react

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SITE_URL: Your public origin, e.g. http://localhost:3000 or https://yourdomain.com

SETUP STEPS
npx create-next-app@latest . --ts --tailwind --app --src-dir
npx shadcn@latest init
npx shadcn@latest add button input card badge

### Convex
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Convex Dashboard: https://dashboard.convex.dev
- Next.js quickstart: https://docs.convex.dev/quickstart/nextjs
- HTTP actions / webhooks: https://docs.convex.dev/functions/http-actions
- convex npm: https://www.npmjs.com/package/convex

NPM PACKAGES
- convex

API KEYS — name and exactly where to create them
- CONVEX_DEPLOYMENT: Created by npx convex dev (written to .env.local)
- NEXT_PUBLIC_CONVEX_URL: Convex Dashboard → Settings → URL, or from npx convex dev

SETUP STEPS
1. https://dashboard.convex.dev → create project
2. npx convex dev (writes CONVEX_DEPLOYMENT + NEXT_PUBLIC_CONVEX_URL)
3. Money writes only from mutations called by the Stripe webhook / httpAction

### Stripe
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Stripe Dashboard: https://dashboard.stripe.com
- API keys: https://dashboard.stripe.com/apikeys
- Webhooks: https://dashboard.stripe.com/webhooks
- Checkout Sessions: https://docs.stripe.com/payments/checkout/how-checkout-works
- Create a Checkout Session API: https://docs.stripe.com/api/checkout/sessions/create
- stripe npm: https://www.npmjs.com/package/stripe
- Stripe CLI (local webhooks): https://docs.stripe.com/stripe-cli
- Test cards: https://docs.stripe.com/testing#cards

NPM PACKAGES
- stripe

API KEYS — name and exactly where to create them
- STRIPE_SECRET_KEY: https://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: Same page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
- STRIPE_WEBHOOK_SECRET: https://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe

SETUP STEPS
1. https://dashboard.stripe.com → sign up, stay in Test mode
2. Developers → API keys → STRIPE_SECRET_KEY
3. Create Checkout Session: mode=payment, line_items price_data.unit_amount = bid cents, metadata.pending_id
4. Success URL: {SITE}/success?checkout_id={pending_id}
5. Webhook endpoint: {SITE}/api/webhooks/stripe event checkout.session.completed
6. Local: stripe listen --forward-to localhost:3000/api/webhooks/stripe → STRIPE_WEBHOOK_SECRET
7. Test card: 4242 4242 4242 4242

---

You are scaffolding a pay-to-rank bidding board (same product as ihatereading.lol).

PRODUCT
A public leaderboard. Anyone pastes a URL (or listing), types a bid of $1+, pays, and that listing rises by CUMULATIVE money. Highest total bid is #1. No user accounts. Anonymous is fine.

USER JOURNEY
1. Land on / — hero + bid form + live board
2. Paste URL → preview title/image/description (Open Graph)
3. Set bid (number stepper, min $1 / 100 cents). Default the input to the current highest board bid.
4. Pay → redirect to provider checkout
5. Return /success?checkout_id=... → poll status until live → show rank + confetti
6. Board shows rank, thumbnail, title, total $, bid count. Click a row → /listing/[id] to add another bid (stack).

DOMAIN (use these names, do not invent a different model)
- listings: id, url, type, title, description, thumbnail_url, favicon_url, author_name, submitted_by_name, total_bid_cents, bid_count, bid_cents_last_24h, status (pending|live|rejected), created_at, last_bid_at
- bids: id, listing_id, amount_cents, bidder_name, provider, checkout_id, payment_id, customer_email, created_at
- pending_checkouts: id, url, preview fields, bid_amount_cents, existing_listing_id (null = new URL), provider_session_id, created_at
- Optional: presence + stats counters for "N online" / views

RULES (must implement)
- Min bid $1 (400 if amount_cents < 100)
- Normalize URLs (https, strip tracking params). Same URL cannot create a second listing — return 409 with existing_listing_id; user must stack a bid instead
- Stacking: paying against existing_listing_id adds amount to total_bid_cents and bid_count
- Rank all-time: ORDER BY total_bid_cents DESC, last_bid_at DESC
- Rank trending: ORDER BY bid_cents_last_24h DESC
- Payments are idempotent: never insert the same payment_id twice
- Flow: create pending_checkout → create provider checkout with metadata { pending_id, bid_amount_cents } → webhook OR success-page poll fulfills → create/update listing + insert bid
- Server-only writes for money (never let the browser increment totals)
- No auth required
- Admin page optional: password gate to reject a listing

PAGES
- / landing (form + board)
- /listing/[id] detail + stack bid + bid history
- /success payment confirmation
- /stats simple counters
- shadcn/ui + Tailwind. Light theme. Mobile-first: stack board cards on small screens.

APIS
- POST /api/preview { url }
- POST /api/checkout { url, preview, bid_amount_cents, existing_listing_id? }
- GET /api/status?checkout_id=
- POST /api/webhooks/[provider]
- GET /api/board?sort=all_time|trending

SHIP
- .env.example with every key AND a comment above each key: what it is + the exact dashboard URL to create it
- README: numbered setup using the PLATFORM DETAILS links below
- Seed + clear scripts
- TypeScript strict. Do not add extra features (auth, AI ranking, comments).


STACK (do not substitute)
- Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn/ui, React Query or SWR
- Database: Convex
- Payments: Stripe Checkout

ADAPTER RULES
Convex tables for listings/bids/pending. Mutations only from Convex httpActions / Next webhook that calls an internal mutation.
Stripe Checkout. Board uses useQuery so ranks update live after pay.

ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- CONVEX_DEPLOYMENT
- NEXT_PUBLIC_CONVEX_URL
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET

Copy every official link from PLATFORM DETAILS into README.md under Setup.
Build the complete runnable app now. After files exist, print: install, env, migrate/seed, webhook listen, npm run dev.

Upstash Redis · Stripe Checkout

Upstash + Stripe

Tiny deploy. Sorted set for the leaderboard.

Next.js + shadcn + Tailwind

  • NEXT_PUBLIC_SITE_URLYour public origin, e.g. http://localhost:3000 or https://yourdomain.com

Upstash Redis

  • UPSTASH_REDIS_REST_URLUpstash Console → database → REST API → UPSTASH_REDIS_REST_URL
  • UPSTASH_REDIS_REST_TOKENSame page → UPSTASH_REDIS_REST_TOKEN

Stripe

  • STRIPE_SECRET_KEYhttps://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
  • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYSame page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
  • STRIPE_WEBHOOK_SECREThttps://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe
Follow official docs only. Use the URLs, npm packages, and key locations in PLATFORM DETAILS. Do not invent SDK method names — look them up from those links.

### Next.js + shadcn + Tailwind
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Next.js App Router: https://nextjs.org/docs/app
- Route Handlers: https://nextjs.org/docs/app/building-your-application/routing/route-handlers
- Environment variables: https://nextjs.org/docs/app/building-your-application/configuring/environment-variables
- shadcn install (Next.js): https://ui.shadcn.com/docs/installation/next
- shadcn components: https://ui.shadcn.com/docs/components
- Tailwind v3 + Next.js: https://v3.tailwindcss.com/docs/guides/nextjs
- Vercel deploy: https://vercel.com/docs/getting-started-with-vercel
- Vercel env vars: https://vercel.com/docs/projects/environment-variables

NPM PACKAGES
- next
- react
- tailwindcss
- class-variance-authority
- lucide-react

API KEYS — name and exactly where to create them
- NEXT_PUBLIC_SITE_URL: Your public origin, e.g. http://localhost:3000 or https://yourdomain.com

SETUP STEPS
npx create-next-app@latest . --ts --tailwind --app --src-dir
npx shadcn@latest init
npx shadcn@latest add button input card badge

### Upstash Redis
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Upstash Console: https://console.upstash.com
- Redis REST: https://upstash.com/docs/redis/overall/getstarted
- Next.js + Upstash: https://upstash.com/docs/redis/howto/nextjs
- @upstash/redis npm: https://www.npmjs.com/package/@upstash/redis

NPM PACKAGES
- @upstash/redis

API KEYS — name and exactly where to create them
- UPSTASH_REDIS_REST_URL: Upstash Console → database → REST API → UPSTASH_REDIS_REST_URL
- UPSTASH_REDIS_REST_TOKEN: Same page → UPSTASH_REDIS_REST_TOKEN

SETUP STEPS
1. https://console.upstash.com → Create Redis database
2. Copy REST URL + token
3. Use a ZSET score = total_bid_cents for ranking

### Stripe
OFFICIAL LINKS (open and follow these — do not invent APIs)
- Stripe Dashboard: https://dashboard.stripe.com
- API keys: https://dashboard.stripe.com/apikeys
- Webhooks: https://dashboard.stripe.com/webhooks
- Checkout Sessions: https://docs.stripe.com/payments/checkout/how-checkout-works
- Create a Checkout Session API: https://docs.stripe.com/api/checkout/sessions/create
- stripe npm: https://www.npmjs.com/package/stripe
- Stripe CLI (local webhooks): https://docs.stripe.com/stripe-cli
- Test cards: https://docs.stripe.com/testing#cards

NPM PACKAGES
- stripe

API KEYS — name and exactly where to create them
- STRIPE_SECRET_KEY: https://dashboard.stripe.com/apikeys → Secret key (sk_test_… locally, sk_live_… in prod). Server only.
- NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: Same page → Publishable key (pk_test_…). Only if you use Stripe.js; Checkout redirect can skip it.
- STRIPE_WEBHOOK_SECRET: https://dashboard.stripe.com/webhooks → Add endpoint → Signing secret (whsec_…). Locally: stripe listen --forward-to localhost:3000/api/webhooks/stripe

SETUP STEPS
1. https://dashboard.stripe.com → sign up, stay in Test mode
2. Developers → API keys → STRIPE_SECRET_KEY
3. Create Checkout Session: mode=payment, line_items price_data.unit_amount = bid cents, metadata.pending_id
4. Success URL: {SITE}/success?checkout_id={pending_id}
5. Webhook endpoint: {SITE}/api/webhooks/stripe event checkout.session.completed
6. Local: stripe listen --forward-to localhost:3000/api/webhooks/stripe → STRIPE_WEBHOOK_SECRET
7. Test card: 4242 4242 4242 4242

---

You are scaffolding a pay-to-rank bidding board (same product as ihatereading.lol).

PRODUCT
A public leaderboard. Anyone pastes a URL (or listing), types a bid of $1+, pays, and that listing rises by CUMULATIVE money. Highest total bid is #1. No user accounts. Anonymous is fine.

USER JOURNEY
1. Land on / — hero + bid form + live board
2. Paste URL → preview title/image/description (Open Graph)
3. Set bid (number stepper, min $1 / 100 cents). Default the input to the current highest board bid.
4. Pay → redirect to provider checkout
5. Return /success?checkout_id=... → poll status until live → show rank + confetti
6. Board shows rank, thumbnail, title, total $, bid count. Click a row → /listing/[id] to add another bid (stack).

DOMAIN (use these names, do not invent a different model)
- listings: id, url, type, title, description, thumbnail_url, favicon_url, author_name, submitted_by_name, total_bid_cents, bid_count, bid_cents_last_24h, status (pending|live|rejected), created_at, last_bid_at
- bids: id, listing_id, amount_cents, bidder_name, provider, checkout_id, payment_id, customer_email, created_at
- pending_checkouts: id, url, preview fields, bid_amount_cents, existing_listing_id (null = new URL), provider_session_id, created_at
- Optional: presence + stats counters for "N online" / views

RULES (must implement)
- Min bid $1 (400 if amount_cents < 100)
- Normalize URLs (https, strip tracking params). Same URL cannot create a second listing — return 409 with existing_listing_id; user must stack a bid instead
- Stacking: paying against existing_listing_id adds amount to total_bid_cents and bid_count
- Rank all-time: ORDER BY total_bid_cents DESC, last_bid_at DESC
- Rank trending: ORDER BY bid_cents_last_24h DESC
- Payments are idempotent: never insert the same payment_id twice
- Flow: create pending_checkout → create provider checkout with metadata { pending_id, bid_amount_cents } → webhook OR success-page poll fulfills → create/update listing + insert bid
- Server-only writes for money (never let the browser increment totals)
- No auth required
- Admin page optional: password gate to reject a listing

PAGES
- / landing (form + board)
- /listing/[id] detail + stack bid + bid history
- /success payment confirmation
- /stats simple counters
- shadcn/ui + Tailwind. Light theme. Mobile-first: stack board cards on small screens.

APIS
- POST /api/preview { url }
- POST /api/checkout { url, preview, bid_amount_cents, existing_listing_id? }
- GET /api/status?checkout_id=
- POST /api/webhooks/[provider]
- GET /api/board?sort=all_time|trending

SHIP
- .env.example with every key AND a comment above each key: what it is + the exact dashboard URL to create it
- README: numbered setup using the PLATFORM DETAILS links below
- Seed + clear scripts
- TypeScript strict. Do not add extra features (auth, AI ranking, comments).


STACK (do not substitute)
- Next.js 14+ App Router, TypeScript, Tailwind CSS, shadcn/ui, React Query or SWR
- Database: Upstash Redis
- Payments: Stripe Checkout

ADAPTER RULES
Store listing hashes + a Redis ZSET score = total_bid_cents for ranking.
Bids as a list per listing. All writes in Route Handlers.
Stripe Checkout + webhook. Fine for a small board; document the scale limit.

ENV KEYS for .env.example (comment each with the dashboard URL from PLATFORM DETAILS)
- NEXT_PUBLIC_SITE_URL
- UPSTASH_REDIS_REST_URL
- UPSTASH_REDIS_REST_TOKEN
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET

Copy every official link from PLATFORM DETAILS into README.md under Setup.
Build the complete runnable app now. After files exist, print: install, env, migrate/seed, webhook listen, npm run dev.