Stellix Glow — Free One-Tap Firefly Flying Game Using HTML, CSS and JS

Stellix Glow — Free One-Tap Firefly Flying Game Using HTML, CSS and JS

One Button. That’s the Whole Game. Good Luck.

Some of the most addictive games ever made have exactly one control. Think about it — the games that stole entire weeks from people weren’t the complicated ones with tutorials and skill trees. They were the ones you understood in a single second and then couldn’t put down for an hour. Tap to jump. Tap to flap. Tap to not die.

Stellix Glow is my take on that idea, and here’s the pitch in one line: you’re a little glowing firefly flying through a night full of giant teal crystals, and every tap keeps you in the air. That’s it. That’s the game. And if you’re thinking “sounds easy” — please, go ahead and try. My personal record took me an embarrassing number of attempts, and I built the thing.

How It Plays

Open the game and you’ll see the firefly gently bobbing in the middle of a starry night sky, waiting. Tap the screen (or press Space on a computer) and the game begins. Every tap gives your firefly a little upward push; the moment you stop tapping, gravity pulls it back down. Crystal towers scroll toward you from the right — one hanging from the sky, one rising from the ground — with a gap between them. Your entire job is to tap at the right rhythm so the firefly glides through each gap.

Pass a gap, and your score goes up by one with a satisfying little chime. Touch a crystal, the ceiling, or the ground — and it’s over, with a burst of golden sparks and a Game Over card showing your score and your best. Then one more tap and you’re flying again, because “one more try” is the entire psychology of this genre and I leaned into it completely.

The Part That Makes It Addictive: It Gets Harder

A one-tap game with constant difficulty gets boring in two minutes. So Stellix Glow quietly turns the pressure up as you play. With every point you score, the world scrolls a little faster, and the gaps between crystals grow a little narrower. The first five points feel almost relaxing. By fifteen, you’re locked in. Past twenty, you’re not blinking.

This ramp is what turns a casual toy into a real score-chase. Your first game might end at 3. Your tenth might end at 11. And somewhere in there, the game stops being about the firefly and starts being about beating the number in the top corner — your session best, sitting there, judging you.

Small Details, Big Feel

I believe the difference between a game that feels cheap and a game that feels good is a pile of tiny details most players never consciously notice. So let me point some of them out, because I spent real time on them.

The firefly isn’t a static sprite — its wings actually flap, fast and blurry while you’re flying, slow and lazy while it waits on the start screen. It carries a warm amber glow that lights up the night around it, because that’s what fireflies do. Every tap releases a few golden sparks behind it, like it’s shedding light with the effort. When it dives, it tilts nose-down; when you tap, it perks back up — the rotation follows the physics, not an animation file.

The night itself is alive too. Seventy stars twinkle at their own rhythms and drift slowly past, giving the sky depth. There’s a crescent moon. The crystals aren’t plain rectangles — they’re faceted, pointed formations with glowing teal edges, like something growing out of the dark.

And there’s sound: a soft blip for every flap, a bright chime for every point, and a low buzz when you crash — all generated live by the browser’s own audio engine, so there are zero sound files to load. Everything you hear is being synthesized the moment it happens.

Fair Physics on Every Device

Here’s a problem most people don’t know browser games have: on a fast gaming PC the screen refreshes 144 times a second, and on an older phone maybe 30. If a game naively moves things “a little bit per frame,” it literally runs almost five times faster on the good machine — which makes high scores meaningless.

Stellix Glow uses delta-time physics, meaning every movement is calculated based on real elapsed time, not frame count. Gravity pulls with the same force, crystals approach at the same speed, and a score of 20 means the same achievement whether you earned it on a flagship phone or a five-year-old laptop. When you’re competing with friends for the best score — and you will be — that fairness matters.

The game also detects your screen’s pixel density and renders sharp on Retina and high-DPI displays, and the portrait play area scales itself to fit any screen, from a small phone to a widescreen monitor. Touch and keyboard both work. No installation, no app store, no loading screens — it’s a single file that starts instantly.

Why I Keep Building Games Like This

Between the practical tools I make — converters, generators, editors — I always come back to small games, and there’s a reason beyond fun. A game like this is secretly one of the best programming lessons that exists. Inside this single file there’s a game loop, real gravity and velocity physics, collision detection, a particle system, procedural difficulty, and synthesized audio — the same core concepts behind every big game you’ve ever played, just small enough to actually understand.

That’s the whole spirit of Coding Stellix: build something genuinely fun or useful, keep it in one readable file, and then teach exactly how it works. I break down projects like this step by step on YouTube and Instagram at @coding_stellix, so if you’ve ever wanted to build your own game — not follow a tutorial blindly, but actually understand one — this firefly is a pretty good place to start.

Your Turn

The game is free, it’s instant, and it works on whatever device you’re reading this on right now. Tap to fly, dodge the crystals, and see how far the little firefly can carry you.

Fair warning before you start: the first crash feels unfair, the fifth feels personal, and by the tenth you’ll be saying “okay, ONE more” — which, statistically, is never one more.

Drop your best score in the comments. And if you beat 25, I want proof. 😄

Coding Stellix

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Stellix Glow — Firefly Flying Game by Coding Stellix</title>
<meta name="description" content="Stellix Glow — a free one-tap flying game by Coding Stellix. Guide a glowing firefly through crystal towers in the night. Tap to fly, dodge the crystals, beat your best score!">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Jost:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
:root{
  --night:#0a0e1e;
  --amber:#ffc24d;
  --teal:#3ddad7;
  --ink:#eef1ff;
}
*{margin:0;padding:0;box-sizing:border-box;font-family:'Jost',sans-serif;-webkit-tap-highlight-color:transparent}
html,body{height:100%;overflow:hidden}
body{background:var(--night);color:var(--ink);display:flex;flex-direction:column}

.topbar{
  display:flex;align-items:center;justify-content:space-between;
  padding:12px 18px;flex-shrink:0
}
.logo{display:flex;align-items:center;gap:9px;font-weight:700;font-size:1rem}
.logo .dot{
  width:32px;height:32px;border-radius:9px;display:grid;place-items:center;
  background:linear-gradient(135deg,var(--amber),#ff9d2e);color:#231400;font-weight:800;font-size:.85rem;
  box-shadow:0 0 18px rgba(255,194,77,.5)
}
.logo b{color:var(--amber)}
.best{
  font-size:.85rem;font-weight:600;color:#aab2d5;
  border:1px solid #262c48;border-radius:99px;padding:6px 14px;background:#10152b
}
.best b{color:var(--amber)}

.game-wrap{flex:1;display:flex;align-items:center;justify-content:center;padding:0 12px 12px;min-height:0}
canvas{
  border-radius:18px;box-shadow:0 24px 70px rgba(0,0,0,.6),0 0 0 1px #1c2340;
  touch-action:manipulation;cursor:pointer;max-width:100%;max-height:100%
}
footer{flex-shrink:0;text-align:center;font-size:.75rem;color:#5d6588;padding:0 0 10px}
footer b{color:var(--amber)}
</style>
</head>
<body>

<div class="topbar">
  <div class="logo">
    <div class="dot">CS</div>
    <span>Stellix <b>Glow</b></span>
  </div>
  <div class="best">BEST <b id="bestVal">0</b></div>
</div>

<div class="game-wrap">
  <canvas id="cv"></canvas>
</div>

<footer>Made with ✨ by <b>Coding Stellix</b> — @coding_stellix</footer>

<script>
(function(){
  const cv=document.getElementById('cv'), ctx=cv.getContext('2d');
  const bestEl=document.getElementById('bestVal');

  /* ---------- sizing (portrait play area) ---------- */
  const GW=420, GH=640; // logical game size
  let scale=1;
  function fit(){
    const wrap=document.querySelector('.game-wrap');
    const aw=wrap.clientWidth-8, ah=wrap.clientHeight-8;
    scale=Math.min(aw/GW, ah/GH);
    const dpr=Math.min(window.devicePixelRatio||1,2);
    cv.width=Math.round(GW*scale*dpr);
    cv.height=Math.round(GH*scale*dpr);
    cv.style.width=Math.round(GW*scale)+'px';
    cv.style.height=Math.round(GH*scale)+'px';
    ctx.setTransform(scale*dpr,0,0,scale*dpr,0,0);
  }
  window.addEventListener('resize',fit);
  fit();

  /* ---------- audio (tiny beeps) ---------- */
  let AC=null;
  function beep(freq,dur=.08,type='sine',vol=.15){
    try{
      AC=AC||new (window.AudioContext||window.webkitAudioContext)();
      const o=AC.createOscillator(), g=AC.createGain();
      o.type=type;o.frequency.value=freq;
      g.gain.setValueAtTime(vol,AC.currentTime);
      g.gain.exponentialRampToValueAtTime(.001,AC.currentTime+dur);
      o.connect(g).connect(AC.destination);
      o.start();o.stop(AC.currentTime+dur);
    }catch(e){}
  }

  /* ---------- game state ---------- */
  const G={
    mode:'ready',           // ready | play | dead
    y:GH/2, vy:0, rot:0,
    towers:[],              // {x, gapY, gapH, passed}
    score:0, best:0,
    t:0, deathT:0,
    stars:[], sparks:[]
  };
  const FIREFLY_X=110, R=16;
  const GRAV=1500, FLAP=-420;
  const SPEED0=150, GAP0=185;

  // starfield
  for(let i=0;i<70;i++){
    G.stars.push({x:Math.random()*GW,y:Math.random()*GH,r:Math.random()*1.6+.4,tw:Math.random()*6.28,sp:.15+Math.random()*.35});
  }

  function reset(){
    G.mode='ready';
    G.y=GH/2;G.vy=0;G.rot=0;
    G.towers=[];G.score=0;G.t=0;G.sparks=[];
    spawnTower(GW+120);
    spawnTower(GW+120+240);
  }
  function speed(){return SPEED0+Math.min(90,G.score*3);}
  function gap(){return Math.max(140,GAP0-G.score*1.5);}
  function spawnTower(x){
    const g=gap();
    const gapY=90+Math.random()*(GH-180-g);
    G.towers.push({x,gapY,gapH:g,passed:false,w:74,seed:Math.random()*10});
  }

  function flap(){
    if(G.mode==='ready'){G.mode='play';}
    if(G.mode==='play'){
      G.vy=FLAP;
      beep(520,.06,'sine',.12);
      for(let i=0;i<6;i++)G.sparks.push({x:FIREFLY_X-8,y:G.y+6,vx:-60-Math.random()*60,vy:20+Math.random()*60,life:.5,max:.5});
    }else if(G.mode==='dead'&&G.deathT>.5){
      reset();
    }
  }
  cv.addEventListener('pointerdown',e=>{e.preventDefault();flap();});
  window.addEventListener('keydown',e=>{
    if(e.code==='Space'||e.code==='ArrowUp'){e.preventDefault();flap();}
  });

  function die(){
    if(G.mode!=='play')return;
    G.mode='dead';G.deathT=0;
    beep(180,.25,'sawtooth',.18);
    if(G.score>G.best){G.best=G.score;bestEl.textContent=G.best;}
    for(let i=0;i<24;i++){
      const a=Math.random()*6.28,s=80+Math.random()*180;
      G.sparks.push({x:FIREFLY_X,y:G.y,vx:Math.cos(a)*s,vy:Math.sin(a)*s,life:.9,max:.9});
    }
  }

  /* ---------- update ---------- */
  function update(dt){
    G.t+=dt;
    G.stars.forEach(s=>{s.x-=s.sp*speed()*dt*.3;if(s.x<-4)s.x=GW+4;});
    G.sparks.forEach(p=>{p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=300*dt;p.life-=dt;});
    G.sparks=G.sparks.filter(p=>p.life>0);

    if(G.mode==='ready'){
      G.y=GH/2+Math.sin(G.t*2.4)*14;
      return;
    }
    if(G.mode==='dead'){
      G.deathT+=dt;
      // fall
      G.vy+=GRAV*dt;
      G.y=Math.min(GH-20,G.y+G.vy*dt);
      G.rot=Math.min(1.4,G.rot+3*dt);
      return;
    }

    // play
    G.vy+=GRAV*dt;
    G.y+=G.vy*dt;
    G.rot=Math.max(-.45,Math.min(1.2,G.vy/600));

    const sp=speed();
    G.towers.forEach(t=>t.x-=sp*dt);
    if(G.towers.length&&G.towers[0].x<-90)G.towers.shift();
    const last=G.towers[G.towers.length-1];
    if(last&&last.x<GW-160)spawnTower(last.x+240);

    // score + collide
    G.towers.forEach(t=>{
      if(!t.passed&&t.x+t.w<FIREFLY_X-R){
        t.passed=true;G.score++;
        beep(760,.09,'triangle',.14);
      }
      // AABB vs circle-ish
      if(FIREFLY_X+R>t.x&&FIREFLY_X-R<t.x+t.w){
        if(G.y-R<t.gapY||G.y+R>t.gapY+t.gapH)die();
      }
    });

    if(G.y+R>GH-14||G.y-R<0)die();
  }

  /* ---------- draw ---------- */
  function crystal(t){
    // top tower (pointing down) and bottom tower (pointing up)
    const drawTower=(x,w,yTop,yBot,pointDown)=>{
      const grad=ctx.createLinearGradient(x,0,x+w,0);
      grad.addColorStop(0,'#123c46');
      grad.addColorStop(.5,'#1e6d74');
      grad.addColorStop(1,'#0e2f3a');
      ctx.fillStyle=grad;
      ctx.beginPath();
      if(pointDown){
        ctx.moveTo(x,yTop);
        ctx.lineTo(x+w,yTop);
        ctx.lineTo(x+w,yBot-26);
        ctx.lineTo(x+w*.62,yBot);
        ctx.lineTo(x+w*.38,yBot-14);
        ctx.lineTo(x,yBot-30);
      }else{
        ctx.moveTo(x,yBot);
        ctx.lineTo(x+w,yBot);
        ctx.lineTo(x+w,yTop+26);
        ctx.lineTo(x+w*.62,yTop);
        ctx.lineTo(x+w*.38,yTop+14);
        ctx.lineTo(x,yTop+30);
      }
      ctx.closePath();ctx.fill();
      // edge glow
      ctx.strokeStyle='rgba(61,218,215,.55)';
      ctx.lineWidth=2;ctx.stroke();
      // inner facet line
      ctx.strokeStyle='rgba(61,218,215,.18)';
      ctx.beginPath();
      ctx.moveTo(x+w*.5,pointDown?yTop:yBot);
      ctx.lineTo(x+w*.5,pointDown?yBot-10:yTop+10);
      ctx.stroke();
    };
    drawTower(t.x,t.w,-4,t.gapY,true);
    drawTower(t.x,t.w,t.gapY+t.gapH,GH-12,false);
  }

  function draw(){
    // sky
    const sky=ctx.createLinearGradient(0,0,0,GH);
    sky.addColorStop(0,'#0a0e1e');
    sky.addColorStop(.7,'#101a34');
    sky.addColorStop(1,'#152242');
    ctx.fillStyle=sky;ctx.fillRect(0,0,GW,GH);

    // moon
    ctx.fillStyle='rgba(238,241,255,.9)';
    ctx.beginPath();ctx.arc(GW-70,84,26,0,7);ctx.fill();
    ctx.fillStyle='#101a34';
    ctx.beginPath();ctx.arc(GW-60,78,22,0,7);ctx.fill();

    // stars
    G.stars.forEach(s=>{
      const a=.4+.6*Math.abs(Math.sin(G.t*2+s.tw));
      ctx.fillStyle=`rgba(238,241,255,${a*.8})`;
      ctx.beginPath();ctx.arc(s.x,s.y,s.r,0,7);ctx.fill();
    });

    // towers
    G.towers.forEach(crystal);

    // ground strip
    ctx.fillStyle='#0c1226';
    ctx.fillRect(0,GH-12,GW,12);
    ctx.fillStyle='rgba(61,218,215,.3)';
    ctx.fillRect(0,GH-12,GW,2);

    // sparks
    G.sparks.forEach(p=>{
      ctx.fillStyle=`rgba(255,194,77,${p.life/p.max})`;
      ctx.beginPath();ctx.arc(p.x,p.y,2.4,0,7);ctx.fill();
    });

    // firefly
    if(!(G.mode==='dead'&&G.deathT>2.2)){
      ctx.save();
      ctx.translate(FIREFLY_X,G.y);
      ctx.rotate(G.rot*.5);
      // glow
      const glow=ctx.createRadialGradient(0,0,2,0,0,44);
      glow.addColorStop(0,'rgba(255,194,77,.55)');
      glow.addColorStop(1,'rgba(255,194,77,0)');
      ctx.fillStyle=glow;
      ctx.beginPath();ctx.arc(0,0,44,0,7);ctx.fill();
      // wings
      const wob=G.mode==='play'?Math.sin(G.t*40)*.5:Math.sin(G.t*8)*.35;
      ctx.fillStyle='rgba(200,220,255,.5)';
      ctx.save();ctx.rotate(-.6+wob);ctx.beginPath();ctx.ellipse(-2,-13,13,6,.4,0,7);ctx.fill();ctx.restore();
      ctx.save();ctx.rotate(.1-wob);ctx.beginPath();ctx.ellipse(-2,-13,13,6,-.4,0,7);ctx.fill();ctx.restore();
      // body
      ctx.fillStyle='#3a2a12';
      ctx.beginPath();ctx.ellipse(-4,0,11,9,0,0,7);ctx.fill();
      // glowing tail
      ctx.fillStyle='#ffc24d';
      ctx.beginPath();ctx.ellipse(8,1,8,7,0,0,7);ctx.fill();
      // eye
      ctx.fillStyle='#fff';
      ctx.beginPath();ctx.arc(-10,-3,3,0,7);ctx.fill();
      ctx.fillStyle='#141414';
      ctx.beginPath();ctx.arc(-9.4,-3,1.6,0,7);ctx.fill();
      ctx.restore();
    }

    // score
    if(G.mode!=='ready'){
      ctx.font="800 52px 'Jost'";
      ctx.textAlign='center';
      ctx.lineWidth=6;ctx.strokeStyle='rgba(10,14,30,.7)';
      ctx.strokeText(G.score,GW/2,86);
      ctx.fillStyle='#eef1ff';
      ctx.fillText(G.score,GW/2,86);
    }

    // overlays
    ctx.textAlign='center';
    if(G.mode==='ready'){
      ctx.font="800 40px 'Jost'";
      ctx.fillStyle='#ffc24d';
      ctx.fillText('STELLIX GLOW',GW/2,GH*.33);
      ctx.font="600 19px 'Jost'";
      ctx.fillStyle='#aab2d5';
      ctx.fillText('Tap or press SPACE to fly',GW/2,GH*.33+36);
      const pulse=.5+.5*Math.sin(G.t*3);
      ctx.fillStyle=`rgba(238,241,255,${.5+pulse*.5})`;
      ctx.font="700 17px 'Jost'";
      ctx.fillText('▲ TAP TAP TAP ▲',GW/2,GH*.72);
    }
    if(G.mode==='dead'&&G.deathT>.4){
      ctx.fillStyle='rgba(10,14,30,.72)';
      ctx.fillRect(GW/2-150,GH*.30,300,190);
      ctx.strokeStyle='rgba(255,194,77,.6)';ctx.lineWidth=2;
      ctx.strokeRect(GW/2-150,GH*.30,300,190);
      ctx.font="800 30px 'Jost'";
      ctx.fillStyle='#ffc24d';
      ctx.fillText('GAME OVER',GW/2,GH*.30+52);
      ctx.font="600 20px 'Jost'";
      ctx.fillStyle='#eef1ff';
      ctx.fillText('Score  '+G.score,GW/2,GH*.30+96);
      ctx.fillStyle='#aab2d5';
      ctx.font="500 17px 'Jost'";
      ctx.fillText('Best  '+G.best,GW/2,GH*.30+124);
      ctx.fillStyle='#3ddad7';
      ctx.font="700 17px 'Jost'";
      ctx.fillText('Tap to fly again',GW/2,GH*.30+164);
    }
  }

  /* ---------- loop ---------- */
  let last=performance.now();
  function loop(now){
    const dt=Math.min(.033,(now-last)/1000);
    last=now;
    update(dt);
    draw();
    requestAnimationFrame(loop);
  }
  reset();
  requestAnimationFrame(loop);
})();
</script>
</body>
</html>

Leave a Reply

Your email address will not be published. Required fields are marked *

SHARE:-

Trending Post

Latest Post