Implement 3D Snake with HTML + JS + CSS. The entire code should be written in a single HTML block with embedded JS and CSS. Don't use any remote assets. After opening the html, user will need to hit space to start / restart the game, the snake will randomly go in one direction at the start and use "wasd" to control the direction of the snake. The game will take place on the surface of a cube the snake will automatically curl around the sides of the cube when it reaches the edge and the cube will always be showing the face with the head of the snake on it. the cube will be see through / transparent so that we can always see what side the apples are on no matter where they spawn. Make it pretty and the background large.
current issues: snake is trapped on a single face it can move around freely on that face which is nice but when it gets to some of the edges it just stops as if running against a wall. sometimes it can travel to an upper face but once it gets there it just freezes in place being uncontrollable. everything else works great.
<!DOCTYPE html>
<html>
<head>
<title>3D Snake Game on Cube Surface</title>
<style>
body {
margin: 0;
overflow: hidden;
background: radial-gradient(circle at center, #000428, #004e92);
}

#game-container {
perspective: 1000px;
width: 100vw;
height: 100vh;
position: relative;
}

#cube {
position: absolute;
top: 50%;
left: 50%;
width: 300px;
height: 300px;
transform-style: preserve-3d;
transform: translate(-50%, -50%) rotateX(-20deg) rotateY(45deg);
transition: transform 0.5s ease-out;
}

.face {
position: absolute;
width: 300px;
height: 300px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
box-sizing: border-box;
}

/* Position each face /
#face-0 { / Front /
transform: rotateY(0deg) translateZ(150px);
}
#face-1 { / Back /
transform: rotateY(180deg) translateZ(150px);
}
#face-2 { / Right /
transform: rotateY(90deg) translateZ(150px);
}
#face-3 { / Left /
transform: rotateY(-90deg) translateZ(150px);
}
#face-4 { / Top /
transform: rotateX(90deg) translateZ(150px);
}
#face-5 { / Bottom /
transform: rotateX(-90deg) translateZ(150px);
}

/ Snake and apple cells /
.cell {
position: absolute;
width: 30px; / Assuming grid size 10x10 /
height: 30px;
box-sizing: border-box;
}

.snake {
background: limegreen;
}

.apple {
background: red;
}

/ Additional styles */
#message {
position: absolute;
top: 20px;
width: 100%;
text-align: center;
color: white;
font-family: Arial, sans-serif;
font-size: 24px;
}

</style>
</head>
<body>
<div id="game-container">
<div id="cube">
<div class="face" id="face-0"></div>
<div class="face" id="face-1"></div>
<div class="face" id="face-2"></div>
<div class="face" id="face-3"></div>
<div class="face" id="face-4"></div>
<div class="face" id="face-5"></div>
</div>
<div id="message">Press SPACE to start the game</div>
</div>
<script>
(function() {
const gridSize = 10; // 10x10 grid on each face
const cellSize = 30; // Must match CSS
const faces = [0,1,2,3,4,5];
const cube = document.getElementById('cube');
const faceElements = faces.map(faceIndex => document.getElementById('face-' + faceIndex));

let snake = [];
let direction = null;
let apple = null;
let gameInterval = null;
let isGameOver = false;
let isGameStarted = false;
const messageElement = document.getElementById('message');

const faceAdjacency = {
0: { // Front
up: {face:4, x:'x', y: gridSize - 1 - 'x'},
down: {face:5, x:'x', y:'x'},
left: {face:3, x: gridSize - 1, y:'y'},
right: {face:2, x: 0, y:'y'}
},
1: { // Back
up: {face:4, x: gridSize - 1 - 'x', y: 0},
down: {face:5, x: gridSize - 1 - 'x', y: gridSize - 1},
left: {face:2, x: gridSize - 1, y: 'y'},
right: {face:3, x: 0, y: 'y'}
},
2: { // Right
up: {face:4, x: 'y', y: 'x'},
down: {face:5, x: gridSize - 1 - 'y', y: 'x'},
left: {face:0, x: gridSize - 1, y: 'y'},
right: {face:1, x: 0, y: 'y'}
},
3: { // Left
up: {face:4, x: gridSize - 1 - 'y', y: gridSize - 1 - 'x'},
down: {face:5, x: 'y', y: gridSize - 1 - 'x'},
left: {face:1, x: gridSize - 1, y: 'y'},
right: {face:0, x: 0, y: 'y'}
},
4: { // Top
up: {face:1, x: 'x', y: 0},
down: {face:0, x: 'x', y: 0},
left: {face:3, x:'x', y:0},
right: {face:2, x: 'x', y: 0}
},
5: { // Bottom
up: {face:0, x: 'x', y: gridSize - 1},
down: {face:1, x: 'x', y: gridSize -1},
left: {face:3, x:'x', y: gridSize - 1},
right: {face:2, x: 'x', y: gridSize -1}
}
};

function initGame() {
// Clear previous game
snake.forEach(segment => {
removeCell(segment);
});
if (apple) removeCell(apple);

snake = [];
direction = null;
isGameOver = false;
isGameStarted = false;
messageElement.textContent = "Press SPACE to start the game";

// Initialize snake starting position
const startingFace = Math.floor(Math.random() * 6);
const startingX = Math.floor(gridSize / 2);
const startingY = Math.floor(gridSize / 2);
direction = getRandomDirection();

snake.push({face: startingFace, x: startingX, y: startingY});

drawCell(snake[0], 'snake');

spawnApple();
}

function getRandomDirection() {
const dirs = ['up', 'down', 'left', 'right'];
return dirs[Math.floor(Math.random() * dirs.length)];
}

function spawnApple() {
let validCell = false;
while (!validCell) {
const face = Math.floor(Math.random() * 6);
const x = Math.floor(Math.random() * gridSize);
const y = Math.floor(Math.random() * gridSize);
// Check if apple is on snake
if (!snake.some(segment => segment.face === face && segment.x === x && segment.y === y)) {
apple = {face, x, y};
validCell = true;
}
}
drawCell(apple, 'apple');
}

function drawCell(cell, className) {
const cellDiv = document.createElement('div');
cellDiv.classList.add('cell', className);
cellDiv.style.left = (cell.x * cellSize) + 'px';
cellDiv.style.top = (cell.y * cellSize) + 'px';
faceElements[cell.face].appendChild(cellDiv);
cell.element = cellDiv;
}

function removeCell(cell) {
if (cell.element && cell.element.parentNode) {
cell.element.parentNode.removeChild(cell.element);
cell.element = null;
}
}

function gameLoop() {
if (isGameOver) return;

// Calculate new head position
const head = snake[0];
let newHead = {face: head.face, x: head.x, y: head.y};

switch (direction) {
case 'up':
newHead.y -=1;
break;
case 'down':
newHead.y +=1;
break;
case 'left':
newHead.x -=1;
break;
case 'right':
newHead.x +=1;
break;
default:
return; // No movement if direction is null
}

if (newHead.x < 0 || newHead.x >= gridSize || newHead.y < 0 || newHead.y >= gridSize) {
// Need to move to adjacent face
const adjacency = faceAdjacency[newHead.face][direction];
const adjFace = adjacency.face;
let adjX = adjacency.x;
let adjY = adjacency.y;

adjX = (typeof adjX === 'number') ? adjX : eval(adjX.replace(/'x'/g, head.x).replace(/'y'/g, head.y));
adjY = (typeof adjY === 'number') ? adjY : eval(adjY.replace(/'x'/g, head.x).replace(/'y'/g, head.y));

newHead.face = adjFace;
newHead.x = adjX;
newHead.y = adjY;
}

// Check collision with self
if (snake.some(segment => segment.face === newHead.face && segment.x === newHead.x && segment.y === newHead.y)) {
gameOver();
return;
}

// Insert new head
snake.unshift(newHead);
drawCell(newHead, 'snake');

// Check if apple is eaten
if (apple.face === newHead.face && apple.x === newHead.x && apple.y === newHead.y) {
removeCell(apple);
spawnApple();
} else {
// Remove tail
const tail = snake.pop();
removeCell(tail);
}

// Rotate cube to show the face with snake's head
rotateCubeToFace(newHead.face);
}

function rotateCubeToFace(face){
// Reset transform
cube.style.transform = translate(-50%, -50%);

// Apply rotation
let rotationX = 0;
let rotationY = 0;
switch(face){
case 0: // Front
rotationY = 0;
break;
case 1: // Back
rotationY = 180;
break;
case 2: // Right
rotationY = -90;
break;
case 3: // Left
rotationY = 90;
break;
case 4: // Top
rotationX = -90;
break;
case 5: // Bottom
rotationX = 90;
break;
}
cube.style.transform += rotateX(${rotationX}deg) rotateY(${rotationY}deg);
}

function gameOver() {
isGameOver = true;
clearInterval(gameInterval);
messageElement.textContent = "Game Over! Press SPACE to restart";
}

// Handle key input
document.addEventListener('keydown', function(e) {
if (isGameOver && e.code === 'Space') {
initGame();
isGameStarted = true;
messageElement.textContent = "";
gameInterval = setInterval(gameLoop, 300);
}

if (!isGameStarted && e.code === 'Space') {
isGameStarted = true;
messageElement.textContent = "";
gameInterval = setInterval(gameLoop, 300);
}

if (e.key === 'w' || e.key === 'ArrowUp') {
if (direction !== 'down') direction = 'up';
} else if (e.key === 's' || e.key === 'ArrowDown') {
if (direction !== 'up') direction = 'down';
} else if (e.key === 'a' || e.key === 'ArrowLeft') {
if (direction !== 'right') direction = 'left';
} else if (e.key === 'd' || e.key === 'ArrowRight') {
if (direction !== 'left') direction = 'right';
}
});

// Start the game
initGame();

})();
</script>
</body>
</html>