Make it better: <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta
    name="viewport"
    content="width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover"
  />
  <title>Cyber‑Neon Snake</title>
  <style>
    /* Reset & Base */
    * { box-sizing: border-box; margin: 0; padding: 0; }
    html, body { height: 100%; overflow: hidden; user-select: none; }
    body {
      display: flex; flex-direction: column; align-items: center; justify-content: center;
      background: linear-gradient(45deg, #0f2027, #203a43, #2c5364);
      background-size: 600% 600%; animation: bgShift 20s ease infinite;
      font-family: 'Segoe UI', sans-serif;
    }
    @keyframes bgShift {
      0% { background-position: 0% 50%; }
      50% { background-position: 100% 50%; }
      100% { background-position: 0% 50%; }
    }

    /* Game Frame */
    #game {
      position: relative;
      width: 90vmin;
      height: 90vmin;
      background: #111;
      border: 4px solid #0f9;
      border-radius: 20px;
      box-shadow: inset 0 0 20px #0f9a;
      touch-action: none;
    }
    canvas {
      width: 100%; height: 100%; display: block;
      background: #111; border-radius: 20px;
    }

    /* Neon Scoreboard (now static below controls) */
    @keyframes neonFlicker {
      0%,18%,22%,25%,53%,57%,100% { text-shadow: 0 0 15px #0f9; }
      20%,24%,55%               { text-shadow: none; }
    }
    #scoreboard {
      margin-top: 12px;
      text-align: center;
      color: #0f9;
      font-size: 1.5rem;
      font-weight: 700;
      animation: neonFlicker 3s infinite;
    }
    #scoreboard.flash {
      color: #fff;
      text-shadow: 0 0 25px #0ff, 0 0 50px #0ff;
      transition: text-shadow 0.2s, color 0.2s;
    }

    /* Controls */
    #controls {
      margin-top: 12px;
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      gap: 12px;
      width: 90vmin;
    }
    .btn {
      background: #0f9; border: none; border-radius: 12px;
      padding: 14px 0; font-size: 1.4rem; font-weight: 700;
      box-shadow: 0 0 20px #0f9; cursor: pointer;
      transition: background 0.2s, box-shadow 0.2s;
      user-select: none;
    }
    .btn:active {
      background: #0c7; box-shadow: inset 0 0 30px #0c7;
    }
    @media (max-width: 400px) {
      .btn { font-size: 1.2rem; padding: 10px 0; }
    }
  </style>
</head>
<body>
  <div id="game">
    <canvas id="canvas"></canvas>
  </div>

  <div id="controls" aria-label="Directional controls">
    <button class="btn" data-dir="left"  aria-label="Move left">←</button>
    <button class="btn" data-dir="up"    aria-label="Move up">↑</button>
    <button class="btn" data-dir="down"  aria-label="Move down">↓</button>
    <button class="btn" data-dir="right" aria-label="Move right">→</button>
  </div>

  <div id="scoreboard">
    Score: <span id="currentScore">0</span>
    | High: <span id="highScore">0</span>
  </div>

  <!-- Sound effect file must be in same folder -->
  <audio id="eatSfx" src="eat.mp3" preload="auto"></audio>

  <script>
  (function(){
    // DOM & audio refs
    const game       = document.getElementById('game');
    const canvas     = document.getElementById('canvas');
    const ctx        = canvas.getContext('2d');
    const scoreEl    = document.getElementById('currentScore');
    const highEl     = document.getElementById('highScore');
    const sb         = document.getElementById('scoreboard');
    const eatSfx     = document.getElementById('eatSfx');
    const btns       = document.querySelectorAll('.btn');

    // Config & state
    let tileCount    = 24;
    let gridSize;
    const fps        = 10;
    const maxApples  = 3;
    let snake        = [{x:12,y:12}, {x:11,y:12}, {x:10,y:12}];
    let velocity     = {x:1, y:0}, nextVel = {x:1, y:0};
    let apples       = [], score = 0, gameOver = false;
    let comboCounter=0, lastAppleTime = Date.now(), hue=0;
    let effects     = { score: [], particles: [] };

    let highScore   = parseInt(localStorage.getItem('snakeHighScore'),10) || 0;
    highEl.textContent = highScore;

    // Dynamic canvas sizing
    function resizeCanvas(){
      const size = game.clientWidth;
      canvas.width  = size;
      canvas.height = size;
      gridSize      = size / tileCount;
    }
    resizeCanvas();
    window.addEventListener('resize', resizeCanvas);

    // Helpers (color gradients, rounded rect, empty‑cell check)
    function colorGradient(isSnake){
      hue = (hue + 2) % 360;
      if(isSnake){
        const c1 = `hsl(${hue},100%,50%)`,
              c2 = `hsl(${(hue+40)%360},100%,60%)`,
              g  = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
        g.addColorStop(0, c1);
        g.addColorStop(1, c2);
        return g;
      } else {
        const g = ctx.createRadialGradient(0,0,5, 0,0, gridSize*0.6);
        g.addColorStop(0, `hsl(${hue},100%,70%)`);
        g.addColorStop(1, `hsl(${(hue+60)%360},80%,40%)`);
        return g;
      }
    }
    function roundedRect(x,y,w,h,r){
      ctx.beginPath();
      ctx.moveTo(x+r,y);           ctx.lineTo(x+w-r,y);
      ctx.quadraticCurveTo(x+w,y,  x+w,y+r);
      ctx.lineTo(x+w,y+h-r);
      ctx.quadraticCurveTo(x+w,y+h, x+w-r,y+h);
      ctx.lineTo(x+r,y+h);
      ctx.quadraticCurveTo(x,y+h,   x,y+h-r);
      ctx.lineTo(x,y+r);
      ctx.quadraticCurveTo(x,y,     x+r,y);
      ctx.closePath();
      ctx.fill();
    }
    function isEmpty(x,y){
      return !snake.some(s=>s.x===x&&s.y===y)
          && !apples.some(a=>a.x===x&&a.y===y);
    }

    // Apple spawning
    function spawnApples(){
      while(apples.length < maxApples){
        const p = {
          x: Math.floor(Math.random() * tileCount),
          y: Math.floor(Math.random() * tileCount)
        };
        if(isEmpty(p.x, p.y)) apples.push(p);
      }
    }

    // Effects (score pop‑up, particles, flash)
    function addScoreFx(x,y,amt){
      effects.score.push({
        x: x*gridSize + gridSize/2,
        y: y*gridSize + gridSize/2,
        alpha: 1, text: '+'+amt, size: 24
      });
    }
    function addParticles(x,y){
      for(let i=0;i<12;i++){
        effects.particles.push({
          x: x*gridSize + gridSize/2,
          y: y*gridSize + gridSize/2,
          dx: (Math.random()-0.5)*8,
          dy: (Math.random()-0.5)*8,
          alpha: 1, size: Math.random()*4+2
        });
      }
    }
    function updateEffects(){
      effects.score.forEach((e,i)=>{
        e.y -= 1.5; e.alpha -= 0.02; e.size += 0.3;
        if(e.alpha <= 0) effects.score.splice(i,1);
      });
      effects.particles.forEach((p,i)=>{
        p.x += p.dx; p.y += p.dy; p.alpha -= 0.03;
        p.color = `hsla(${(hue + i*30)%360},100%,60%,${p.alpha})`;
        if(p.alpha <= 0) effects.particles.splice(i,1);
      });
    }
    function flashScore(){
      sb.classList.add('flash');
      setTimeout(()=> sb.classList.remove('flash'), 200);
      eatSfx.currentTime = 0;
      eatSfx.play();
    }

    // Game update
    function update(){
      if(gameOver) return;
      if((nextVel.x !== -velocity.x || nextVel.y !== -velocity.y)
       &&(nextVel.x !== velocity.x  || nextVel.y !== velocity.y)) {
        velocity = nextVel;
      }
      const head = {
        x: (snake[0].x + velocity.x + tileCount) % tileCount,
        y: (snake[0].y + velocity.y + tileCount) % tileCount
      };
      if(snake.some(p=>p.x===head.x && p.y===head.y)){
        gameOver = true;
        return setTimeout(()=> alert(`Game Over! Score: ${score}`), 100);
      }
      snake.unshift(head);
      let ate = false;
      for(let i=0; i<apples.length; i++){
        if(apples[i].x===head.x && apples[i].y===head.y){
          const now = Date.now();
          const timeB  = (now - lastAppleTime < 3000) ? 5 : 0;
          const comboB = comboCounter * 2;
          const gain   = 10 + timeB + comboB;
          score += gain; comboCounter++; lastAppleTime = now;
          scoreEl.textContent = score;
          if(score > highScore){
            highScore = score;
            localStorage.setItem('snakeHighScore', highScore);
            highEl.textContent = highScore;
          }
          addScoreFx(head.x, head.y, gain);
          addParticles(head.x, head.y);
          flashScore();
          apples.splice(i,1);
          spawnApples();
          ate = true;
          break;
        }
      }
      if(!ate){
        comboCounter = 0;
        snake.pop();
      }
    }

    // Draw
    function draw(){
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      // grid
      ctx.strokeStyle = '#222'; ctx.lineWidth = 1;
      for(let i=0; i<=tileCount; i++){
        const pos = i * gridSize;
        ctx.beginPath();
        ctx.moveTo(pos, 0);              ctx.lineTo(pos, canvas.height);
        ctx.moveTo(0, pos);              ctx.lineTo(canvas.width, pos);
        ctx.stroke();
      }
      // apples
      apples.forEach(a=>{
        const cx = a.x*gridSize + gridSize/2;
        const cy = a.y*gridSize + gridSize/2;
        ctx.save();
        ctx.translate(cx, cy);
        ctx.shadowColor = '#f44'; ctx.shadowBlur = 18;
        ctx.fillStyle   = colorGradient(false);
        ctx.beginPath();
        ctx.ellipse(0,0, gridSize*0.4, gridSize*0.55, Math.PI/6, 0, 2*Math.PI);
        ctx.fill();
        ctx.fillStyle   = '#282';
        ctx.beginPath();
        ctx.ellipse(0, -gridSize*0.3, gridSize*0.15, gridSize*0.3, Math.PI/4, 0, 2*Math.PI);
        ctx.fill();
        ctx.restore();
      });
      // snake
      snake.forEach((s,i)=>{
        const px = s.x*gridSize, py = s.y*gridSize;
        ctx.save();
        ctx.fillStyle   = colorGradient(true);
        ctx.shadowColor = '#0ff';
        ctx.shadowBlur  = (i===0 ? 25 : 18);
        roundedRect(px+2, py+2, gridSize-4, gridSize-4, 8);
        ctx.restore();
      });
      // effects
      effects.particles.forEach(p=>{
        ctx.fillStyle = p.color;
        ctx.beginPath();
        ctx.arc(p.x, p.y, p.size, 0, 2*Math.PI);
        ctx.fill();
      });
      effects.score.forEach(e=>{
        ctx.fillStyle   = `rgba(0,255,150,${e.alpha})`;
        ctx.font        = `${e.size}px Arial`;
        ctx.textAlign   = 'center';
        ctx.shadowColor = '#0f9'; ctx.shadowBlur = 10;
        ctx.fillText(e.text, e.x, e.y);
      });
    }

    // Input
    let touchStart = null, minSwipe = 30;
    canvas.addEventListener('touchstart', e=>{
      if(e.touches.length===1) touchStart = { x: e.touches[0].clientX, y: e.touches[0].clientY };
    }, { passive: true });
    canvas.addEventListener('touchmove', e=>{
      if(!touchStart) return;
      const dx = e.touches[0].clientX - touchStart.x;
      const dy = e.touches[0].clientY - touchStart.y;
      if(Math.abs(dx)>minSwipe || Math.abs(dy)>minSwipe){
        const dir = Math.abs(dx)>Math.abs(dy) ? (dx>0?'right':'left') : (dy>0?'down':'up');
        changeDir(dir);
        touchStart = null;
      }
    }, { passive: true });
    canvas.addEventListener('touchend', ()=> { touchStart = null; });

    btns.forEach(b => b.addEventListener('click', () => changeDir(b.dataset.dir)));
    function changeDir(dir){
      const map = { up:{x:0,y:-1}, down:{x:0,y:1}, left:{x:-1,y:0}, right:{x:1,y:0} };
      const nv = map[dir];
      if(nv && (nv.x !== -velocity.x || nv.y !== -velocity.y)) nextVel = nv;
    }

    // Loop
    let lastTime = 0;
    function loop(ts=0){
      if(ts - lastTime > 1000 / fps){
        lastTime = ts;
        update(); updateEffects(); draw();
      }
      requestAnimationFrame(loop);
    }

    // Start
    spawnApples();
    requestAnimationFrame(loop);
  })();
  </script>
</body>
</html>