mirror of
https://github.com/ghndrx/k8s-game-2048.git
synced 2026-02-10 14:54:59 +00:00
Initial commit: 2048 game with Knative and Kourier deployment
- Complete 2048 game implementation with responsive design - Knative Serving manifests for dev/staging/prod environments - Scale-to-zero configuration with environment-specific settings - Custom domain mapping for wa.darknex.us subdomains - GitHub Actions workflows for CI/CD - Docker container with nginx and health checks - Setup scripts for Knative and Kourier installation - GHCR integration for container registry
This commit is contained in:
82
src/index.html
Normal file
82
src/index.html
Normal file
@@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>2048 Game - Knative Edition</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="icon" type="image/png" href="favicon.png">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>2048</h1>
|
||||
<div class="environment-badge" id="env-badge"></div>
|
||||
<div class="scores-container">
|
||||
<div class="score-container">
|
||||
<div class="score-title">SCORE</div>
|
||||
<div class="score" id="score">0</div>
|
||||
</div>
|
||||
<div class="score-container">
|
||||
<div class="score-title">BEST</div>
|
||||
<div class="score" id="best">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="above-game">
|
||||
<p class="game-intro">
|
||||
<strong>HOW TO PLAY:</strong> Use your <strong>arrow keys</strong> to move the tiles.
|
||||
When two tiles with the same number touch, they <strong>merge into one!</strong>
|
||||
</p>
|
||||
<button class="restart-button" id="restart-button">New Game</button>
|
||||
</div>
|
||||
|
||||
<div class="game-container">
|
||||
<div class="game-message" id="game-message">
|
||||
<p></p>
|
||||
<div class="lower">
|
||||
<button class="keep-playing-button" id="keep-playing-button">Keep going</button>
|
||||
<button class="retry-button" id="retry-button">Try again</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-container">
|
||||
<div class="grid-row">
|
||||
<div class="grid-cell"></div>
|
||||
<div class="grid-cell"></div>
|
||||
<div class="grid-cell"></div>
|
||||
<div class="grid-cell"></div>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="grid-cell"></div>
|
||||
<div class="grid-cell"></div>
|
||||
<div class="grid-cell"></div>
|
||||
<div class="grid-cell"></div>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="grid-cell"></div>
|
||||
<div class="grid-cell"></div>
|
||||
<div class="grid-cell"></div>
|
||||
<div class="grid-cell"></div>
|
||||
</div>
|
||||
<div class="grid-row">
|
||||
<div class="grid-cell"></div>
|
||||
<div class="grid-cell"></div>
|
||||
<div class="grid-cell"></div>
|
||||
<div class="grid-cell"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tile-container" id="tile-container"></div>
|
||||
</div>
|
||||
|
||||
<div class="game-explanation">
|
||||
<p><strong>Knative Edition:</strong> This game is deployed using Knative Serving with scale-to-zero capabilities on Kubernetes!</p>
|
||||
<p>Environment: <span id="environment">Production</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
348
src/script.js
Normal file
348
src/script.js
Normal file
@@ -0,0 +1,348 @@
|
||||
// 2048 Game JavaScript - Knative Edition
|
||||
|
||||
class Game2048 {
|
||||
constructor() {
|
||||
this.grid = [];
|
||||
this.score = 0;
|
||||
this.best = localStorage.getItem('best2048') || 0;
|
||||
this.gameWon = false;
|
||||
this.gameOver = false;
|
||||
this.keepPlaying = false;
|
||||
|
||||
this.init();
|
||||
this.setupEventListeners();
|
||||
this.setEnvironment();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.grid = [
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0]
|
||||
];
|
||||
|
||||
this.score = 0;
|
||||
this.gameWon = false;
|
||||
this.gameOver = false;
|
||||
this.keepPlaying = false;
|
||||
|
||||
this.updateScore();
|
||||
this.addRandomTile();
|
||||
this.addRandomTile();
|
||||
this.updateDisplay();
|
||||
}
|
||||
|
||||
setEnvironment() {
|
||||
const envElement = document.getElementById('environment');
|
||||
const envBadge = document.getElementById('env-badge');
|
||||
|
||||
// Try to detect environment from hostname
|
||||
const hostname = window.location.hostname;
|
||||
let environment = 'production';
|
||||
|
||||
if (hostname.includes('dev')) {
|
||||
environment = 'development';
|
||||
} else if (hostname.includes('staging')) {
|
||||
environment = 'staging';
|
||||
}
|
||||
|
||||
envElement.textContent = environment.charAt(0).toUpperCase() + environment.slice(1);
|
||||
envBadge.textContent = environment;
|
||||
envBadge.className = `environment-badge ${environment}`;
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
document.addEventListener('keydown', (e) => this.handleKeyPress(e));
|
||||
document.getElementById('restart-button').addEventListener('click', () => this.restart());
|
||||
document.getElementById('keep-playing-button').addEventListener('click', () => this.keepPlayingGame());
|
||||
document.getElementById('retry-button').addEventListener('click', () => this.restart());
|
||||
|
||||
// Touch/swipe support for mobile
|
||||
let startX, startY;
|
||||
|
||||
document.addEventListener('touchstart', (e) => {
|
||||
startX = e.touches[0].clientX;
|
||||
startY = e.touches[0].clientY;
|
||||
});
|
||||
|
||||
document.addEventListener('touchend', (e) => {
|
||||
if (!startX || !startY) return;
|
||||
|
||||
const endX = e.changedTouches[0].clientX;
|
||||
const endY = e.changedTouches[0].clientY;
|
||||
|
||||
const diffX = startX - endX;
|
||||
const diffY = startY - endY;
|
||||
|
||||
if (Math.abs(diffX) > Math.abs(diffY)) {
|
||||
if (diffX > 0) {
|
||||
this.move('left');
|
||||
} else {
|
||||
this.move('right');
|
||||
}
|
||||
} else {
|
||||
if (diffY > 0) {
|
||||
this.move('up');
|
||||
} else {
|
||||
this.move('down');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleKeyPress(e) {
|
||||
if (this.gameOver && !this.keepPlaying) return;
|
||||
|
||||
switch (e.code) {
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
this.move('up');
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
this.move('down');
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
e.preventDefault();
|
||||
this.move('left');
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
e.preventDefault();
|
||||
this.move('right');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
move(direction) {
|
||||
const previousGrid = this.grid.map(row => [...row]);
|
||||
let moved = false;
|
||||
|
||||
switch (direction) {
|
||||
case 'left':
|
||||
moved = this.moveLeft();
|
||||
break;
|
||||
case 'right':
|
||||
moved = this.moveRight();
|
||||
break;
|
||||
case 'up':
|
||||
moved = this.moveUp();
|
||||
break;
|
||||
case 'down':
|
||||
moved = this.moveDown();
|
||||
break;
|
||||
}
|
||||
|
||||
if (moved) {
|
||||
this.addRandomTile();
|
||||
this.updateDisplay();
|
||||
this.checkGameState();
|
||||
}
|
||||
}
|
||||
|
||||
moveLeft() {
|
||||
let moved = false;
|
||||
for (let row = 0; row < 4; row++) {
|
||||
const newRow = this.slideArray(this.grid[row]);
|
||||
if (!this.arraysEqual(this.grid[row], newRow)) {
|
||||
moved = true;
|
||||
this.grid[row] = newRow;
|
||||
}
|
||||
}
|
||||
return moved;
|
||||
}
|
||||
|
||||
moveRight() {
|
||||
let moved = false;
|
||||
for (let row = 0; row < 4; row++) {
|
||||
const reversed = [...this.grid[row]].reverse();
|
||||
const newRow = this.slideArray(reversed).reverse();
|
||||
if (!this.arraysEqual(this.grid[row], newRow)) {
|
||||
moved = true;
|
||||
this.grid[row] = newRow;
|
||||
}
|
||||
}
|
||||
return moved;
|
||||
}
|
||||
|
||||
moveUp() {
|
||||
let moved = false;
|
||||
for (let col = 0; col < 4; col++) {
|
||||
const column = [this.grid[0][col], this.grid[1][col], this.grid[2][col], this.grid[3][col]];
|
||||
const newColumn = this.slideArray(column);
|
||||
if (!this.arraysEqual(column, newColumn)) {
|
||||
moved = true;
|
||||
for (let row = 0; row < 4; row++) {
|
||||
this.grid[row][col] = newColumn[row];
|
||||
}
|
||||
}
|
||||
}
|
||||
return moved;
|
||||
}
|
||||
|
||||
moveDown() {
|
||||
let moved = false;
|
||||
for (let col = 0; col < 4; col++) {
|
||||
const column = [this.grid[0][col], this.grid[1][col], this.grid[2][col], this.grid[3][col]];
|
||||
const reversed = [...column].reverse();
|
||||
const newColumn = this.slideArray(reversed).reverse();
|
||||
if (!this.arraysEqual(column, newColumn)) {
|
||||
moved = true;
|
||||
for (let row = 0; row < 4; row++) {
|
||||
this.grid[row][col] = newColumn[row];
|
||||
}
|
||||
}
|
||||
}
|
||||
return moved;
|
||||
}
|
||||
|
||||
slideArray(arr) {
|
||||
const filtered = arr.filter(val => val !== 0);
|
||||
const missing = 4 - filtered.length;
|
||||
const zeros = Array(missing).fill(0);
|
||||
const newArray = filtered.concat(zeros);
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (newArray[i] !== 0 && newArray[i] === newArray[i + 1]) {
|
||||
newArray[i] *= 2;
|
||||
newArray[i + 1] = 0;
|
||||
this.score += newArray[i];
|
||||
}
|
||||
}
|
||||
|
||||
const filtered2 = newArray.filter(val => val !== 0);
|
||||
const missing2 = 4 - filtered2.length;
|
||||
const zeros2 = Array(missing2).fill(0);
|
||||
return filtered2.concat(zeros2);
|
||||
}
|
||||
|
||||
arraysEqual(a, b) {
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
}
|
||||
|
||||
addRandomTile() {
|
||||
const emptyCells = [];
|
||||
for (let row = 0; row < 4; row++) {
|
||||
for (let col = 0; col < 4; col++) {
|
||||
if (this.grid[row][col] === 0) {
|
||||
emptyCells.push({row, col});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (emptyCells.length > 0) {
|
||||
const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
|
||||
this.grid[randomCell.row][randomCell.col] = Math.random() < 0.9 ? 2 : 4;
|
||||
}
|
||||
}
|
||||
|
||||
updateDisplay() {
|
||||
const container = document.getElementById('tile-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
for (let row = 0; row < 4; row++) {
|
||||
for (let col = 0; col < 4; col++) {
|
||||
if (this.grid[row][col] !== 0) {
|
||||
const tile = document.createElement('div');
|
||||
tile.className = `tile tile-${this.grid[row][col]}`;
|
||||
tile.textContent = this.grid[row][col];
|
||||
tile.style.left = `${col * 121.25}px`;
|
||||
tile.style.top = `${row * 121.25}px`;
|
||||
|
||||
if (this.grid[row][col] > 2048) {
|
||||
tile.className = 'tile tile-super';
|
||||
}
|
||||
|
||||
container.appendChild(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateScore() {
|
||||
document.getElementById('score').textContent = this.score;
|
||||
|
||||
if (this.score > this.best) {
|
||||
this.best = this.score;
|
||||
localStorage.setItem('best2048', this.best);
|
||||
}
|
||||
|
||||
document.getElementById('best').textContent = this.best;
|
||||
}
|
||||
|
||||
checkGameState() {
|
||||
this.updateScore();
|
||||
|
||||
// Check for 2048 tile (game won)
|
||||
if (!this.gameWon && !this.keepPlaying) {
|
||||
for (let row = 0; row < 4; row++) {
|
||||
for (let col = 0; col < 4; col++) {
|
||||
if (this.grid[row][col] === 2048) {
|
||||
this.gameWon = true;
|
||||
this.showMessage('You Win!', 'game-won');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for game over
|
||||
if (this.isGameOver()) {
|
||||
this.gameOver = true;
|
||||
this.showMessage('Game Over!', 'game-over');
|
||||
}
|
||||
}
|
||||
|
||||
isGameOver() {
|
||||
// Check for empty cells
|
||||
for (let row = 0; row < 4; row++) {
|
||||
for (let col = 0; col < 4; col++) {
|
||||
if (this.grid[row][col] === 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for possible merges
|
||||
for (let row = 0; row < 4; row++) {
|
||||
for (let col = 0; col < 4; col++) {
|
||||
const current = this.grid[row][col];
|
||||
if (
|
||||
(row < 3 && current === this.grid[row + 1][col]) ||
|
||||
(col < 3 && current === this.grid[row][col + 1])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
showMessage(text, className) {
|
||||
const messageElement = document.getElementById('game-message');
|
||||
messageElement.querySelector('p').textContent = text;
|
||||
messageElement.className = `game-message ${className}`;
|
||||
messageElement.style.display = 'block';
|
||||
}
|
||||
|
||||
hideMessage() {
|
||||
const messageElement = document.getElementById('game-message');
|
||||
messageElement.style.display = 'none';
|
||||
}
|
||||
|
||||
restart() {
|
||||
this.hideMessage();
|
||||
this.init();
|
||||
}
|
||||
|
||||
keepPlayingGame() {
|
||||
this.hideMessage();
|
||||
this.keepPlaying = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the game when the page loads
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new Game2048();
|
||||
});
|
||||
382
src/style.css
Normal file
382
src/style.css
Normal file
@@ -0,0 +1,382 @@
|
||||
/* 2048 Game CSS - Knative Edition */
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: #faf8ef;
|
||||
color: #776e65;
|
||||
font-family: "Clear Sans", "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 80px 0;
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h1.title {
|
||||
font-size: 80px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
color: #776e65;
|
||||
font-size: 80px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.environment-badge {
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
color: white;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.environment-badge.development {
|
||||
background: #ff6b6b;
|
||||
}
|
||||
|
||||
.environment-badge.staging {
|
||||
background: #ffa726;
|
||||
}
|
||||
|
||||
.environment-badge.production {
|
||||
background: #66bb6a;
|
||||
}
|
||||
|
||||
.scores-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.score-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
background: #bbada0;
|
||||
padding: 10px 20px;
|
||||
font-size: 25px;
|
||||
height: 60px;
|
||||
line-height: 47px;
|
||||
font-weight: bold;
|
||||
border-radius: 3px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.score-title {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 10px;
|
||||
left: 0;
|
||||
text-transform: uppercase;
|
||||
font-size: 13px;
|
||||
line-height: 13px;
|
||||
text-align: center;
|
||||
color: #eee4da;
|
||||
}
|
||||
|
||||
.score {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.above-game {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.game-intro {
|
||||
line-height: 1.65;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.restart-button {
|
||||
display: inline-block;
|
||||
background: #8f7a66;
|
||||
border-radius: 3px;
|
||||
padding: 0 20px;
|
||||
text-decoration: none;
|
||||
color: #f9f6f2;
|
||||
height: 40px;
|
||||
line-height: 42px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.restart-button:hover {
|
||||
background: #9f8a76;
|
||||
}
|
||||
|
||||
.game-container {
|
||||
position: relative;
|
||||
padding: 15px;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
background: #bbada0;
|
||||
border-radius: 10px;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.game-message {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: rgba(255, 255, 255, 0.73);
|
||||
z-index: 100;
|
||||
text-align: center;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.game-message p {
|
||||
font-size: 60px;
|
||||
font-weight: bold;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
margin-top: 150px;
|
||||
}
|
||||
|
||||
.game-message .lower {
|
||||
display: block;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.game-message a {
|
||||
display: inline-block;
|
||||
background: #8f7a66;
|
||||
border-radius: 3px;
|
||||
padding: 0 20px;
|
||||
text-decoration: none;
|
||||
color: #f9f6f2;
|
||||
height: 40px;
|
||||
line-height: 42px;
|
||||
margin-left: 9px;
|
||||
}
|
||||
|
||||
.game-won {
|
||||
background: rgba(237, 194, 46, 0.5);
|
||||
color: #f9f6f2;
|
||||
}
|
||||
|
||||
.game-won .game-message p {
|
||||
color: #f9f6f2;
|
||||
}
|
||||
|
||||
.game-over {
|
||||
background: rgba(238, 228, 218, 0.73);
|
||||
color: #776e65;
|
||||
}
|
||||
|
||||
.game-over .game-message p {
|
||||
color: #776e65;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.grid-row {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.grid-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.grid-cell {
|
||||
width: 106.25px;
|
||||
height: 106.25px;
|
||||
background: rgba(238, 228, 218, 0.35);
|
||||
border-radius: 6px;
|
||||
margin-right: 15px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.grid-cell:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.tile-container {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.tile {
|
||||
width: 106.25px;
|
||||
height: 106.25px;
|
||||
background: #eee4da;
|
||||
color: #776e65;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 106.25px;
|
||||
font-size: 55px;
|
||||
position: absolute;
|
||||
transition: 0.15s ease-in-out;
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.tile-2 { background: #eee4da; color: #776e65; }
|
||||
.tile-4 { background: #ede0c8; color: #776e65; }
|
||||
.tile-8 { color: #f9f6f2; background: #f2b179; }
|
||||
.tile-16 { color: #f9f6f2; background: #f59563; }
|
||||
.tile-32 { color: #f9f6f2; background: #f67c5f; }
|
||||
.tile-64 { color: #f9f6f2; background: #f65e3b; }
|
||||
.tile-128 { color: #f9f6f2; background: #edcf72; font-size: 45px; }
|
||||
.tile-256 { color: #f9f6f2; background: #edcc61; font-size: 45px; }
|
||||
.tile-512 { color: #f9f6f2; background: #edc850; font-size: 45px; }
|
||||
.tile-1024 { color: #f9f6f2; background: #edc53f; font-size: 35px; }
|
||||
.tile-2048 { color: #f9f6f2; background: #edc22e; font-size: 35px; }
|
||||
|
||||
.tile-super { color: #f9f6f2; background: #3c3a32; font-size: 30px; }
|
||||
|
||||
.tile-new {
|
||||
animation: appear 200ms ease-in-out;
|
||||
animation-fill-mode: backwards;
|
||||
}
|
||||
|
||||
.tile-merged {
|
||||
z-index: 20;
|
||||
animation: pop 200ms ease-in-out;
|
||||
animation-fill-mode: backwards;
|
||||
}
|
||||
|
||||
@keyframes appear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pop {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.game-explanation {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
color: #776e65;
|
||||
}
|
||||
|
||||
.game-explanation p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.keep-playing-button, .retry-button {
|
||||
display: inline-block;
|
||||
background: #8f7a66;
|
||||
border-radius: 3px;
|
||||
padding: 0 20px;
|
||||
text-decoration: none;
|
||||
color: #f9f6f2;
|
||||
height: 40px;
|
||||
line-height: 42px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.keep-playing-button:hover, .retry-button:hover {
|
||||
background: #9f8a76;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media screen and (max-width: 520px) {
|
||||
.container {
|
||||
width: 280px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
.scores-container {
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.above-game {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.game-container {
|
||||
width: 280px;
|
||||
height: 280px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.grid-cell {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tile {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
font-size: 35px;
|
||||
}
|
||||
|
||||
.tile-128, .tile-256, .tile-512 {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.tile-1024, .tile-2048 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.tile-super {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user