MechGen is a pixel art mech avatar generator with over 10 million combinations, a full JavaScript API, platform-specific exports, a marketing page with feature sections, and SEO-optimized structured data. It is also a single HTML file. No React. No Vue. No Webpack. No npm install. No node_modules. Just one file with roughly 2,900 lines of HTML, CSS, and JavaScript.

This is not a limitation or a shortcut. It is a deliberate architectural choice, and it comes with trade-offs worth examining.

The Architecture

Open index.html and you find three sections in the order browsers expect them:

  1. HTML structure: Semantic markup for the full page (nav, hero, generator UI, feature sections, API docs, FAQ, footer), plus meta tags, Open Graph tags, and Schema.org structured data for SEO.

  2. CSS in a <style> block: Dark cyberpunk theme using CSS custom properties (--cyan, --magenta, --bg-deep), responsive breakpoints, animations, and a scanline overlay effect.

  3. JavaScript in a <script> block: The entire application logic, including the PRNG, all drawing functions, the MechGen API, UI event handlers, and export utilities.

Everything is self-contained. The only external resources are two Google Fonts loaded via <link> tags.

Canvas-Based Rendering

The visual heart of MechGen is a 64x64 pixel canvas. Every mech component — heads, eyes, arms, legs, antenna, bodies, accessories — is drawn programmatically using ctx.fillRect() calls. There are no sprite sheets, no SVGs, no image assets.

Here is what drawing a mech part looks like in practice:

// Simplified example of drawing a "Dome" head
function drawDomeHead(ctx, colors) {
  ctx.fillStyle = colors.primary;
  // Main head block
  ctx.fillRect(20, 8, 24, 18);
  // Dome curve (stepped pixels)
  ctx.fillRect(22, 6, 20, 2);
  ctx.fillRect(26, 4, 12, 2);
  // Highlight
  ctx.fillStyle = colors.lighter;
  ctx.fillRect(22, 8, 2, 8);
}

Each part category has ten variants, and each variant is a function that places colored rectangles on the canvas. The drawing happens in a specific back-to-front order — accessory, legs, body, arms, head, antenna, eyes — to get correct visual layering.

The 64x64 canvas is then displayed at a larger size using CSS with image-rendering: pixelated, which preserves the crisp pixel edges instead of blurring them. For exports, the canvas is redrawn at the target resolution (512px default, with platform-specific sizes for Discord, GitHub, X, and others).

Why Canvas Over SVG or DOM?

For pixel art specifically, canvas is the natural fit:

  • Direct pixel control: fillRect() maps exactly to placing colored pixels on a grid. SVG or DOM elements would add abstraction that fights the pixel art paradigm.
  • Fast compositing: Drawing hundreds of rectangles in a single render pass is trivially fast on canvas. No layout calculations, no reflow, no paint invalidation.
  • Clean export: canvas.toDataURL("image/png") gives you a base64 PNG in one call. No html2canvas hacks, no screenshot libraries.
  • Resolution independence: Redraw at any size by scaling the coordinate system. The same drawing code produces a 128px thumbnail or an 800px YouTube avatar.

Why No Framework?

The honest answer: MechGen does not need one. Here is the decision framework that led to vanilla JavaScript:

What does the UI actually do?

The generator UI has a canvas, some buttons, a few dropdown-style part selectors, color pickers, and text displays that show the current state. The marketing page below it is static content.

This is not a data-driven dashboard with complex state flows. It is not a multi-page app with routing. There are no lists that grow and shrink dynamically, no real-time data subscriptions, no deeply nested component trees.

The total interactive surface is small: click a button, update a value, re-render the canvas, sync a few text labels. Vanilla JavaScript handles this with direct DOM manipulation:

function updateUI() {
  document.getElementById("head-label").textContent = HEAD_NAMES[state.head];
  document.getElementById("head-counter").textContent =
    `${state.head + 1} / 10`;
  // ... similar for other parts
}

A framework would add an abstraction layer over operations that are already simple.

What about state management?

MechGen’s state is a single flat object with about a dozen keys (seven part indices and four color values). When anything changes, the entire canvas re-renders and updateUI() syncs the DOM. There is no partial update optimization needed because the render is fast (sub-millisecond for a 64x64 canvas) and the DOM updates touch fewer than 20 elements.

React’s virtual DOM diffing, Vue’s reactivity system, Svelte’s compiled updates — these are powerful tools for managing complex, frequently changing UI trees. For MechGen’s scope, they would be solving a problem that does not exist.

What about the developer experience?

This is the most honest trade-off. JSX is pleasant to write. Component-based architecture is clean. Hot module replacement speeds up iteration. These are real benefits.

But they come with costs: a build step, a node_modules directory, configuration files, version management, and a deployment pipeline that needs to handle bundling. For a single-purpose tool, those costs outweigh the ergonomic benefits.

With a single HTML file, the development workflow is: edit the file, refresh the browser. Deployment is: push the file to a static host. There is nothing to configure, nothing to update, nothing to break.

The Trade-Offs

Being honest about the downsides matters. The single-file approach has real limitations:

Maintainability at Scale

At 2,900 lines, the file is manageable. At 10,000 lines, it would not be. If MechGen needed to grow into a full platform with user accounts, galleries, and social features, the single-file approach would become a liability. The current scope sits comfortably within the pattern’s limits.

No Component Reuse

Every UI pattern is written inline. There is no button component, no card component, no reusable modal. If the same pattern appears in multiple places, it is duplicated or handled by a shared function. For MechGen’s relatively flat UI, this is fine. For a deeply nested interface, it would lead to painful repetition.

Testing

Unit testing vanilla JavaScript in a single HTML file is awkward. You cannot import individual functions into a test runner without extraction. Integration testing via Playwright or Puppeteer works well (load the page, call the API, check results), but fine-grained unit tests require more effort.

Collaboration

Multiple developers working on the same file leads to merge conflicts. This pattern works best for solo developers or very small teams with clear ownership boundaries.

When This Pattern Works

The single-file, zero-dependency approach is a good fit when:

  • The tool has a bounded scope: MechGen generates pixel mechs. It is not going to become a social network. The feature set is defined and finite.
  • Performance matters more than developer ergonomics: No framework means no framework overhead. The page loads instantly — there is no JavaScript bundle to parse, no hydration step, no framework initialization. The canvas renders on first paint.
  • Longevity matters: A vanilla HTML file written today will work in browsers ten years from now. Framework code often requires ongoing maintenance just to keep up with breaking changes in the ecosystem.
  • Distribution simplicity matters: A single file can be hosted anywhere, embedded in anything, forked with zero setup. It is the most portable format a web application can take.

Performance Characteristics

The numbers speak clearly for the zero-dependency approach:

  • Zero JavaScript framework overhead: No virtual DOM diffing, no reactivity bookkeeping, no component lifecycle management.
  • No build artifacts: What you write is what the browser loads. No transpilation, no tree-shaking, no code splitting — because there is nothing to split.
  • Instant interactivity: The generator is functional as soon as the script block executes. There is no hydration delay.
  • Minimal network requests: One HTML file plus two font files. Three requests total for the complete application.

The Philosophy

Choosing vanilla JavaScript for a tool like MechGen is not a rejection of modern frameworks. React, Vue, and Svelte are excellent tools that solve real problems at scale. The choice is about matching the tool to the job.

MechGen is a focused utility that does one thing: generate pixel mechs. It does not need routing, server-side rendering, state management libraries, or a component ecosystem. Adding those things would not make it better at generating mechs. It would just make it harder to deploy, slower to load, and more complex to maintain.

Sometimes the best dependency is no dependency at all.

Try the MechGen generator to see what 2,900 lines of vanilla code can do, or read about the JavaScript API to use it programmatically.