DnD 5e Tools is a framework-free, static website that delivers three “table-ready” utilities for Dungeons & Dragons 5e:
- Ability Score Generator (classic 4d6 drop-lowest)
- Physical Stat Generator (race-based age/height/weight with dice parsing)
- Pre-generated Characters gallery (filterable cards linking to PDFs + portraits)
This repo is intentionally “vanilla” (no build step) to highlight fundamentals: clear DOM manipulation, predictable asset conventions, and simple separation between data/service code and UI rendering.
If you’re an employer/recruiter: this project demonstrates practical front-end engineering skills (layout, UX consistency, state/data flow, deterministic file conventions, and maintaining a non-trivial dataset) without hiding behind frameworks.
- Product: static D&D 5e companion tools + pregen character library
- Scope: multiple pages, shared layout, interactive generators, dataset-backed gallery
- Stack: HTML5, CSS3 (Flexbox), JavaScript + jQuery 1.12.4, light polyfills for compatibility
- Deployment: any static host (IIS/Apache/Nginx/GitHub Pages-style hosting), no build pipeline
- Home:
index.html - Ability Generator:
abilities.html - Physical Stat Generator:
physical-stats.html - Pre-generated Characters:
pregen-characters.html
Frontend
- HTML5, semantic-ish page structure
- CSS3 with Flexbox-based layout, responsive breakpoints, and a D&D-inspired theme
- JavaScript
- jQuery for click handlers and simple DOM updates on the generator pages
- Vanilla DOM APIs for the character gallery renderer
Compatibility
pregen-characters.htmlloads ES5/ES6 shims via CDN (for older runtimes)
No build step
- No bundler, transpiler, or package manager required
- Runs as static files behind any web server
Because this site uses XMLHttpRequest to load shared HTML partials (header/footer), it must be served over HTTP(S). Opening files directly via file:// will not load includes.
Option A — Use your existing local web server
- This workspace appears to be configured to run at
http://2021dnd5etools.localhost/. - If you already have that host + server set up, you can use the VS Code task “Open in Browser”.
Option B — Quick local server (no install)
Using Python (if installed):
cd d:\Websites\2021DnD5eTools
python -m http.server 8080Then visit: http://localhost:8080/index.html
Using Node (if installed):
cd d:\Websites\2021DnD5eTools
npx http-server -p 8080This codebase uses a straightforward “static multi-page app” approach:
- Shared layout via HTML partials
header.htmlandfooter.htmlcontain shared navigation and footer markup.js/includeHTML.jsimplements a tiny include system:- finds nodes with
include-html="..." - fetches the referenced file via
XMLHttpRequest - injects the HTML into the element
- finds nodes with
- Feature pages are small and explicit
- Each page includes only the CSS it needs and the JS modules it needs.
- Generators: simple “event → compute → render” flow
- Character gallery: “service → list renderer → DOM mount” flow
- Data is organized by convention
- Pre-generated character PDFs live under
documents/characters/<level>/<Class>/... - Portraits live under
images/characters/<Class>/... - The gallery builds links based on these conventions.
User experience
- Single-call-to-action button
- Shows results in two useful presentations:
- “Assigned” (STR → CHA) for quick character sheet filling
- “Ordered” (high → low) for decision making
Implementation
- Page:
abilities.html - Script:
js/abilityGenerator.js - Uses jQuery
$(document).ready(...)and a click handler on#calc-abilities - Core algorithm per ability:
- roll four integers in [1..6]
- sum them
- subtract the lowest
- Scrolls to results using
window.location.href = '#calculated-abilities'
Engineering notes
- Logic is intentionally readable and testable as pure functions (
rollAbility,rollAbilities).
User experience
- Large select list of races
- One-click generation
- Shows both:
- “Your Stats” (a specific roll)
- “Racial Stat Ranges” (min/max and age brackets)
Implementation
- Page:
physical-stats.html - Data model:
js/races.jscharacterobject holds runtime state- race definitions store dice expressions as strings like
"2d10"
- Logic:
js/physicalCharacteristics.jsprepCharacter(raceName)maps the selected UI string to a race tablegetCharacteristicRanges()computes age brackets and min/max rangesrollDice('XdY')parses dice expressions to produce random rollsconvertInchesToFeet(...)provides user-friendly height output
Design decisions
- Dice expressions are stored as human-readable strings rather than hard-coded distributions.
- Weight depends on the height modifier roll (matching 5e table behavior).
User experience
- Filter by class and level
- Card-based layout with portrait + key info
- One-click access to a PDF character sheet
Implementation
- Page:
pregen-characters.html - “Service” layer:
js/characters-api.service.js- returns an in-memory array of character records (a stand-in for a real API)
- record shape:
character_idcharacter_namecharacter_racecharacter_classcharacter_buildcharacter_max_level
- UI renderer:
js/characters.service.jsCharacterListrenders cards using vanilla DOM APIs- filters by selected class and level
- contains special-case portrait naming for Verdan variants by level
- Wiring:
js/app.jsinstantiatesCharactersServiceandCharacterList, then callscharacterList.init()js/setMaxLevel.jspopulates the level dropdown on class changejs/getCharacterSelection.jsprevents form submission and triggers re-render
Asset/link conventions
Character PDFs:
documents/characters/<level>/<Class>/<Class> <level> [<Build>] - <Character Name>.pdf
Portraits:
images/characters/<Class>/<Character Name>.jpg
Why conventions matter
- The gallery does not “discover” files; it computes URLs from the selected filters + the dataset.
- This makes it easy to host as static files, and easy to audit/maintain once the convention is known.
Theme
- The look aims for a D&D “book/leather/metal” feel using:
- textured background images
- warm gold/red accent colors
- decorative divider sprites
- the Cinzel/Cinzel Decorative typefaces
Layout
- Flexible, responsive layout using Flexbox
- Fixed header + fixed footer to keep navigation and attribution visible
- Hamburger/overlay navigation implemented with a CSS-only toggler pattern
Key styling files
css/common.css: typography, shared elements (buttons, dividers), base layout primitivescss/main.css: main content layout, tool page layout, responsivenesscss/menu.css: hamburger menu and full-screen overlay animationcss/characters.css: character gallery card grid and card styling
Add a new pre-generated character
- Add a PDF:
- Place it under
documents/characters/<level>/<Class>/...using the naming convention.
- Place it under
- Add a portrait:
- Place it under
images/characters/<Class>/<Character Name>.jpg.
- Place it under
- Add a dataset row:
- Add a new object entry to
js/characters-api.service.js.
- Add a new object entry to
Add a new race to the physical generator
- Add a race object in
js/races.js(age, base height/weight, dice modifiers) - Add the race to the
<select>inphysical-stats.html - Add mapping logic in
prepCharacter(...)injs/physicalCharacteristics.js
Because this is static, deployment is simple:
- Any static web host works (IIS, Nginx, Apache, S3-style static hosting)
- No environment variables required
- If you serve from a sub-path, ensure relative URLs remain correct (most links are relative)
If you’re reading this as part of an interview loop, here are realistic improvements that would be good “next steps” in a production iteration:
- Replace the long
if/elsemapping inprepCharacter(...)with a dictionary/lookup table to reduce repetition - Extract the character dataset to a JSON file and fetch it (keeps JS smaller and makes data easier to update)
- Add lightweight unit tests for the pure functions (dice parsing, ability rolling)
- Improve accessibility: keyboard nav for menu, verify color contrast, ensure consistent
alttext - Fix minor HTML validity issues (e.g., some images/viewport meta attributes)
This project is a fan-made utility site for tabletop gameplay. It is not affiliated with or endorsed by Wizards of the Coast.
Special case:
- For Verdan, the image path changes based on level (< 5 uses
(Young), >= 5 uses(Mature)).
The gallery is split into two layers:
CharactersService(js/characters-api.service.js): provides character data (currently static, but structured like a real API call)CharacterList(js/characters.service.js): turns records into DOM and mounts them under#characters
This project must be served over HTTP (not opened via file://) because shared partials are loaded using XMLHttpRequest.
Any static server works; here are a few common choices:
Python
# from the repo root
python -m http.server 8000Then open the URL printed by the server (typically http://127.0.0.1:8000/).
Node (one-liner)
npx serve .If you use VS Code tasks, you can run the workspace’s Open in Browser task (see .vscode/tasks.json) to open whatever URL you’ve configured for your local server.
- Add a new record to the array returned by
CharactersService.getCharacters()injs/characters-api.service.js. - Add the PDF to:
documents/characters/<level>/<Class>/
- Add the portrait image to:
images/characters/<Class>/
Ensure the filename matches the conventions described above.
The gallery builds file paths from a mix of:
- the selected class/level in the form (dropdown text/value), and
- the character record fields (
character_name,character_build,character_class,character_race).
That means spelling, spaces, punctuation, and casing must match across:
- the class dropdown (
pregen-characters.html) - folder names under
documents/characters/<level>/<Class>/andimages/characters/<Class>/ - the
character_nameandcharacter_buildstrings injs/characters-api.service.js
PDF filenames are generated like:
<Class> <level> [<Build>] - <Character Name>.pdf
Example:
documents/characters/5/Warlock/Warlock 5 [The Undying] - Utassi Birdcruncher.pdf
Image filenames are generated like:
<Character Name>.jpg
Example:
images/characters/Warlock/Utassi Birdcruncher.jpg
Verdan special case:
- For a character whose
character_raceis exactlyVerdan, the image name must be either"<Name> (Young).jpg"(level < 5) or"<Name> (Mature).jpg"(level >= 5).
- Add/update the race object in
js/races.js. - Ensure
prepCharacter()injs/physicalCharacteristics.jsmaps the UI label to the correct race object.
prepCharacter(race) compares the selected dropdown label text (e.g. "Elf, Dark (Drow)") against a long set of if/else string checks.
When adding/renaming a race:
- Update the
<option>label text inphysical-stats.htmland the matching string inprepCharacter()together. - Ensure the referenced race object exists in
js/races.jsand has the expected fields (AdultAge,MaxAge,BaseHeight,HeightModifier,BaseWeight,WeightModifier).
- Separation of concerns: a small “service” layer (
CharactersService) provides structured data, whileCharacterListfocuses on DOM rendering. - Deterministic conventions: PDFs and images are linked through consistent folder/file naming so content can scale without rewriting logic.
- Progressive compatibility: shims are included where needed to keep client-side logic working across older environments.
This project is a fan-made utility site and is not affiliated with Wizards of the Coast. Dungeons & Dragons and D&D are trademarks of Wizards of the Coast.