npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

haxball-ui-framework

v1.0.1

Published

Minimal DOM-based UI framework for HaxBall client overlays — windows, drag, resize, native button injection, and the HaxBall theme.

Readme

haxball-ui-framework

HaxBall UI Framework is a minimal, stable UI layer for building overlay windows on top of the HaxBall client.

It is not a full framework like React. It is not a game engine. It is a small, well-designed UI core that gives you full DOM control without fighting HaxBall.


🧠 Core Idea

HaxBall UI Framework introduces a thin overlay layer over the HaxBall DOM that enables:

  • window creation and management
  • dynamic content updates
  • clean lifecycle and destruction
  • full CSS isolation via Shadow DOM
  • safe event handling that doesn't leak into the game
  • drag & resize, native-styled buttons, and a theme that matches HaxBall's own UI

Everything is built around a single principle:

One API. One namespace. Full control.


⚙️ Architecture

The framework is divided into layers, each with a single responsibility:

| Layer | Module | Responsibility | | :--- | :--- | :--- | | Bootstrap | RootMount | Detects execution context, anchors #haxui-root to document.body, re-anchors if HaxBall clears the DOM | | Registry | WindowManager | Window registry, lifecycle, and z-index stack | | Instance | Window | Builds the DOM tree for one window, owns its Shadow Root | | Safety | EventGuard | Per-event-type policy so window events never leak into the game | | Safety | EventRegistry | Tracks every listener so destroy() cleans up with zero leaks | | Style | StyleManager | Injects base styles per theme into each Shadow Root | | Content | Sanitize | DOMParser-based sanitization — strings never hit innerHTML raw | | Interaction | DragManager | Drag windows by their header, clamped to the viewport | | Interaction | ResizeManager | Resize from any of the 4 corners, with minimum dimensions | | Integration | ButtonInjector | Injects native-styled buttons into HaxBall's own button bar | | Public API | HaxUI | window.HaxUI — the only global name exposed | | Handle | WindowHandle | Lightweight object returned per window — no internals exposed |


📦 Project Structure

haxball-ui-framework/
│
├── core/
│   ├── HaxUI.js            # Public API entry point
│   ├── WindowManager.js    # Window registry, lifecycle, z-index stack
│   ├── Window.js           # Individual window with Shadow DOM
│   ├── RootMount.js        # Root node, context detection, re-anchor
│   ├── EventGuard.js       # Per-event-type policy for game isolation
│   ├── EventRegistry.js    # Listener registry for clean destroy()
│   ├── StyleManager.js     # Theme-aware styles injected per Shadow Root
│   ├── DragManager.js      # Header drag, viewport-clamped (v1)
│   ├── ResizeManager.js    # Corner resize handles (v1)
│   └── ButtonInjector.js   # Native HaxBall-styled button injection (v1)
│
├── constants/
│   └── config.js           # BASE_Z_INDEX, themes, namespace, operation modes
│
├── utils/
│   └── sanitize.js         # DOMParser wrapper for safe setContent()
│
├── dev/
│   ├── playground.js       # 13 test groups, 70+ assertions
│   └── examples.js         # 5 worked examples (scoreboard, roster, chat, admin panel, dashboard)
│
├── build.js                # Concatenates modules into a single IIFE bundle
├── package.json
└── haxball-ui.bundle.js    # Generated output — inject this into HaxBall

🚀 Getting Started

1. Clone the repository

git clone https://github.com/mcvn2wrgx2-cpu/haxball-ui-framework
cd haxball-ui-framework

2. Build the bundle

No dependencies required. Just Node.js.

node build.js
# → haxball-ui.bundle.js

3. Inject into HaxBall

Option A — DevTools console (development): Paste the contents of haxball-ui.bundle.js directly into the browser console while HaxBall is open.

Option B — Tampermonkey (recommended): Create a userscript with @require file:///absolute/path/to/haxball-ui.bundle.js and enable local file access in Tampermonkey settings.

4. Verify it loaded

HaxUI.diagnostics();
// { initialized: false, mode: null, rootPresent: false, windowCount: 0, baseZ: 9000, version: 'v0' }

🧩 Public API

Initialize

// Optional — called automatically on first createWindow() if omitted
HaxUI.init({ baseZ: 9000 });

Create a window

const win = HaxUI.createWindow({
  id: 'stats',
  title: 'Statistics',
  width: 260,
  height: 180,
  x: 16,
  y: 16,
  content: '<p>Loading...</p>',

  // v1
  theme: 'default',     // 'default' | 'haxball'
  draggable: true,      // drag from the header
  resizable: true,      // resize from any corner
  titleVisible: true    // show/hide the header on creation
});

Update content

// Safe: pass a Node, not a raw string with external data
const node = document.createElement('div');
node.textContent = 'Goals: ' + data.goals;
win.setContent(node);

// Also valid for static markup
win.setContent('<p>Match ended</p>');

Show / hide / title (v1)

win.show();
win.hide();

win.hideTitle();   // collapse the header, content fills the window
win.showTitle();

Destroy

win.destroy();

// Or by ID
HaxUI.destroyWindow('stats');

// Or everything at once (use on script unload)
HaxUI.destroyAll();

Get an existing window

const existing = HaxUI.getWindow('stats');
if (existing) {
  existing.setContent('<p>Restarting...</p>');
}
// getWindow() returns null if not found — never throws

Native-styled buttons (v1)

const btn = HaxUI.createButton({
  id: 'stats-btn',
  label: '📊 Stats',
  onClick: () => win.show()
});

btn.destroy();
// or
HaxUI.destroyButton('stats-btn');

createButton() injects directly into HaxBall's own .header-btns container, appearing inline with the native Rec / Link / Leave buttons. If that container isn't mounted yet, the button falls back to a fixed overlay and a MutationObserver re-injects it the moment HaxBall's UI becomes available.


🎨 The haxball Theme

theme: 'haxball' replicates HaxBall's native .dialog style using values extracted directly from the live DOM (background, border-radius, header accent line, font, button colors) — so a HaxUI window can look indistinguishable from one of HaxBall's own dialogs:

HaxUI.createWindow({
  id: 'confirm',
  title: 'Leave room?',
  theme: 'haxball',
  width: 300,
  height: 150,
  x: 100,
  y: 100,
  content: '<p>Are you sure you want to leave the room?</p><button>Leave</button>'
});

🔒 Design Decisions

Every decision responds to a specific HaxBall environment risk.

| Decision | Risk mitigated | | :--- | :--- | | Shadow DOM per window (with CSS namespace fallback) | HaxBall's global CSS bleeding into overlay elements | | Single window.HaxUI namespace | Collisions with HaxBall's own globals or other scripts | | EventGuard with per-event-type policy | Keyboard/mouse events leaking into the game | | BASE_Z = 9000, configurable | Overlay windows rendering behind HaxBall's own menus | | MutationObserver in RootMount | HaxBall clearing the DOM on room transitions | | DOMParser in setContent() | XSS when rendering external strings (player names, chat) | | WindowHandle._destroyed flag | Safe post-destroy calls — no errors inside game callbacks | | Context detection in RootMount.init() | Script injected into the wrong frame (iframe environments) | | Drag listeners attached/removed per-cycle, outside EventRegistry | Registry listeners persist for the window's lifetime — using them for drag caused the cursor to never release | | ButtonInjector targets .header-btns with a MutationObserver fallback | Native button bar not mounted yet when the script runs | | buttonBorder: '0' in the HaxBall theme | HaxBall's native buttons have no border — verified via getComputedStyle() on the live DOM, not assumed |


🎯 Example: Live Stats Overlay

HaxUI.init();

const stats = HaxUI.createWindow({
  id: 'haxui-stats',
  title: 'Stats',
  width: 260,
  height: 180,
  x: 16,
  y: 16
});

function onGameData(data) {
  const node = document.createElement('div');
  node.innerHTML = [
    '<div>Team 1: ' + data.score[0] + '</div>',
    '<div>Team 2: ' + data.score[1] + '</div>',
    '<div>Possession: ' + data.possession + '%</div>',
    '<div>Time: ' + data.time + '</div>'
  ].join('');
  stats.setContent(node);
}

function onLeaveRoom() {
  HaxUI.destroyAll();
}

More worked examples — live scoreboard, player roster, chat log, admin panel, and a full multi-window dashboard — are in dev/examples.js.


🗺️ Roadmap

  • [x] v0 — Core
    • [x] Window creation and destruction
    • [x] Dynamic content updates
    • [x] Shadow DOM isolation with CSS fallback
    • [x] Event isolation from the game
    • [x] DOM re-anchor on HaxBall transitions
  • [x] v1 — Interaction
    • [x] Drag & drop windows
    • [x] Resize from all four corners
    • [x] Native HaxBall-styled buttons (createButton)
    • [x] haxball theme matching the native .dialog style
    • [x] hideTitle() / showTitle()
  • [ ] v2 — Components
    • [ ] Component system for setContent()
    • [ ] Base components: text, table, list, button
  • [ ] v3 — Plugins
    • [ ] HaxUI.use(plugin) plugin registration
    • [ ] Window lifecycle hooks for plugins
    • [ ] Cook a Cake

⚠️ Status

Project Status: v1 — drag, resize, native buttons, and the HaxBall theme are stable. Public API may still grow toward v2, but the v0 contract is frozen and won't break.


🧠 Design Philosophy

  • minimal surface area, maximum control
  • one global name, zero internals exposed
  • every decision traceable to a real HaxBall environment risk, verified against the live DOM
  • extensible to v2–v3 without breaking the v0/v1 API contract

📄 License

MIT