
Security News
Lovable’s OJ Rewrites Vite’s Dev Server in Rust as AI Lowers the Cost of Forking Open Source
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.
A tiny, dependency-free retro arcade game engine: DPI-aware canvas, render-safe game loop, keyboard/mouse/touch input, Supabase-or-localStorage leaderboard, overlay screens with retro high-score entry, and drawing helpers.
A tiny, dependency-free retro arcade game engine — the shared parts every small canvas game needs, extracted into one file you can drop in with a
<script>tag.
Retroix was extracted from a family of vanilla-JS arcade games (Paddix, Alienix, Space-Jam, Castle …) that all kept re-implementing the same plumbing. It gives you:
requestAnimationFrame with a clamped delta; can't die mid-frame.onGround/blocked flags.solids() that feeds collision rects straight into physics.begin/end world transform.title / play / pause / over with enter/update/exit hooks.after / every callbacks and eased property tweens.localStorage for best scores and settings.top / submit / qualifies).roundRect, a particle-burst system, and a fading toast helper.retroix.css — the neon-on-dark theme (HUD, buttons, screens, initials, leaderboard).…so a game is just its own update(dt) / render(ctx) and level data.
No build step required. Load it straight from a CDN — it's a UMD file, so
window.Retroix is available globally:
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/retroix@0.3.0/retroix.css">
<script src="https://cdn.jsdelivr.net/npm/retroix@0.3.0/retroix.js"></script>
Or from the GitHub repo (no npm needed):
<script src="https://cdn.jsdelivr.net/gh/DanMat/Retroix@v0.3.0/retroix.js"></script>
Or with a bundler:
npm install retroix
import Retroix from 'retroix';
import 'retroix/css';
<canvas id="game" width="480" height="640"></canvas>
<script>
var view = Retroix.canvas('#game', 480, 640); // dpr-aware
var input = Retroix.input(view); // keyboard + pointer
var board = Retroix.leaderboard({ gameId: 'demo' }); // local unless Supabase configured
var x = 240, y = 320;
Retroix.loop(function (dt) {
var a = input.axis();
x += a.x * 200 * dt; y += a.y * 200 * dt;
var ctx = view.ctx;
ctx.fillStyle = '#0a0a1c'; ctx.fillRect(0, 0, 480, 640);
ctx.fillStyle = '#00e5ff'; Retroix.gfx.roundRect(ctx, x - 15, y - 15, 30, 30, 6); ctx.fill();
}).start();
</script>
| Call | What you get |
|---|---|
Retroix.canvas(el, w, h) | { ctx, width, height, dpr, resize(), toLogical(cx,cy) } |
Retroix.loop(step, opts) | { start(), stop(), isRunning() } — step(dt) in seconds |
Retroix.input(canvas, opts) | { actions, pointer, down(a), axis(), onKeyDown(fn) } |
Retroix.audio(opts) | { coin(), jump(), laser(), explosion(), …, play(name), jingle(name), tone(spec), sequence(notes), mute(v), volume(v), toggle() } |
Retroix.fx(dims, opts) | { shake(a), flash(color,s), freeze(dur), frozen(), update(dt), preRender(ctx), postRender(ctx) } |
Retroix.hit | rects(a,b), circles(a,b), circleRect(c,r), pointRect(px,py,r), pointCircle(px,py,c) |
Retroix.physics | body(opts), move(body, solids, dt) — arcade integration + AABB resolution |
Retroix.grid(cols, rows, tile) | { at, set, cellAt, rectOf, forEach, solids(isSolid, region) } |
Retroix.camera(view, opts) | { x, y, follow(t,l), clamp(), begin(ctx), end(ctx), worldToScreen, screenToWorld } |
Retroix.fsm(states, initial) | { set(name,…), update(dt), render(ctx), is(name), current() } |
Retroix.timer() | { after(s,fn), every(s,fn), tween(obj,props,dur,ease,done), cancel(t), update(dt) } |
Retroix.ease | linear, inQuad, outQuad, inOutQuad, inCubic, outCubic, outBack, outBounce |
Retroix.storage(ns) | { get(key,fallback), set(key,val), remove(key), keys(), clear() } |
Retroix.leaderboard(cfg) | { top(n), submit(initials,score,stage), qualifies(score), mode } |
Retroix.screens(root) | { show(id), hideAll(), current() } over [data-screen] elements |
Retroix.initials(el, {onEnter}) | 3-letter entry: { value(), reset(), handleKey(e), active } |
Retroix.renderLeaderboard(tbody, rows, opts) | fills a <tbody> with score rows |
Retroix.gfx | roundRect, particles() |
Retroix.toast(el) | returns toast(msg, ms) |
Retroix.util | clamp, rand, randInt, pick, hypot, dist2, lerp, shuffle, escapeHtml |
No files — every sound is synthesized. The first key/tap unlocks audio (browsers require a gesture); mute and volume persist across sessions.
var sfx = Retroix.audio(); // { volume: 0.35 } by default
sfx.coin(); // built-in presets
sfx.explosion();
sfx.play('laser'); // …or by name
sfx.jingle('win'); // short stingers: win / lose / levelup / gameover
sfx.tone({ wave: 'square', freq: 'C5', freqEnd: 'C6', dur: 0.15 }); // custom blip
sfx.sequence(['C5:0.08', 'E5', 'G5']); // your own melody
sfx.toggle(); // mute button; sfx.volume(0.5) to set level
var fx = Retroix.fx(view); // pass the canvas api (or { width, height })
// on a big hit:
sfx.hit(); fx.shake(0.6); fx.flash('#fff', 0.4); fx.freeze(0.05);
// in the loop:
fx.update(dt);
if (!fx.frozen()) { updateWorld(dt); } // hitstop pauses the world, not the loop
fx.preRender(ctx); // shake offset applied
drawWorld(ctx);
fx.postRender(ctx); // flash overlay drawn on top
Bodies integrate gravity/friction and resolve against static rects. A grid turns your level into those rects for free:
var grid = Retroix.grid(40, 30, 16); // 40×30 tiles, 16px each
grid.set(5, 20, 1); // 1 = solid
var hero = Retroix.physics.body({ x: 80, y: 40, w: 12, h: 16, gravity: 0.6, friction: 0.2, maxVx: 4, maxVy: 12 });
Retroix.loop(function (dt) {
var a = input.axis();
hero.vx = a.x * 4;
if (input.down('up') && hero.onGround) { hero.vy = -11; sfx.jump(); }
var solids = grid.solids(null, { x: hero.x - 40, y: hero.y - 40, w: 120, h: 120 }); // only nearby tiles
Retroix.physics.move(hero, solids, dt);
// hero.onGround / hero.blocked.left|right|up|down are now set for this frame
}).start();
Cap velocity (
maxVx/maxVy) below your tile size — arcade physics steps discretely, so an uncapped body can skip clean over a thin wall.
var cam = Retroix.camera(view, { bounds: { x: 0, y: 0, w: 640, h: 480 } });
cam.follow(hero, 0.1); // smooth follow
cam.begin(ctx); drawWorld(ctx); cam.end(ctx);
var game = Retroix.fsm({
title: { enter: function () { showScreen('title'); } },
play: { update: function (dt) { stepWorld(dt); } },
over: { enter: function () { sfx.jingle('gameover'); } }
}, 'title');
// game.set('play'); game.update(dt); if (game.is('play')) { … }
var tm = Retroix.timer();
tm.tween(hero, { y: hero.y - 20 }, 0.3, 'outBack'); // little hop
tm.after(1.5, function () { game.set('over'); });
// in the loop: tm.update(dt);
Retroix.leaderboard({
supabaseUrl: 'https://YOUR-PROJECT.supabase.co', // blank = local only
supabaseAnonKey: 'sb_publishable_...', // public by design
gameId: 'paddix',
size: 10
});
The Supabase board expects a scores table (game, initials, score,
stage, created_at) protected by row-level-security. See any consuming game's
docs/supabase.sql.
retroix.css styles .rx-* classes and reads CSS variables you can override
(--rx-accent, --rx-pixel, --rx-panel, …) to reskin per game. Load the
Press Start 2P font for the headings.
MIT © DanMat
FAQs
A tiny, dependency-free retro arcade game engine: DPI-aware canvas, render-safe game loop, keyboard/mouse/touch input, Supabase-or-localStorage leaderboard, overlay screens with retro high-score entry, and drawing helpers.
The npm package retroix receives a total of 1 weekly downloads. As such, retroix popularity was classified as not popular.
We found that retroix demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.