Skip to content

Repository files navigation

Still Standing

A reality-check tool that benchmarks your life against real global data — income percentile, purchasing power, youth unemployment, and resilience — instead of against Instagram. No data is stored; every report is generated on the fly and never persisted.

Quick start

npm install
cp .env.example .env.local   # optional — only needed if you change NEXT_PUBLIC_SITE_URL
npm run dev

Open http://localhost:3000.

Scripts

Command What it does
npm run dev Start the local dev server with hot reload
npm run build Production build
npm run start Run the production build locally
npm run lint Run ESLint
npm run type-check Run the TypeScript compiler with no emit

Project structure

src/
├── app/
│   ├── page.tsx                  # Main page: form <-> report state machine
│   ├── layout.tsx                # Root layout, fonts, metadata
│   ├── globals.css               # Tailwind entrypoint + a11y defaults
│   └── api/generate-report/
│       └── route.ts              # POST endpoint: validates input, runs the engine
│
├── components/
│   ├── Header.tsx, Footer.tsx
│   ├── InputForm.tsx             # Composes the field groups below
│   ├── form/
│   │   ├── Field.tsx             # Shared label/input wrapper + shared classes
│   │   ├── defaults.ts           # Default form values
│   │   ├── PersonalDetailsFields.tsx
│   │   ├── LifeSituationFields.tsx
│   │   ├── EmploymentFields.tsx
│   │   └── HealthFields.tsx
│   ├── ReportView.tsx            # Composes the report sections below
│   ├── ScoreCard.tsx
│   ├── PrivilegeCard.tsx
│   ├── ReframeBox.tsx
│   ├── WinBox.tsx
│   ├── ContextPanel.tsx
│   └── icon-map.ts               # Maps serializable icon keys -> lucide components
│
├── lib/                          # Pure logic — no React, no HTTP. Unit-testable in isolation.
│   ├── data.ts                   # Benchmark data: countries, cities, currencies, percentiles
│   ├── validation.ts             # zod schema — the security boundary for all user input
│   ├── scoring.ts                # Percentile + resilience math
│   ├── privileges.ts             # "Hidden privileges" list generation
│   ├── insights.ts               # Reframe paragraph + actionable win copy
│   └── engine.ts                 # generateReport() — orchestrates everything above
│
└── types/index.ts                # Shared types used across lib/, components/, and the API route

Why this shape

  • lib/ has zero UI or framework dependencies. Every function in lib/ takes plain data in and returns plain data out, so the scoring logic can be tested, audited, or reused (e.g. in a CLI or a different frontend) without touching React.
  • The scoring engine runs server-side, behind /api/generate-report. The client never sees the benchmark data or formulas directly — it just POSTs form values and gets back a finished report. This means you can tweak scoring logic without shipping it to the browser, and it closes off the obvious "edit the JS in devtools to inflate my score" path.
  • All input is re-validated on the server with zod in lib/validation.ts, regardless of what the client already checked. Never trust a request just because it came from your own form.
  • Components are split by responsibility, not by file size for its own sake: each form field group and each report section is its own file, so a change to (say) the health slider only touches HealthFields.tsx.

Security notes

  • Security headers (CSP, X-Frame-Options, etc.) are set in next.config.js.
  • The API route applies a simple in-memory rate limit (20 requests/minute per IP). This resets on server restart and does not share state across multiple server instances — fine for a single small deployment, but if you deploy to a multi-instance/serverless platform at scale, swap it for a shared store (Vercel KV, Upstash Redis, etc.).
  • Free-text fields (city) are length-capped and restricted to letter/space/ punctuation characters via regex, both to keep garbage out of the generated report text and as defense-in-depth against injection.
  • No personal data is persisted anywhere — each request is stateless.

Updating benchmark data

All country/city/currency figures live in src/lib/data.ts. To add a new country, add an entry to COUNTRY_DATA and, if you have a currency that isn't yet in CURRENCY_TO_USD, add it there too — the form's currency dropdown is generated automatically from that table.

Deployment

This is a standard Next.js 14 App Router project — it deploys as-is to Vercel, Netlify, or any Node.js host that supports Next.js. Set NEXT_PUBLIC_SITE_URL in your hosting provider's environment variables to your production domain before going live (used for Open Graph metadata).

License

All rights reserved. This repository is source-visible but not open-source: no license is granted to use, copy, modify, or distribute this code without explicit written permission from the owner.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages