Skip to content

Instantly share code, notes, and snippets.

@M3kH
Last active January 29, 2026 11:42
Show Gist options
  • Select an option

  • Save M3kH/4cebaea4f1abf192871035d945db3bf2 to your computer and use it in GitHub Desktop.

Select an option

Save M3kH/4cebaea4f1abf192871035d945db3bf2 to your computer and use it in GitHub Desktop.
Fosdem Demo
// orgp.dev/x.js - Mini-games demo for org-press flyer
import confetti from "https://esm.sh/canvas-confetti";
const el = (t) => document.createElement(t);
// Game 1: Dino runner
const dino = (container) => {
container.innerHTML = `
<div style="position:relative;width:100%;max-width:600px;height:150px;margin:0 auto;border-bottom:3px solid #333;overflow:hidden;font-size:2em">
<span id="dino" style="position:absolute;bottom:0;left:50px;transition:bottom 0.3s">๐Ÿฆ–</span>
<span id="cactus" style="position:absolute;bottom:0;right:-50px">๐ŸŒต</span>
<div id="score" style="position:absolute;top:5px;right:10px;font-size:0.6em">0</div>
</div>
<p style="text-align:center;opacity:0.6;margin-top:0.5em">space or tap to jump!</p>`;
let jumping=false,dead=false,score=0,speed=5;
const jump = () => {
if(jumping||dead)return; jumping=true;
container.querySelector("#dino").style.bottom="80px";
setTimeout(()=>{container.querySelector("#dino").style.bottom="0";jumping=false},400);
};
const iv = setInterval(()=>{
if(dead)return;
const c=container.querySelector("#cactus"),d=container.querySelector("#dino");
let pos=parseInt(c.style.right)||0; c.style.right=(pos+speed)+"px";
if(pos>650){c.style.right="-50px";score++;container.querySelector("#score").textContent=score;speed+=0.2}
const dR=d.getBoundingClientRect(),cR=c.getBoundingClientRect();
if(dR.right>cR.left&&dR.left<cR.right&&dR.bottom>cR.top){dead=true;d.textContent="๐Ÿ’€";confetti();setTimeout(()=>{clearInterval(iv);container._restart?.()},1500)}
},20);
const handler = e => e.code==="Space"&&(e.preventDefault(),jump());
document.addEventListener("keydown",handler);
container.onclick=jump;
container._cleanup = () => { clearInterval(iv); document.removeEventListener("keydown",handler); };
};
// Game 2: Space Invaders
const invaders = (container) => {
container.innerHTML = `
<div id="space" style="position:relative;width:100%;max-width:400px;height:300px;margin:0 auto;background:#000;overflow:hidden;border-radius:8px">
<div id="ship" style="position:absolute;bottom:10px;left:50%;font-size:2em">๐Ÿš€</div>
<div id="aliens" style="position:absolute;top:10px;left:10px"></div>
<div id="score" style="position:absolute;top:5px;right:10px;color:#0f0;font-family:monospace">0</div>
</div>
<p style="text-align:center;opacity:0.6;margin-top:0.5em">โ† โ†’ to move, space to shoot!</p>`;
const space=container.querySelector("#space"),ship=container.querySelector("#ship"),aliensDiv=container.querySelector("#aliens");
let shipX=180,score=0,bullets=[],aliens=[],alienDir=1,gameOver=false;
for(let i=0;i<15;i++){const a=el("span");a.textContent="๐Ÿ‘พ";a.style.cssText=`position:absolute;font-size:1.5em;left:${(i%5)*50}px;top:${(i/5|0)*30}px`;aliensDiv.appendChild(a);aliens.push(a)}
const shoot = () => {if(gameOver)return;const b=el("div");b.style.cssText=`position:absolute;bottom:40px;left:${shipX+15}px;width:4px;height:15px;background:#0f0`;space.appendChild(b);bullets.push(b)};
const iv = setInterval(()=>{
if(gameOver)return;
bullets.forEach((b,i)=>{const top=parseInt(b.style.bottom||0)+10;if(top>300){b.remove();bullets.splice(i,1)}else{b.style.bottom=top+"px";
aliens.forEach((a,j)=>{if(a&&Math.abs(a.offsetLeft+aliensDiv.offsetLeft-parseInt(b.style.left))<20&&Math.abs(a.offsetTop+aliensDiv.offsetTop-(300-top))<20){a.remove();aliens[j]=null;b.remove();bullets.splice(i,1);score+=10;container.querySelector("#score").textContent=score;confetti({particleCount:10})}})
}});
aliensDiv.style.left=(parseInt(aliensDiv.style.left||10)+alienDir*2)+"px";
if(parseInt(aliensDiv.style.left)>150||parseInt(aliensDiv.style.left)<10){alienDir*=-1;aliensDiv.style.top=(parseInt(aliensDiv.style.top||10)+10)+"px"}
if(aliens.every(a=>!a)){gameOver=true;confetti({particleCount:200});setTimeout(()=>{clearInterval(iv);container._restart?.()},1500)}
if(parseInt(aliensDiv.style.top)>200){gameOver=true;ship.textContent="๐Ÿ’ฅ";setTimeout(()=>{clearInterval(iv);container._restart?.()},1500)}
},50);
const handler = e => {if(e.code==="ArrowLeft")shipX=Math.max(0,shipX-20);if(e.code==="ArrowRight")shipX=Math.min(360,shipX+20);if(e.code==="Space"){e.preventDefault();shoot()}ship.style.left=shipX+"px"};
document.addEventListener("keydown",handler);
container._cleanup = () => { clearInterval(iv); document.removeEventListener("keydown",handler); };
};
// Game 3: Whack-a-mole
const whack = (container) => {
container.innerHTML = `<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;max-width:250px;margin:0 auto"></div><p style="text-align:center;font-size:1.5em;margin-top:0.5em">Score: <span id="score">0</span></p>`;
const grid = container.querySelector("div");
let score = 0;
for(let i=0;i<9;i++){const hole=el("div");hole.style.cssText="font-size:2em;height:50px;background:#654;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer";grid.appendChild(hole)}
const holes = grid.querySelectorAll("div");
const pop = () => { holes.forEach(h=>h.textContent=""); holes[Math.random()*9|0].textContent="๐Ÿน"; };
holes.forEach(h=>h.onclick=()=>{if(h.textContent==="๐Ÿน"){score++;container.querySelector("#score").textContent=score;confetti({particleCount:10});pop()}});
pop(); const iv=setInterval(pop,1000);
container._cleanup = () => clearInterval(iv);
};
// Game 4: Memory match
const memory = (container) => {
const emojis = ["๐ŸŽฎ","๐ŸŽฒ","๐ŸŽฏ","๐ŸŽจ","๐ŸŽฎ","๐ŸŽฒ","๐ŸŽฏ","๐ŸŽจ"].sort(()=>Math.random()-0.5);
container.innerHTML = `<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px;max-width:240px;margin:0 auto"></div>`;
const grid = container.querySelector("div");
let flipped=[],solved=0;
emojis.forEach((e)=>{const card=el("div");card.style.cssText="font-size:1.8em;height:50px;background:#333;border-radius:8px;display:flex;align-items:center;justify-content:center;cursor:pointer";card.dataset.e=e;card.textContent="โ“";card.onclick=()=>{if(flipped.length<2&&!flipped.includes(card)&&card.textContent==="โ“"){card.textContent=e;flipped.push(card);if(flipped.length===2)setTimeout(()=>{if(flipped[0].dataset.e===flipped[1].dataset.e){solved+=2;if(solved===8){confetti({particleCount:200});setTimeout(()=>container._restart?.(),1500)}}else flipped.forEach(c=>c.textContent="โ“");flipped=[]},500)}};grid.appendChild(card)});
container._cleanup = () => {};
};
const games = { dino, invaders, whack, memory };
const gameNames = Object.keys(games);
function startGame(wrapper, gameArea, name) {
if(gameArea._cleanup) gameArea._cleanup();
gameArea.innerHTML = "";
wrapper.querySelectorAll("button").forEach(b => b.style.background = b.dataset.game === name ? "#667eea" : "#eee");
wrapper.querySelectorAll("button").forEach(b => b.style.color = b.dataset.game === name ? "#fff" : "#000");
gameArea._cleanup = null;
gameArea._restart = () => startGame(wrapper, gameArea, name);
games[name](gameArea);
}
export function render() {
const wrapper = el("div");
const menu = el("div");
menu.style.cssText = "display:flex;gap:8px;justify-content:center;margin-bottom:1em;flex-wrap:wrap";
const gameArea = el("div");
gameNames.forEach(g => {
const btn = el("button");
btn.textContent = {dino:"๐Ÿฆ– Dino",invaders:"๐Ÿ‘พ Invaders",whack:"๐Ÿน Whack",memory:"๐ŸŽฎ Memory"}[g];
btn.dataset.game = g;
btn.style.cssText = "padding:0.5em 1em;border:none;border-radius:1em;cursor:pointer;font-size:1em;background:#eee";
btn.onclick = () => startGame(wrapper, gameArea, g);
menu.appendChild(btn);
});
wrapper.appendChild(menu);
wrapper.appendChild(gameArea);
// Start random game after element is in DOM
setTimeout(() => startGame(wrapper, gameArea, gameNames[Math.random() * gameNames.length | 0]), 0);
return wrapper;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment