A client-side companion web app to create, refine, and share perk builds for Dead by Daylight. Pick a role, filter by tags, lock/ban perks, and let the optimizer propose up to 4 perks using synergies and exclusion rules (e.g., no double Exhaustion).
- 
Zero backend – everything runs in the browser (no login, no user uploads).
 - 
Multi-source dataset with cache
- public APIs,
 - fallback to 
public/perks.json, - minimal embedded fallback.
Cached in 
localStoragefor 6 hours (dbd-api-cache-v8). 
 - 
Optimizer – ranking by tags + synergy + anti-synergy + mutex with bonuses (tier / rate / killer focus).
 - 
Fast UI – search & filters, Lock/Ban, dynamic suggestions.
 - 
Randomiser – 4-perk random builds that respect bans/mutex/no-duplicates.
 - 
Image export (1200×628) – automatic CORS fallbacks and proxy for perk icons.
 - 
Accessibility – keyboard toggles (Enter/Space), ARIA for dialogs and hints.
 - 
Perfect for static hosting – GitHub Pages / Netlify / Vercel.
 
- Vite + React + TypeScript
 - Tailwind CSS v4 (official 
@tailwindcss/viteplugin) - Canvas 2D for the shareable image
 - localStorage for settings + API cache
 
On load:
- 
Try cache from
localStorage(TTL 6h). - 
Fetch APIs with
cache: "no-store":- Survivor:
GET https://dennisreep.nl/dbd/api/v3/getSurvivorPerkData?description=true - Killer:
GET https://dennisreep.nl/dbd/api/v3/getKillerPerkData?description=true 
 - Survivor:
 - 
Killer enrichment – for each killer owner compute
meta.topForKillersviaGET .../getKillerData?killer=<slug>. - 
If APIs fail → fallback to
GET /perks.json. - 
If that fails too → use the minimal in-code dataset.
 
To invalidate the cache, bump
API_CACHE_KEY(current:dbd-api-cache-v8).
Expected shape:
{
  "version": "2025-09-10",
  "perks": [
    {
      "id": "sprint_burst",
      "name": "Sprint Burst",
      "role": "survivor",
      "tags": ["chase", "exhaustion"],
      "synergy": ["Resilience"],
      "anti_synergy": ["Lithe", "Dead Hard", "Balanced Landing", "Overcome"],
      "desc": "Gain a burst of speed when you start running.",
      "icon": "https://.../sprint_burst.png",
      "meta": { "owner": "Meg Thomas", "tier": "A", "rate": 4.1 }
    }
  ]
}Notes
- 
synergy/anti_synergyare optional; the optimizer also works with tags only. - 
Some tags are derived automatically from name/description:
- Survivor → 
exhaustion - Killer → 
scourge_hook 
 - Survivor → 
 - 
For killers, when available the code augments
meta.topForKillersas[{ "slug": "<killer>", "rank": 1, "usage": 4.2 }, ...]. 
const MUTEX_TAGS = {
  survivor: ["exhaustion"],
  killer: ["scourge_hook"]
};If a picked perk contains a mutex tag, the optimizer avoids another perk with the same mutex tag.
For each candidate perk:
- Selected tags match: +10 per matching tag
 - Synergies (
synergy) with locked/current perks: +8 each - Anti-synergies (
anti_synergy) against current perks: −12 each - Mutex conflict (same mutex tag as current): −100 (hard avoid)
 - Tier bonus: 
S:+10, A:+6, B:+3, C:+0, D:-2, E:-4, F:-6 - Rate bonus: clamp 
rateto[0..5], then(rate − 2.5) * 3 - Killer focus: if enabled and present in 
topForKillers,bonus = max(0, 14 - (rank - 1) * 2)(rank 1 → +14, rank 2 → +12, …) - Tiny tie-breaker favoring shorter names
 
The final build contains up to 4 perks, respecting bans, locks, and mutex.
- 
Role switch (Survivor / Killer)
 - 
Search & Filters
- Owner (dynamic label: Killer or Survivor), Tier, Min Rate
 - Clickable tag chips
 
 - 
Perk list
- Click to toggle description (collapsible)
 - Lock / Ban buttons
 
 - 
Optimizer (right column)
- Shows up to 4 suggestions
 - Killer focus (killer role only)
 - Share build → export PNG
 
 - 
Randomiser modal
- Re-roll 4-perk build by role, respects bans/mutex/no-dupes
 
 - 
Persistence in
localStorage(role, selected tags, locked, banned, filters) 
- 
Size: 1200×628
 - 
Flexible layout: each card’s height is measured dynamically (title/meta/tags) so rows align even with long titles.
 - 
Icons: rounded-corner draw, compact size; single-line ellipsis for long titles/tags.
 - 
CORS-safe icon loading sequence:
- Standard CORS 
crossOrigin="anonymous" referrerPolicy="no-referrer"- Proxy: 
https://images.weserv.nl/?url=<host/path> 
 - Standard CORS 
 - 
Sharing: uses Web Share API with a file when available, otherwise triggers PNG download.
 
Requirements: Node 18+
# install dependencies
npm install
# dev server
npm run devTailwind v4 is already configured via the Vite plugin. In your main CSS:
@import "tailwindcss";npm run buildOutput goes to dist/.
Add scripts to package.json:
{
  "scripts": {
    "build": "vite build",
    "predeploy": "npm run build && node -e \"require('fs').copyFileSync('dist/index.html','dist/404.html')\"",
    "deploy": "gh-pages -d dist -b gh-pages"
  }
}Then:
npm run deployGitHub Settings → Pages:
- Source: Deploy from a branch
 - Branch: 
gh-pages/ root 
For a project site (https://USERNAME.github.io/REPO_NAME/), set in vite.config.ts:
export default defineConfig({
  base: '/REPO_NAME/',
  plugins: [react(), tailwind()]
})and read the dataset with:
fetch(import.meta.env.BASE_URL + 'perks.json')If the site lives at the domain root:
vite.config.ts:base: '/'- dataset fetch: 
fetch('/perks.json', { cache: 'no-store' }) - Configure Settings → Pages → Custom domain and your DNS
(CNAME 
www→USERNAME.github.io, A-records at apex → GitHub Pages IPs) - Enable Enforce HTTPS
 
- 
404 for
vite.svgorperks.jsonon Pages → usuallybaseor absolute pathsvite.config.ts→base: '/REPO_NAME/'index.htmlfavicon →href="%BASE_URL%vite.svg"- fetch → 
import.meta.env.BASE_URL + 'perks.json' 
 - 
CORS errors for icons during export Normal if the host doesn’t expose CORS. The code tries no-referrer and then uses images.weserv.nl as a proxy.
 - 
Unchecked runtime.lastErrorin console This typically comes from a browser extension, not the site. - 
Empty dataset after APIs The app logs a warning and falls back to
/perks.json, then to the minimal embedded dataset. - 
Layout not centered / spacing issues Outer wrapper uses
flex justify-center, inner usesmx-auto. Tune grid gaps and paddings via Tailwind classes in JSX. 
public/
  perks.json
src/
  App.tsx
  main.tsx
  index.css                # @import "tailwindcss"
scripts/
  make_perks_from_dennisreep.mjs   # optional: dataset generation
vite.config.ts
Example script (HTML scraper with cheerio):
# generate/update public/perks.json
npm run make-perksConfigure sources in scripts/make_perks_from_dennisreep.mjs.
You can also maintain perks.json manually or from other structured sources.
PRs and issues are welcome. If you change tags, (anti)synergies, or scoring heuristics, please include a short rationale in the PR.
MIT — see LICENSE.
DBD Build Optimizer is a fan-made project, not affiliated with Behaviour Interactive. All trademarks and game content are property of their respective owners.