Live app: https://muscle-strength-psi.vercel.app/
Conference booth web app for Entelo Bio.
Visitors enter a grip dynamometer score and receive:
- muscle age
- percentile vs population norms
- celebrity tier match
- cell identity match
- educational factoid
- optional leaderboard placement
- React + TypeScript + Vite
- Tailwind CSS v4
- Framer Motion
- Zustand (state + localStorage persistence)
- canvas-confetti
npm installcp .env.example .envThe app runs without any required secrets. .env keys are optional branding/integration values.
npm run devThen open the local URL shown in the terminal (usually http://localhost:5173).
npm run build
npm run previewnpm run dev- start local dev servernpm run build- type-check and build production bundlenpm run preview- preview built app locallynpm run lint- run ESLint
- Welcome
- Branded landing screen with animated background.
- Profile
- Required: name, age, sex.
- Optional: company, email.
- Grip Entry
- Up to 3 attempts.
- Best attempt is used for result calculations.
- Loading Reveal (3s)
- Deliberate reveal animation.
- Confetti for strong results.
- Results
- Muscle age, percentile, celebrity tier, cell identity, rotating factoid.
- Buttons for leaderboard, replay, email modal.
- Leaderboard
- Top results for the current device/browser storage.
- Staff reset requires a confirmation tap.
Implemented in src/utils/calculations.ts:
getPercentile(grip, age, sex):- Uses sex-specific age bands and p5/p25/p50/p75/p95 values.
- Piecewise linear interpolation to estimate continuous percentile.
getMuscleAge(grip, sex):- Compares grip against median (p50) trend across age bands.
- Interpolates to a smooth age-equivalent score.
calculateResult(...):- Returns best grip, percentile, muscle age, celebrity tier, and cell type.
- Grip norms:
src/data/gripNorms.ts - Celebrity tiers:
src/data/celebrityTiers.ts - Cell types:
src/data/cellTypes.ts - Educational factoids:
src/data/factoids.ts
- Leaderboard is stored in browser
localStorage. - Key is managed inside
src/hooks/useGameStore.ts. - Data is local to that device/browser unless a backend is added.
- Recommended display: fullscreen/kiosk mode.
- Results/leaderboard screens auto-reset to welcome after 60 seconds of inactivity.
- Staff can use the top-right reset control to restart quickly between visitors.
- Leaderboard reset button has a confirm step to reduce accidental clears.
Use:
http://localhost:5173/?capture=1
This hides the particle background for cleaner screenshot capture.
src/
components/ # reusable UI (e.g. particle background)
data/ # norms, tiers, factoids
hooks/ # Zustand store
pages/ # screen-level components
types/ # TypeScript domain types
utils/ # pure calculation helpers
- Add backend persistence (Supabase/Firebase) for cross-device leaderboard.
- Replace QR placeholder with real QR asset/link.
- Wire email modal to an API endpoint for outbound report emails.