tools.jamesking.io
A collection of small, single-purpose web tools, each built as a standalone, browser-based application. Deployed at tools.jamesking.io.
The guiding principles:
- No build step for the tools themselves — pure HTML, CSS and vanilla JavaScript. Any dependencies are loaded from a CDN.
- Local-first — tools run entirely in the browser; prefer browser-native APIs over libraries.
- Self-contained — each tool lives in its own folder and works when opened directly.
- Consistent — tools share a common look via the design system.
The only build step in the repo is
build.js, which generates the landing page and the styled README pages. Individual tools never need compiling or bundling.
Repository structure
tools/
├── index.html # GENERATED landing page (do not edit by hand)
├── build.js # Generates index.html + README pages from each tool
├── README.md # This file (rendered to /readme on the site)
├── netlify.toml # Deploy config — runs `npm run build`
├── design-system/ # Shared tokens + components (see below)
│ ├── tokens.css
│ ├── components.css
│ └── index.html # styleguide gallery
├── netlify/
│ └── edge-functions/ # e.g. rss-proxy.ts (CORS proxy for feeds)
└── <tool-name>/ # One folder per tool
├── index.html # The tool (for hosted-here tools)
└── README.md # Tool docs (first heading + paragraph feed the index)
Adding a new tool
Create a folder in the repo root named in
kebab-case(e.g.pdf-merger/).Add
index.html— your tool. Link the shared design system in the<head>(note the../— tools sit one level below the root):<link rel="stylesheet" href="../design-system/tokens.css"> <link rel="stylesheet" href="../design-system/components.css">Then build the UI from the design-system classes (
btn,card,field,badge,dialog, etc.). Keep only genuinely tool-specific layout in a small inline<style>, and reference tokens viahsl(var(--token))so it tracks the theme. Seesyndicate-elsewhere/for a worked example.Add
README.md— must start with a level-1 heading and a short summary paragraph. These are extracted for the tool's card on the landing page:# PDF Merger Combine multiple PDFs into one, entirely in your browser.The full README is also rendered as a styled page at
<tool>/readme.html.Rebuild so the landing page and README pages pick up the new tool:
npm run build
Externally-hosted tools
If a tool lives elsewhere (another domain, an Apple Shortcut, etc.), it still gets a card — create a folder with just a README.md and add YAML frontmatter:
---
external: true
url: https://fonts.jamesking.io
---
# Font collection
A short description for the card.
The "Open Tool" button will link to url (opening in a new tab) instead of a local index.html.
Folders that are not tools
Shared infrastructure is excluded from the landing page via IGNORE_DIRS in build.js (currently design-system and netlify). Add to that set if you introduce more non-tool folders.
Development conventions
- Formatting: 2-space indentation.
- Naming:
camelCasefor JS variables/functions,kebab-casefor CSS classes and folder names. - JavaScript: ES6+,
const/letovervar. Vanilla is the default; React is acceptable via CDN/import-maps where a tool genuinely needs it (seeaudiolog-episode-creator). - Error handling: wrap async/IO work in
try/catch. - Browser support: Chrome, Firefox, Safari, Edge (current versions).
These are also recorded in CLAUDE.md for AI-assisted work.
Design system
A shared, no-build CSS layer keeps the tools visually consistent. It lives in design-system/:
| File | Purpose |
|---|---|
tokens.css |
Design tokens as CSS custom properties — colors (HSL channel triplets), spacing, type, radii, shadows. Light / dark / system theming. |
components.css |
Class-based components: btn, card, input/field, select, switch, badge, tabs, dialog, toast, table, prose, plus utilities. |
index.html |
Living gallery of every token and component, with a light/dark toggle. Open this as the reference. |
Theming is driven by data-theme on <html>:
data-theme="light"/"dark"— forced.- No attribute (or
"system") — follows the OS viaprefers-color-scheme.
Tools with a theme setting should resolve it in JS and set data-theme to light/dark.
Conventions: don't redefine component styles inside a tool — add only app-specific layout, and prefer a tool-scoped class over overriding a .btn/.card rule. When you add a new shared component, also add it to index.html. Full details in design-system/README.md.
Build & deploy
npm install # one-time: install build/dev dependencies
npm run build # generate index.html + all README pages
npm start # serve locally (http-server) and open the index
build.js produces, from the project's README files:
index.html— the landing page (cards + links), styled with the design system.readme/index.html— this README, rendered as a styled page (/readmeon the site).<tool>/readme.html— each tool's README as a styled page (linked from its "Documentation" button).design-system/readme.html— the design-system docs, rendered as a styled page.
All of these are generated artifacts and are gitignored (/index.html, /readme/, */readme.html) — never edit them by hand; edit the source README.md / build.js and rebuild.
Deployment: Netlify runs npm run build on deploy (see netlify.toml) and publishes the repo root, so the generated pages are always fresh. Edge functions in netlify/edge-functions/ are deployed alongside — e.g. rss-proxy.ts, a CORS proxy used by syndicate-elsewhere to fetch RSS feeds from the browser, and feed.ts, which serves a radio-archive-rss podcast feed encoded into its own URL.