A fantasy-themed personality quiz that maps your choices to RPG class archetypes and generates beautiful, shareable result cards.
🎮 Try the Quiz | 📖 Documentation | 🤝 Contributing
Classility is an interactive web application that combines fantasy RPG aesthetics with personality assessment. Answer 20 thought-provoking questions and discover which fantasy class—from Paladin to Spellblade, Druid to Warlord—best matches your personality profile.
Each result includes:
- 🎭 Detailed Character Profile - Lore, traits, and personality insights
- 📊 Stat Breakdown - Visualized dimensional scoring across Power, Faith, Arcana, Nature, Guard, and Tactics
- 🎨 Custom Card - High-quality result cards with class art and equipment (exportable as PNG)
- 🎯 Party Role - Your strategic function in a team setting
- 🌟 Growth Quest - Personalized character development challenge
- Immersive UI - Landing page with animated door-opening transition
- Smart Evaluation - Deterministic scoring using weighted dimensions and conditional rules
- 20 Unique Classes - Each with distinct lore, traits, and visual identity
- Scene Artwork - Optional atmospheric images for quiz questions
- Smooth Transitions - Elegant fade animations between questions
- PNG Export - Generate shareable
900×1400pxresult cards - Route Protection - Prevents result viewing without completing the quiz
- Responsive Design - Works seamlessly across desktop and mobile devices
- Frontend Framework: React 19 + TypeScript
- Build Tool: Vite
- Styling: Tailwind CSS with custom theme
- Routing: React Router (HashRouter for GitHub Pages)
- Fonts: Cinzel & EB Garamond via Fontsource
- Export: Playwright + html-to-image
- Deployment: GitHub Pages with automated CI/CD
- Node.js 20 or higher
- npm (comes with Node.js)
# Clone the repository
git clone https://github.com/SeaBoiii/classility.git
cd classility
# Install dependencies
npm install
# Start development server
npm run devVisit http://localhost:5173 in your browser. The app will automatically reload when you make changes.
# Type-check and build
npm run build
# Preview production build locally
npm run preview
# Run linter
npm run lintThe production build will be output to the dist/ directory.
| Route | Description |
|---|---|
/#/ |
Landing page with animated door entrance |
/#/quiz |
20-question personality assessment |
/#/result |
Your latest result (redirects if no attempt) |
/#/result?seed=demo |
Deterministic demo result for debugging |
/#/cards |
Browse all available class cards |
/#/card/:id |
Standalone card renderer (used for exports) |
/#/dimensions |
View scoring dimensions and axes |
Note: The app uses HashRouter for GitHub Pages compatibility.
The application uses three JSON files in the data/ directory to define quiz behavior:
Defines the personality dimensions that questions measure:
{
"id": "power",
"label": "Power",
"left": "Finesse",
"right": "Force",
"description": "How you approach challenges"
}Fields:
id- Unique dimension identifierlabel- Display nameleft/right- Opposing poles of the spectrumdescription(optional) - Explanation of what this dimension measures
Defines the 20 questions presented during the quiz:
{
"id": 1,
"prompt": "When facing an obstacle...",
"image": "mountain_path.png",
"options": [
{
"text": "Find a way around it",
"weights": { "tactics": 2, "nature": 1 }
}
]
}Fields:
id- Question number (1-20)prompt- Question text displayed to userimage(optional) - Filename fromsrc/assets/scenes/options- Array of exactly 4 answer choicestext- Answer textweights- Object mapping dimension IDs to point values
Defines all possible class results and their matching conditions:
{
"id": "class_paladin",
"title": "Paladin",
"tagline": "A Vow Given Form",
"summary": "You stand as a vow made visible...",
"lore": "Paladins are not simply defenders...",
"priority": 20,
"conditions": [
{ "type": "rank_is", "dim": "guard", "rank": 1 }
],
"classSprite": "paladin.png",
"partyRole": "Defensive Striker",
"growthQuestDifficulty": 4
}Core Identity Fields:
id,title,tagline,summarylore,lore2(optional extended lore)traits- Array of personality traits
Visual Assets:
classSprite- Filename fromsrc/assets/class_sprites/partyRoleCrest- Filename fromsrc/assets/crests/signatureEquipment- Filename fromsrc/assets/equipments/
Metadata:
style,risk,partyRolegrowthQuest,growthQuestDifficulty(1-5 scale)signatureItem,battleHabit
Matching Logic:
priority- Higher values are preferred when multiple classes matchconditions- Array of rules that must ALL pass for this class to matchisFallback(optional) - Last resort if no other classes match
Assets are automatically resolved by the data loader (src/lib/data.ts):
| Asset Type | Source Directory | Fallback |
|---|---|---|
| Scene images | src/assets/scenes/ |
None |
| Class sprites | src/assets/class_sprites/ |
None |
| Party crests | src/assets/crests/ |
Uses classSprite name |
| Equipment art | src/assets/equipments/ |
Uses classSprite name |
The class evaluation system uses a two-stage process:
Each quiz answer adds points to specific dimensions based on the weights defined in the question options. The total score for each dimension determines your personality profile.
Implementation: src/lib/scoring.ts
Classes are matched using rule-based conditions. All conditions for a class must pass for it to be considered.
Implementation: src/lib/evaluator.ts
| Operator | Description | Example |
|---|---|---|
min |
Dimension must meet minimum value | { "type": "min", "dim": "power", "value": 15 } |
max_le |
Dimension must be ≤ value | { "type": "max_le", "dim": "faith", "value": 10 } |
max_ge |
Dimension must be ≥ value | { "type": "max_ge", "dim": "arcana", "value": 18 } |
diff_greater |
Difference between two dims > value | { "type": "diff_greater", "dim1": "power", "dim2": "tactics", "value": 5 } |
diff_abs_lte |
Absolute difference ≤ value | { "type": "diff_abs_lte", "dim1": "guard", "dim2": "faith", "value": 3 } |
top_is |
Highest scoring dimension must be specified dim | { "type": "top_is", "dim": "nature" } |
not_top_is |
Highest scoring dimension must NOT be specified dim | { "type": "not_top_is", "dim": "power" } |
rank_is |
Dimension must rank at specified position | { "type": "rank_is", "dim": "tactics", "rank": 1 } |
top_diff_gte |
Difference between top 2 dims ≥ value | { "type": "top_diff_gte", "value": 8 } |
top_diff_lte |
Difference between top 2 dims ≤ value | { "type": "top_diff_lte", "value": 6 } |
total_min |
Sum of all dimensions ≥ value | { "type": "total_min", "value": 60 } |
total_max |
Sum of all dimensions ≤ value | { "type": "total_max", "value": 80 } |
sum_min |
Sum of specified dims ≥ value | { "type": "sum_min", "dims": ["power", "guard"], "value": 20 } |
sum_max |
Sum of specified dims ≤ value | { "type": "sum_max", "dims": ["arcana", "faith"], "value": 30 } |
spread_between |
Range of scores falls within bounds | { "type": "spread_between", "min": 0, "max": 14 } |
- Evaluate all classes - Check which classes have all conditions satisfied
- Sort passing classes - By
priority(descending), then by file order - Select best match - First class in sorted list
- Fallback logic:
- If no classes pass, choose the "nearest" non-fallback class (best partial match)
- If no matches at all, use the class marked with
isFallback: true
| Command | Description |
|---|---|
npm run dev |
Start Vite development server with hot reload |
npm run build |
Type-check with TypeScript and build for production |
npm run lint |
Run ESLint to check code quality |
npm run preview |
Preview production build locally |
Generate high-quality PNG exports of class result cards:
| Command | Description |
|---|---|
npm run export -- --id <class_id> |
Export single class card to out/ directory |
npm run export:all |
Export all class cards as PNGs |
First-time setup (requires Chromium for Playwright):
npx playwright install chromiumExport details:
- Output size:
900×1400px - Transparent outer background
- Fonts loaded and validated before capture
- Uses headless Chromium via Playwright
| Command | Description |
|---|---|
npm run split:crests |
Split combined crest/equipment PNGs into separate assets |
Workflow for adding new class art:
- Place combined art in
src/assets/crests_equipments/<name>.png - Run
npm run split:crests - Script automatically creates:
src/assets/crests/<name>.png(top half)src/assets/equipments/<name>.png(bottom half)
- Reference in
data/results.jsonor rely on filename fallback
Advanced scripts for optimizing quiz balance and class reachability:
| Command | Description |
|---|---|
npm run tune:reachability |
Analyze if all classes are reachable (dry run) |
npm run tune:reachability:apply |
Tune question weights to ensure all classes are reachable |
npm run tune:balance |
Optimize for balanced class distribution (dry run) |
npm run tune:balance:apply |
Apply balanced weight optimization to questions |
npm run tune:balance:apply-best |
Write best candidate even if strict targets aren't met |
Tuning parameters:
--target-reachability 1- Ensure 100% of classes are reachable--optimize-probability- Optimize for even distribution--probability-samples 40000- Number of random quiz attempts to simulate--probability-tolerance 1- Acceptable deviation from ideal distribution (%)--timeout-ms 3600000- Maximum optimization time (1 hour)--workers 0- Use all available CPU cores
⚠️ These commands modifydata/questions.json. Always commit before running with--writeflags.
The project is configured for automatic deployment to GitHub Pages:
- Workflow:
.github/workflows/deploy-pages.yml - Trigger: Automatic on push to
mainbranch, or manual via workflow dispatch - Base Path: Automatically configured in
vite.config.tsfor/<repo>/path - Router: Uses
HashRouterfor GitHub Pages compatibility (no server-side routing needed)
Deployment process:
- Push to
mainbranch - GitHub Actions runs
npm ciandnpm run build - Outputs
dist/directory to GitHub Pages - Site available at
https://seaboiii.github.io/classility/
For other hosting platforms:
# Build the project
npm run build
# Deploy the dist/ directory to your hosting service
# (Netlify, Vercel, AWS S3, etc.)Note: If deploying to a subdirectory, update the
baseproperty invite.config.ts
classility/
├── .github/
│ └── workflows/
│ └── deploy-pages.yml # GitHub Pages deployment workflow
├── data/
│ ├── dimensions.json # Personality dimension definitions
│ ├── questions.json # Quiz questions and weights
│ └── results.json # Class definitions and conditions
├── scripts/
│ ├── export.ts # Playwright-based PNG export
│ ├── tune-reachability.ts # Balance optimization scripts
│ └── split-crests-equipments.ps1 # Asset splitting utility
├── src/
│ ├── assets/ # Images, sprites, and artwork
│ │ ├── class_sprites/ # Main class character art
│ │ ├── crests/ # Party role crest icons
│ │ ├── equipments/ # Signature equipment art
│ │ ├── scenes/ # Question background images
│ │ └── crests_equipments/ # Combined assets (pre-split)
│ ├── components/ # Reusable UI components
│ ├── lib/
│ │ ├── data.ts # Asset loading and resolution
│ │ ├── evaluator.ts # Class matching logic
│ │ └── scoring.ts # Score calculation
│ ├── pages/ # Route page components
│ │ ├── LandingPage.tsx # Animated entrance
│ │ ├── QuizPage.tsx # Question flow
│ │ ├── ResultPage.tsx # Result display
│ │ ├── CardsPage.tsx # Class browser
│ │ ├── CardPage.tsx # Single card view
│ │ └── DimensionsPage.tsx # Dimension reference
│ ├── styles/ # Global styles and Tailwind
│ ├── types.ts # TypeScript type definitions
│ ├── App.tsx # Root component with routing
│ └── main.tsx # Application entry point
├── index.html # HTML template
├── package.json # Dependencies and scripts
├── vite.config.ts # Vite configuration
├── tailwind.config.js # Tailwind CSS configuration
└── tsconfig.json # TypeScript configuration
Problem: TypeScript errors during build
# Clear cache and reinstall dependencies
rm -rf node_modules package-lock.json
npm install
npm run buildProblem: Vite port already in use
# Start on a different port
npm run dev -- --port 3000Problem: Playwright browser not installed
# Install Chromium browser
npx playwright install chromiumProblem: Export produces blank images
- Ensure fonts are loaded (script waits for this automatically)
- Check that the class ID exists in
data/results.json - Verify all asset paths are correct
Problem: Class never appears as result
- Run
npm run tune:reachabilityto check if class is reachable - Verify the conditions aren't contradictory
- Check if another class with higher priority is always matching first
Problem: Unbalanced class distribution
- Run
npm run tune:balanceto analyze current distribution - Use
npm run tune:balance:applyto optimize question weights
Problem: Changes not reflecting in browser
- Check that hot reload is working (Vite should log updates)
- Hard refresh the browser (
Ctrl+Shift+RorCmd+Shift+R) - Clear browser cache and localStorage
Problem: Images not loading
- Verify asset files exist in correct directories
- Check filename spelling in JSON data files
- Ensure Vite is resolving assets correctly (check browser console)
Contributions are welcome! Here's how you can help:
- Create artwork - Add sprite, crest, and equipment images to appropriate
src/assets/folders - Define class - Add entry to
data/results.jsonwith lore, traits, and conditions - Test balance - Run
npm run tune:reachabilityto ensure class is reachable - Export card - Generate PNG with
npm run export -- --id your_class_id
- Design question - Create engaging scenario with 4 distinct options
- Add to data - Insert into
data/questions.jsonwith appropriate dimension weights - Test balance - Run tuning scripts to ensure question doesn't break distribution
- Optional: Add scene image to
src/assets/scenes/
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Test thoroughly (
npm run dev,npm run build) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Follow existing TypeScript and React patterns
- Use Tailwind CSS for styling (avoid inline styles)
- Run
npm run lintbefore committing - Keep components focused and reusable
- Add comments for complex logic
This project is available for personal and educational use. Please check with the repository owner before using commercially.
- Character art and design inspiration from classic RPG games
- Fonts: Cinzel and EB Garamond
- Built with modern web technologies from the React and Vite ecosystems
Made with ⚔️ by SeaBoiii