Turn your GitHub profile into an RPG character card.
An animated SVG stats card for your GitHub profile README — class, level, XP,
fame, and combo streak, generated from your public activity. No token, no
config, nothing to install. Just an <img> tag.
Traditional GitHub stats cards answer "what have you done?" GitLevel answers a more fun question: "who are you becoming as a developer?"
Paste this in and swap in your GitHub username — that's the only change:
[](https://gitlevel.vercel.app/u/YOUR_GITHUB_LOGIN)The link wraps the image so the card opens your character sheet —
/u/YOUR_GITHUB_LOGIN — where the XP breakdown, the full class ladder, your
season standing and every badge's next rung live. A bare  still
works, it just goes nowhere when clicked.
Already embedded a card? Cards used to be plain images, and a plain image in a README opens the image when clicked — not your sheet. The link lives in your markdown, so no update on our side can fix an embed that's already out there. Wrap what you have in
[...](https://gitlevel.vercel.app/u/YOUR_LOGIN)and it becomes clickable:-  + [](https://gitlevel.vercel.app/u/YOU)
Or with sizing and a theme — size it with card_width, not a fixed height:
the card grows a row taller as you earn more badges, so a hardcoded height
squashes it.
<a href="https://gitlevel.vercel.app/u/YOUR_GITHUB_LOGIN">
<img src="https://gitlevel.vercel.app/api/card?username=YOUR_GITHUB_LOGIN&theme=volt&card_width=560" alt="GitLevel card" />
</a>Running your own deployment? Swap
gitlevel.vercel.appfor your domain — see SETUP.md.
- Class — your most-used language becomes an RPG profession (Python → Oracle, Rust → Sentinel, C++ → Warlord, …), with a crest glyph.
- Subclass — your second language, drawn beneath your class as a matching mini crest with a glyph and accent colour of its own (Python Oracle, then C++ Warlord) — so the card shows both halves of what you actually write.
- Level & XP — earned from craft, consistency, and a dampened dash of reach. Early levels come fast; the top tiers take real work.
- Rarity — five bands (Common → Rare → Epic → Legendary → Mythic), shown by the stars, frame colour, and a crown at Legendary+.
- Fame — followers + stars. Lifts your level without dominating it, and at truly elite reach sets a rarity floor.
- Combo — your contribution streak in days. Consistency is craft, so a long streak lifts your level too.
- Season rank — one line (
S3 ’26 · SILVER) for the current quarter. Level is a career and barely moves once you're established; this moves weekly. See Seasons. - Achievement badges — earned independent of level, so a newer dev still
has something to show off. Most evolve as you grow (On a Roll → Ablaze →
Inferno → Eternal Flame), and a few rare pins are inferred from the
shape of an account rather than any single number. The card shows your best
four so the crest keeps top billing; the rest — and what each one's next
rung costs — live on your character sheet.
?badges=Nif you want more.
A GitHub username is the only required input; everything else is inferred.
Click the card and it opens your character sheet at /u/YOUR_LOGIN.
All 14 badge families
Nine are ladders — you only ever display the highest rung you've reached, so a family is worth at most one badge and the card never fills up with near-misses:
| Family | Signal | Rungs |
|---|---|---|
| 🔥 Streak | contribution streak | On a Roll 7d → Ablaze 30d → Inferno 100d → Eternal Flame 365d |
| 🌐 Languages | languages shipped | Polyglot 3 → Hyperpolyglot 6 → Babel 10 |
| 🤝 Reviews | PR reviews (past yr) | Collaborator 10 → Gatekeeper 100 → Guardian 500 |
| 📦 Repos | repos created | Founder 5 → Architect 20 → Worldbuilder 50 |
| ⭐ Stars | stars earned | Starstruck 100 → Constellation 1k → Supernova 10k |
| ⏳ Tenure | account age | Veteran 5y → Elder 10y → Ancient One 15y → Day One 17y |
| ⚓ Merged PRs | merged PRs | Shipwright 25 → Fleetmaster 150 → Armada 500 |
| 📡 Followers | followers | Beacon 100 → Luminary 1k → Icon 10k |
| 🐛 Issues | issues closed | Bug Hunter 25 → Exorcist 150 → Purifier 500 |
Plus 🌱 Rising — under a year old with 30+ commits. It expires, on purpose: it exists to give a brand-new account something on day one.
Four are rare and inferred from the shape of an account. Each is a conjunction, which is what makes them uncommon and what lets them say something no single count can:
| Badge | Earned when |
|---|---|
| ✨ Mentor | 50+ reviews and 2× your own merged PRs — you review more than you ship |
| 🐺 Lone Wolf | 10+ repos, 300+ commits, almost no reviews — you build alone |
| 🎭 Renaissance | 6+ languages and 100+ stars and 25+ reviews |
| ⚡ Prolific | 2,000+ commits in the last 12 months alone |
Rare badges — the bold top rungs plus all four inferred ones — render with a brighter fill, a stronger rim and a glass sheen, and always sort to the front so they're the ones that survive if a row runs out of space.
Streak, reviews and commits are last-12-months; stars, followers, repos, merged PRs, closed issues and tenure are lifetime. Thresholds account for the difference. Single source of truth:
src/achievements.js.
Your most-used language becomes your class, promoted through five tiers as you level — shown here at Legendary rank, with full regalia (emblem, rune ring, crown, 4 of 5 stars):
All 30 class ladders, across 31 languages, and their five tiers
| Language | Common | Rare | Epic | Legendary | Mythic |
|---|---|---|---|---|---|
| Python | Adept | Oracle | Seer | Archoracle | Godseer |
| TypeScript | Scribe | Arbiter | Justicar | High Arbiter | Lawgiver |
| JavaScript | Wanderer | Maverick | Outrider | Legend | Mythmaker |
| Rust | Watchman | Sentinel | Guardian | Eternal Guardian | Undying |
| Go | Explorer | Pathfinder | Trailblazer | Wayfinder | Worldwalker |
| Java | Steward | Chancellor | Magistrate | Grand Chancellor | Sovereign |
| C++ | Soldier | Warlord | Conqueror | Overlord | Warbringer |
| C# | Enchanter | Spellsmith | Spellmaster | Archsmith | Runelord |
| Ruby | Performer | Virtuoso | Maestro | Grand Maestro | Luminary |
| PHP | Tinkerer | Artificer | Inventor | Master Artificer | Demiurge |
| Kotlin | Disciple | Ascendant | Exemplar | Paragon | Ascended |
| Swift | Fencer | Duelist | Champion | Grand Duelist | Blademaster |
| C | Operator | Machinist | Systemwright | Kernel Lord | Machine God |
| Zig | Kindler | Voltmage | Tempest | Stormlord | Thunderking |
| Lua | Moonling | Lunar Adept | Moon Sage | Selenarch | Moonlord |
| Verilog / VHDL | Drafter | Circuitwright | Logic Architect | Chip Lord | Silicon Sovereign |
| Elixir | Brewer | Alchemist | Potion Sage | Grand Alchemist | Philosopher |
| Haskell | Scholar | Lambda Adept | Monadic Sage | Category Archon | The Pure |
| Shell | Scripter | Shellbinder | Daemoncaller | Terminal Lord | Root Sovereign |
| Dart | Thrower | Marksman | Sharpshooter | Deadeye | Truesight |
| Scala | Climber | Ridgewright | Summit Sage | Peak Lord | Skybreaker |
| R | Analyst | Statmage | Data Augur | Grand Statistician | Numbermancer |
| SQL | Clerk | Archivist | Query Weaver | Grand Archivist | Data Warden |
| Assembly | Bitsmith | Ironhand | Forgewright | Anvil Lord | Worldforger |
| Julia | Stargazer | Starcaller | Astral Sage | Celestial Magus | Cosmarch |
| Ada | Loomhand | Patternwright | Threadbinder | Grand Weaver | Fatespinner |
| Fortran | Reckoner | Tabulator | Elder Numerist | Ancient Reckoner | Progenitor |
| COBOL | Bookkeeper | Auditor | Vault Keeper | High Auditor | Eternal Ledger |
| Perl | Sigilist | Glyphbinder | Scriptmage | Arch-Sorcerer | Wordbender |
| Erlang | Nodekeeper | Supervisor | Crashbinder | Lord of Nines | The Unbroken |
Any other language falls back to a generic path (Novice → Adept → Expert → Master → Grandmaster) so every developer still gets classed.
Your class comes from language bytes across your own, non-fork repos. If a
data dump or vendored code skews it, drop those languages with exclude_langs.
Easiest way: enter your username on gitlevel.vercel.app and the forge lists the languages you actually ship as clickable chips — drop one and the preview and snippet update as you go, so you can't misspell a language into doing nothing. Or write it by hand:
[](https://gitlevel.vercel.app/u/YOU)Exclusions only change which class you resolve to — XP never comes from language bytes, so dropping a language never costs you a level.
XP blends craft, consistency, and a heavily dampened dash of reach — so what you built dominates, showing up daily counts, and a genuine legend whose work GitHub under-counts still gets recognized.
| Contribution | XP | Multiplier | Cap | |
|---|---|---|---|---|
| Repo created | 120 | Tenure — per year on GitHub | +75% | |
| Merged pull request | 65 | Combo — per day of current streak | +25% | |
| Pull-request review | 40 | |||
| Closed issue | 30 | |||
| Commit | 10 |
tenureMult = 1 + min(years, 15) × 0.05
comboMult = 1 + min(streak, 365) / 365 × 0.25
fameXP = min(40000, 48 × sqrt(followers + stars)) # √-damped, hard cap
totalXP = craftXP × tenureMult × comboMult + streak × 8 + fameXP
level = floor( sqrt( totalXP / 100 ) ) # XP for level L = 100 × L²
Tenure and combo amplify craft rather than adding flat XP, so an old but empty account still scores ≈ 0. Fame is added flat and sqrt-scaled: it tops out around Epic on its own, and Legendary+ still needs real craft underneath.
Level bands are front-loaded, so most active devs climb quickly and Mythic stays a rare summit:
| Tier | Levels | Craft XP to reach¹ | Stars |
|---|---|---|---|
| ⚪ Common | 1 – 5 | 0 | ★ |
| 🔵 Rare | 6 – 14 | 3,600 | ★★ |
| 🟣 Epic | 15 – 28 | 22,500 | ★★★ |
| 🟢 Legendary | 29 – 54 | 84,100 | ★★★★ |
| 🔴 Mythic | 55 + | 302,500 | ★★★★★ |
¹ Before tenure, combo, and fame — a long-tenured or famous dev reaches each tier with proportionally less raw craft.
Reach can also raise rarity directly. Elite Fame sets a tier floor — ≥120k → Legendary, ≥400k → Mythic — so a platform legend (Linus: Linux and Git) gets the frame, stars, and title to match, while the level number stays craft-honest. Thresholds this high can't be faked by one viral repo.
Single source of truth for these numbers:
XP_WEIGHTS,BASE_XP,TENURE,COMBO,FAMEinsrc/engine.js, andFAME_TIER_FLOORSinsrc/classes.js. This doc mirrors them.
Level is a career. Season rank is this quarter.
The level curve is quadratic, which is honest but means an established dev's card effectively stops moving — Lv 30 → 31 is around 350 commits. So there's a second axis that runs on a weekly timescale: every calendar quarter is a season, and season XP counts only what you shipped inside that window.
| Rank | Season XP |
|---|---|
| Iron | 0 |
| Bronze | 1,500 |
| Silver | 5,000 |
| Gold | 12,000 |
| Mythril | 30,000 |
Two deliberate rules:
- Season rank is purely additive. A quiet quarter shows a low rank. It never touches your level, class, tier or badges, and nothing decays — rank decay is how a tool meant to flatter people starts making them resent it.
- No multipliers. Tenure, combo and fame exist to make a career legible. A season is just "what did you make in these 90 days", and it reads the same for a newcomer and a fifteen-year veteran.
Finished seasons accumulate on the shelf on your character sheet. A season's mark can only be earned while that season is running — there is no way to go back and collect Season 1, which is the point.
Season XP uses PRs opened in the window, not merged: a windowed
contributionsCollectionis the only per-period signal GitHub exposes, and it counts openings. Lifetime XP still uses merged PRs. The two ladders are only ever compared to themselves. Seesrc/seasons.js.
GET /api/card — the character card. /api is an alias, so both URLs work.
| Param | Type | Default | Notes |
|---|---|---|---|
username |
string | — | required |
theme |
enum | volt |
volt · midnight · sunset · matrix · ice · transparent |
exclude_langs |
string | — | comma-separated languages to ignore when picking your class (e.g. HTML,CSS) |
hide_border |
bool | false |
|
title_color |
color | theme | hex without # (3/4/6/8) or CSS name |
text_color |
color | theme | |
bg_color |
color | theme | 00000000 = transparent, or a gradient deg,c1,c2 |
border_color |
color | theme | |
glow_color |
color | theme | drives the neon glow filter |
border_radius |
number | 14 |
clamped 0–60 |
card_width |
int | 500 |
clamped 440–800 |
badges |
int | 4 |
how many earned badges the card shows, rarest first; 0 hides the strip. The full set (and each one's next rung) lives on your character sheet |
cache_seconds |
int | 21600 |
clamped 3600–86400 (6h default) |
animation |
bool | true |
false renders a static card |
creator |
bool | true |
false shows a creator's real class instead of the sigil |
The card accent is tinted by your class colour automatically; theme params control the surrounding chrome.
GET /api/profile?username=… — the same character as JSON: level, class and
its full five-title ladder, rarity + division, the XP breakdown (every
contribution row, the tenure/combo multipliers, the flat streak and fame
bonuses), every badge family with its next rung and how far off it is, plus the
tunable config so a client can re-run the model itself. Accepts exclude_langs
and creator like the card. Unlike the card this answers real status codes
(404 unknown user, 429 rate-limited, 503 upstream) — the always-200 rule
exists to protect a README <img>, and there isn't one here.
GET /u/USERNAME — the character sheet the linked card opens: the card itself,
where the XP came from, the class ladder, the season standing and shelf, and
every badge's next rung.
It's a public record, not a control panel. Anyone can open anyone's sheet,
so it describes its subject rather than addressing whoever's reading, and it
takes no settings — configuration happens once, at embed time, in the forge.
The one interactive piece is What if?, which configures nothing: it runs the
real XP model client-side from the config block /api/profile returns (not a
second copy of the formula) so you can see what twenty more merged PRs would be
worth. That serves a curious visitor as much as the person on the card.
Built on /api/profile, so the page is a static asset and the data is cached
once for both it and the card.
GET /api/stats — returns { enabled, uniqueUsers, cardsServed } for a
deployment. Requires the optional Upstash env vars (SETUP.md);
without them it answers enabled: false with both counters at 0 rather than
erroring. The cards served badge above and the counter on the landing page
both read this endpoint, so they go live the moment Upstash is configured — and
show nothing at all until then.
volt |
midnight |
sunset |
matrix |
ice |
transparentno background — blends into any README |
All motion runs once on load and settles (plus a soft glow pulse on the level),
and cards respect prefers-reduced-motion.
README <img> → GitHub Camo → /api/card (Vercel serverless, holds the token)
→ per-user cache → GitHub GraphQL → stat engine → animated SVG
→ Cache-Control headers so Camo/CDN absorb repeat views
- Zero runtime dependencies —
fetchand template strings, nothing else. - Layered caching. The CDN caches each rendered URL; beneath it a per-instance cache keyed on username alone holds the raw GraphQL payload, so one user in six themes is six CDN keys but one API call. With Upstash configured, a durable third layer survives cold starts.
- Errors never break the image slot — unknown user, rate limits, and bad tokens all render a small error SVG with HTTP 200.
- All query params are validated and escaped before touching SVG markup.
You don't need to — anyone can use the public deployment above. But it's ~2 minutes if you want your own domain and rate-limit budget:
→ SETUP.md — one-click deploy, environment variables, and local development.
- Param conventions inspired by anuraghazra/github-readme-stats (MIT).
- Chrome icons adapted from GitHub Octicons (MIT).