Cradle is a personal, static, framework-free website that catalogs a growing collection of small experiments: games, AI/ML demos, dev tools, productivity utilities, and misc projects. The repository is not one application but two layers: a root landing page that lists and links to every project, and a projects/ directory of independent, self-contained mini-projects. A small Node.js script scans the project folders and generates the metadata the landing page reads, so the list of projects never has to be hand-maintained.
- Give visitors and contributors a single entry point to browse every experiment in the repo
- Keep each project fully isolated and independently runnable, with no shared framework or build step
- Derive project metadata from the folder structure instead of a hand-written registry
- Offer a small, dependency-free UI component library that the landing page and, optionally, individual projects can reuse
- Stay runnable as static files, with no bundler or server required
cradle/
├── index.html # Landing page shell: hero, search, category filters, project grid
├── script.js # Landing page logic: fetch/cache projects, render grid, filtering
├── style.css # Landing page styles
├── package.json # npm scripts (generate, dev, build) and repo metadata
├── package-lock.json
│
├── data/
│ └── projects.json # Generated project registry, consumed by script.js
│
├── scripts/
│ ├── generate-projects.js # Scans projects/ and (re)writes data/projects.json
│ ├── theme.js # Light/dark theme init + toggle, persisted to localStorage
│ └── worker.js # Web Worker offloading search/category filtering from the main thread
│
├── src/
│ └── components/
│ └── ui/ # Cradle UI - shared, framework-free component library
│ ├── tokens.css # Design tokens (colors, spacing) shared by all components
│ ├── escapeHtml.js # Shared HTML escaping utility (window.CradleEscape)
│ ├── index.js # Barrel loader exposing window.CradleUI, lazy-loads components
│ ├── demo.html # Standalone demo/preview page for the component library
│ ├── README.md # Component library usage docs
│ ├── Button/Button.js
│ ├── Card/Card.js
│ ├── ThemeToggle/ThemeToggle.js
│ ├── Navbar/Navbar.js
│ └── BackToHome/BackToHome.js
│
├── projects/ # Every experiment, grouped by category
│ ├── aiml/ # ai-circuit-builder, image-classifier, neural-network-playground
│ ├── dev-tools/ # cpu-emulator
│ ├── games/ # 2048-game, cannon-shooting, chess, dot-game, ludo-game,
│ │ # memory-flip-game, stone-paper-scissors-game
│ ├── misc/ # meme-generator
│ └── productivity/ # attendance-tracker
│ └── <project-name>/ # Each project folder is self-contained:
│ ├── index.html # own entry point
│ ├── script.js/logic.js
│ ├── style.css
│ ├── README.md # project-specific readme
│ └── ARCHITECTURE.md # project-specific architecture doc
│
├── tests/ # Root-level tests over individual projects' pure logic modules
│ ├── 2048-grid.test.js
│ ├── 2048-logic.test.js
│ ├── attendance-tracker.test.js
│ ├── chess-logic.test.js
│ ├── memory-flip.test.js
│ └── stone-paper-scissors.test.js
│
├── ARCHITECTURE.md # This file
├── ARCHITECTURE_TEMPLATE.md # Template every project copies for its own ARCHITECTURE.md
├── CONTRIBUTING.md
├── CODE_OF_CONDUCT.md
└── README.md
Cradle is a directory of independent applications sitting behind a shared discovery layer.
- The discovery layer (
index.html,script.js,style.css) has no knowledge of any project's internal code. It only reads the metadata indata/projects.json: a title, a category, and a relative path. - Each project under
projects/<category>/<name>/is a standalone static site with its ownindex.html. It does not import from the root app, and the root app does not import from it. The only requirement is that the folder sits underprojects/sogenerate-projects.jscan discover it. - The component library (
src/components/ui/) is an optional shared dependency, loaded via<script>tags. The root app uses it directly; individual projects may opt in but are not required to. - There is no backend and no build step to run the site: it is served as static files.
graph TD
A[projects/ folder tree] -->|npm run generate| B[scripts/generate-projects.js]
B --> C[data/projects.json]
C -->|fetch| D[script.js]
D --> E[IndexedDB cache: CradleDB]
D --> F[renderCategories / renderProjects]
F --> G[Cradle UI: Card, Button]
D -->|search + category state| H[scripts/worker.js]
H -->|filtered list| F
F --> I[index.html projects-grid]
I -->|Open Project link| J[projects/category/name/index.html]
| File / Path | Responsibility |
|---|---|
index.html |
Landing page markup: hero, search box, category buttons, project grid, footer |
script.js |
Landing page controller: loads/caches project data, renders categories and cards, wires search and filter events |
style.css |
Landing page visual styling |
data/projects.json |
Generated flat list of { title, category, path } for every project |
scripts/generate-projects.js |
Build-time script that walks projects/ and regenerates data/projects.json |
scripts/theme.js |
Light/dark theme state, localStorage persistence, toggle button wiring |
scripts/worker.js |
Web Worker that filters the project list by category and search query off the main thread |
src/components/ui/* |
Shared UI primitives (Button, Card, ThemeToggle, Navbar, BackToHome), the shared HTML escaping utility (escapeHtml.js), and design tokens |
projects/<category>/<name>/ |
One self-contained experiment with its own HTML/CSS/JS, README, and ARCHITECTURE.md |
tests/*.test.js |
Node's built-in test runner exercising pure logic exported by individual projects |
Contributor adds projects/<category>/<project-name>/ with an index.html
↓
`npm run generate` runs scripts/generate-projects.js
↓
Reads every subdirectory of projects/<category>/, title-cases the folder
name (with acronym fixes, e.g. "ai" → "AI", "cpu" → "CPU")
↓
Sorts entries alphabetically by title and writes them to
data/projects.json (generated file, not hand-edited)
↓
Browser opens index.html → loads script.js (ES module)
↓
script.js opens an IndexedDB database ("CradleDB")
↓
Cache hit → render immediately from cached data, then re-fetch
data/projects.json in the background and re-render
Cache miss → fetch data/projects.json directly, populate the cache
↓
renderCategories() builds category buttons; renderProjects() builds one
CradleCard per project, each linking to its path via an "Open Project"
CradleButton
↓
User types in the search box or clicks a category button
↓
If Web Workers are supported, the project list + filters are posted to
scripts/worker.js, which filters off the main thread and posts back the
result; otherwise script.js filters synchronously
↓
renderProjects() re-renders the grid with the filtered list
- Zero-build static site: no bundler, framework, or transpilation needed to run or contribute
- Project registry auto-generated from the folder tree (
scripts/generate-projects.js) - Client-side search and category filtering, offloaded to a Web Worker when available
- Responsive keyboard shortcuts (
/search focus,Escclear/close,Ttheme toggle,?helper modal) - IndexedDB caching of the project list for instant repeat loads, with background refresh
- Light/dark theme with
localStoragepersistence and an inline pre-paint script to avoid a flash of the wrong theme - Shared, dependency-free UI component library (
src/components/ui) usable by the landing page and, optionally, individual projects - Strict isolation between projects: each lives entirely inside its own folder and can run independently
| Technology | Purpose |
|---|---|
| HTML5 | Structure for the landing page and every individual project |
| CSS3 (Custom Properties / design tokens) | Landing page styling and the shared Cradle UI token system |
| Vanilla JavaScript (ES6 modules) | Landing page logic, theme handling, per-project logic |
| IndexedDB API | Client-side caching of the project registry |
| Web Workers | Off-main-thread search/category filtering |
| Node.js | Runs scripts/generate-projects.js at build/dev time |
Node's built-in test runner (node:test) |
Root-level tests for individual projects' pure logic modules |
| Google Fonts (CDN) | Landing page typography (Space Grotesk) |
openDB()/getCachedProjects()- open/read theCradleDBIndexedDB storefetchAndCacheProjects()- fetchdata/projects.json, populate the cacheloadProjects()- orchestrates cache-first loading with background refreshrenderCategories()- builds category filter buttons from the loaded projectsrenderProjects()- builds the project card gridapplyFilters()- dispatches filtering to the Web Worker or falls back to synchronous filteringupdateClearButtonVisibility()/clearFilters()- manage and reset the "Clear Filters" controltoggleShortcutsModal()/ Global keydown event listener - handles accessibility keyboard navigation (/,Esc,T,?)
titleCase(str)- converts a folder name (e.g.ai-circuit-builder) into a display title, with acronym correction (Ai→AI,Cpu→CPU)generateProjects()- walksprojects/<category>/*, builds the sorted registry, writesdata/projects.json
initTheme()- reads the saved or OS-preferred theme on loadapplyTheme(theme)- applies the theme class, persists it, updates the toggle buttontoggleTheme()- flips between light and dark
- Single
onmessagehandler that filters a project list by category and search query and posts the filtered result back
resolveBase()- locates the component library's own base URL regardless of how deep the current page is nested underprojects/CradleUI.loadAll()/CradleUI.load(name)- dynamically load individual component scripts on demand, plus shared utilities (e.g.CradleUI.load("escapeHtml"))
escapeHtml(value)- escapes& < > " 'so user-controlled text is inert when interpolated intoinnerHTML; shared by the affected mini-projects- Exposed as
window.CradleEscape.escapeHtmlin the browser andmodule.exports.escapeHtmlin Node (UMD, mirroringexportHtml.js)
- Generated metadata instead of a hand-written registry -
data/projects.jsonis produced byscripts/generate-projects.jsso adding a project never requires editing a separate list; the folder structure is the single source of truth. - No shared build step across projects - each project under
projects/is self-contained so it can be developed, tested, and even copied elsewhere independently of the rest of the repo. - IndexedDB cache with background refresh - the landing page renders instantly from cache on repeat visits while still picking up newly generated projects shortly after.
- Web Worker for filtering - keeps search/category filtering off the main thread as the project list grows, with a synchronous fallback when Workers are unavailable.
- Zero-dependency component library -
src/components/uiis plain JS/CSS with no build tooling, so any project, including ones written before the library existed, can opt in without adopting a framework. - Per-project ARCHITECTURE.md instead of one giant doc - each project keeps its own architecture notes; this file documents only the shared root infrastructure and metadata flow.
- Engine Modules vs. Inline Logic Rationale - mini-projects featuring dedicated engine/logic/storage modules (
*Engine.js,*Logic.js,*Storage.js) encapsulate pure mathematical, algorithmic, and state computations in UMD modules for headless Node.js unit testing. Meanwhile, inline client scripts (script.js) handle DOM bindings, rendering loops, and browser UI fallbacks, ensuring zero-build static browser execution alongside automated test coverage.
None at runtime. The site uses only native browser APIs (DOM, fetch, IndexedDB, Web Workers) and Node.js's built-in fs/path/node:test modules for tooling and tests.
| Dependency | Version | How loaded | Purpose |
|---|---|---|---|
| Space Grotesk (font) | — | Google Fonts CDN | Landing page typography |
| live-server | — | npm, dev-only (npm run dev) |
Local static file serving during development |
- Add a CI check that fails if
data/projects.jsonis out of date with theprojects/folder tree - Add automated verification that every project folder contains both
README.mdandARCHITECTURE.md - Derive category display labels from a shared enum instead of formatting folder names directly
data/projects.jsoncan drift from the actualprojects/folder tree ifnpm run generateis not re-run after adding/removing a project- Category display names are derived mechanically from folder names (e.g.
dev-tools→ "DEV TOOLS"), which can look inconsistent for multi-word categories Fixed — now usesresolveBase()insrc/components/ui/index.jsrelied on path heuristics (looking for aprojects/segment) to locate itself; unusual hosting setups could break it<meta name="cradle-ui-base">tag (Strategy 1), script-src scanning (Strategy 2),document.currentScript(Strategy 3), with a clean fallback to the repo root
-
Regenerate the project registry after adding, renaming, or removing a project folder:
npm run generate
-
Serve the site locally instead of using
file://, sincefetch("./data/projects.json")and Web Workers require an HTTP context in most browsers:python3 -m http.server 8000
-
Run the root-level test suite:
npm test -
When adding a new project, follow
CONTRIBUTING.mdand copyARCHITECTURE_TEMPLATE.mdinto the new project's own folder as itsARCHITECTURE.md.
README.md- project overview and getting-started instructionsCONTRIBUTING.md- contribution workflow, including the architecture-documentation requirement for new projectsARCHITECTURE_TEMPLATE.md- standardized template used for every per-projectARCHITECTURE.mdsrc/components/ui/README.md- usage docs for the shared Cradle UI component library