Skip to content

Instantly share code, notes, and snippets.

@tolepcoy
Created July 19, 2025 17:30
Show Gist options
  • Save tolepcoy/7d48884e78179f029ea528114d4ade9e to your computer and use it in GitHub Desktop.
Save tolepcoy/7d48884e78179f029ea528114d4ade9e to your computer and use it in GitHub Desktop.
Zen Sliding Picture Game🌿
<!-- Energy Display -->
<div class="points-display">
<span class="points-icon">🌿</span>
<span class="points-value" id="points-value">0</span>
</div>
<!-- Quick Power-ups Panel -->
<div class="quick-powerups" id="quick-powerups">
<!-- Owned power-ups will be generated here -->
</div>
<!-- Garden Wisdom -->
<div class="debug-toggle" id="debug-toggle">
<span>🪷</span>
<span>Wisdom</span>
</div>
<!-- Debug Info -->
<div class="debug-info" id="debug-info">
<div>Board State: <span id="debug-board"></span></div>
<div>Empty Position: <span id="debug-empty"></span></div>
<div>Is Solvable: <span id="debug-solvable"></span></div>
</div>
<div class="game-container">
<div class="game-header">
<h1 class="game-title">🌸 Floating Garden</h1>
<p class="game-subtitle">"Simplicity is the ultimate sophistication"</p>
<div class="game-stats">
<div class="stat-item">
<span class="stat-icon">🍃</span>
<span class="stat-label">Flows</span>
<span class="stat-value" id="move-count">0</span>
</div>
<div class="stat-item">
<span class="stat-icon">⏳</span>
<span class="stat-label">Breeze</span>
<span class="stat-value" id="timer">00:00</span>
</div>
</div>
</div>
<div class="puzzle-board" id="puzzle-board">
<!-- Tiles will be generated here -->
</div>
<div class="game-controls">
<button class="control-btn primary" id="new-game-btn">
<span class="btn-icon">🌱</span>
<span>New Garden</span>
</button>
<button class="control-btn secondary" id="shuffle-btn">
<span class="btn-icon">🌊</span>
<span>Ripple</span>
</button>
<button class="control-btn secondary" id="change-image-btn">
<span class="btn-icon">🦋</span>
<span>New Scene</span>
</button>
<button class="control-btn secondary" id="hint-btn">
<span class="btn-icon">💫</span>
<span>Gentle Glow</span>
</button>
<button class="control-btn secondary" id="rules-btn">
<span class="btn-icon">📜</span>
<span>Garden Way</span>
</button>
</div>
<div class="difficulty-selector">
<label class="difficulty-label">Difficulty:</label>
<div class="difficulty-options">
<button class="difficulty-btn" data-size="3">3×3</button>
<button class="difficulty-btn active" data-size="4">4×4</button>
<button class="difficulty-btn" data-size="5">5×5</button>
</div>
</div>
<!-- Garden Elements -->
<div class="shop-container">
<h3 class="shop-title">🌺 Garden Elements</h3>
<div class="shop-items" id="shop-items">
<!-- Garden elements will be generated here -->
</div>
</div>
</div>
<div class="win-modal" id="win-modal">
<div class="modal-content">
<div class="win-icon">🌸</div>
<h2 class="win-title">Garden in Harmony!</h2>
<div class="win-stats">
<div class="win-stat">
<span class="win-stat-value" id="final-moves">0</span>
<span class="win-stat-label">Flows</span>
</div>
<div class="win-stat">
<span class="win-stat-value" id="final-time">00:00</span>
<span class="win-stat-label">Breeze</span>
</div>
</div>
<div class="win-actions">
<button class="control-btn primary" id="play-again-btn">
<span class="btn-icon">🌱</span>
<span>Plant Again</span>
</button>
</div>
</div>
</div>
<!-- Notification -->
<div class="notification" id="notification"></div>
<!-- Rules Modal -->
<div class="rules-modal" id="rules-modal">
<div class="rules-content">
<div class="rules-header">
<h2 class="rules-title">🌿 Floating Garden - The Garden Way</h2>
<button class="rules-close" id="rules-close">✕</button>
</div>
<div class="rules-sections">
<div class="rules-nav">
<button class="rules-nav-btn active" data-section="basics">🌱 Essence</button>
<button class="rules-nav-btn" data-section="controls">🍃 Flow</button>
<button class="rules-nav-btn" data-section="powerups">🌺 Elements</button>
<button class="rules-nav-btn" data-section="scoring">🌿 Energy</button>
<button class="rules-nav-btn" data-section="tips">🪷 Wisdom</button>
</div>
<div class="rules-body">
<!-- Basics Section -->
<div class="rules-section active" id="section-basics">
<h3>🎯 Game Basics</h3>
<div class="rule-item">
<h4>🎮 Objective</h4>
<p>Arrange numbered tiles in order (1-15 for 4×4) by sliding them into the empty space. The
final arrangement should have numbers 1-15 in sequence with the empty space in the
bottom-right corner.</p>
</div>
<div class="rule-item">
<h4>🧩 How to Move Tiles</h4>
<ul>
<li><strong>Click:</strong> Click any tile adjacent to the empty space</li>
<li><strong>Keyboard:</strong> Use arrow keys to move tiles into the empty space</li>
<li><strong>Touch:</strong> Tap or swipe tiles on mobile devices</li>
</ul>
</div>
<div class="rule-item">
<h4>🎲 Difficulty Levels</h4>
<ul>
<li><strong>3×3:</strong> Beginner (8 tiles) - Perfect for learning</li>
<li><strong>4×4:</strong> Normal (15 tiles) - Standard challenge</li>
<li><strong>5×5:</strong> Expert (24 tiles) - Advanced puzzle</li>
</ul>
</div>
</div>
<!-- Controls Section -->
<div class="rules-section" id="section-controls">
<h3>🎮 Controls & Features</h3>
<div class="rule-item">
<h4>🔄 Game Controls</h4>
<ul>
<li><strong>New Game:</strong> Start fresh with a new shuffled puzzle</li>
<li><strong>Shuffle:</strong> Re-shuffle current puzzle (+5 moves penalty)</li>
<li><strong>New Image:</strong> Get a random scenic background image</li>
<li><strong>Hint:</strong> Highlights tiles that can be moved</li>
<li><strong>Rules:</strong> Opens this help guide</li>
</ul>
</div>
<div class="rule-item">
<h4>🎯 Visual Helpers</h4>
<ul>
<li><strong>Movable Tiles:</strong> Glow with special border when adjacent to empty
space</li>
<li><strong>Numbers:</strong> Displayed over beautiful background images</li>
<li><strong>Particle Effects:</strong> Colorful particles appear on successful moves
</li>
<li><strong>Animations:</strong> Smooth sliding transitions for better feedback</li>
</ul>
</div>
<div class="rule-item">
<h4>🐛 Debug Mode</h4>
<p>Click the debug button (🐛) in the top-left to enable developer mode. This shows board
state information and gives bonus points for testing.</p>
</div>
</div>
<!-- Power-ups Section -->
<div class="rules-section" id="section-powerups">
<h3>⚡ Power-Up Shop</h3>
<div class="rule-item">
<h4>⚡ Lightning Bolt</h4>
<ul>
<li><strong>Cost:</strong> 50 points</li>
<li><strong>Effect:</strong> Move any tile to any empty position instantly</li>
<li><strong>Penalty:</strong> +3 moves to your count</li>
<li><strong>How to use:</strong> Buy → Click "Use" → Select tile → Click empty space
</li>
</ul>
</div>
<div class="rule-item">
<h4>🛒 Shop System</h4>
<ul>
<li><strong>Purchase:</strong> Buy power-ups with earned points</li>
<li><strong>Ownership:</strong> Keep purchased items permanently</li>
<li><strong>Usage:</strong> Use owned power-ups as many times as desired</li>
<li><strong>Visual Feedback:</strong> Lightning mode changes cursor and highlights tiles
</li>
</ul>
</div>
</div>
<!-- Scoring Section -->
<div class="rules-section" id="section-scoring">
<h3>💎 Points & Scoring</h3>
<div class="rule-item">
<h4>💰 Earning Points</h4>
<ul>
<li><strong>Regular Moves:</strong> +1 point per move</li>
<li><strong>Power-up Usage:</strong> +5 points when using lightning bolt</li>
<li><strong>Completion Bonus:</strong> Large bonus based on speed and efficiency</li>
<li><strong>Time Bonus:</strong> Up to 300 points for fast completion</li>
<li><strong>Move Bonus:</strong> Up to 50 points for efficient solving</li>
</ul>
</div>
<div class="rule-item">
<h4>📊 Move Penalties</h4>
<ul>
<li><strong>Shuffle:</strong> +5 moves</li>
<li><strong>Lightning Bolt:</strong> +3 moves</li>
<li><strong>Debug Mode:</strong> +2 moves (original penalty)</li>
</ul>
</div>
<div class="rule-item">
<h4>🎯 Winning</h4>
<p>Complete the puzzle to earn bonus points and see your final stats. The faster and more
efficiently you solve it, the higher your bonus!</p>
</div>
</div>
<!-- Tips Section -->
<div class="rules-section" id="section-tips">
<h3>💡 Pro Tips & Strategies</h3>
<div class="rule-item">
<h4>🧠 Solving Strategy</h4>
<ul>
<li><strong>Start with corners:</strong> Position corner pieces first</li>
<li><strong>Work top-down:</strong> Complete the top row, then second row, etc.</li>
<li><strong>Use the hint button:</strong> When stuck, see which tiles can move</li>
<li><strong>Plan ahead:</strong> Think 2-3 moves in advance</li>
</ul>
</div>
<div class="rule-item">
<h4>⚡ Power-up Strategy</h4>
<ul>
<li><strong>Save for stuck moments:</strong> Use lightning bolt when truly stuck</li>
<li><strong>Final positions:</strong> Great for placing the last few tiles</li>
<li><strong>Point management:</strong> Earn points through regular play before spending
</li>
</ul>
</div>
<div class="rule-item">
<h4>🎮 Advanced Techniques</h4>
<ul>
<li><strong>Corner cycling:</strong> Move tiles around corners to position them</li>
<li><strong>Empty space management:</strong> Keep the empty space where you need it</li>
<li><strong>Pattern recognition:</strong> Learn common tile arrangements</li>
<li><strong>Keyboard shortcuts:</strong> Arrow keys are faster than clicking</li>
</ul>
</div>
<div class="rule-item">
<h4>🏆 Mastery Goals</h4>
<ul>
<li><strong>Speed:</strong> Complete 4×4 puzzle under 2 minutes</li>
<li><strong>Efficiency:</strong> Solve with minimal moves</li>
<li><strong>Progression:</strong> Master 3×3, then 4×4, then 5×5</li>
<li><strong>No power-ups:</strong> Challenge yourself without lightning bolt</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="./script.js"></script>
$(document).ready(function () {
// Game state
let gameState = {
size: 4,
board: [],
emptyPos: { row: 3, col: 3 },
moveCount: 0,
startTime: null,
timerInterval: null,
isPlaying: false,
isComplete: false,
points: 100, // Starting points
currentImage: null,
debugMode: false,
lightningMode: false,
powerUps: {
lightning: { owned: 0, price: 50, uses: 0 }
}
};
// Expanded image sources for puzzle tiles
const imageSources = [
"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Mountain landscape
"https://images.unsplash.com/photo-1469474968028-56623f02e42e?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Forest path
"https://images.unsplash.com/photo-1441974231531-c6227db76b6e?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Lake reflection
"https://images.unsplash.com/photo-1501436513145-30f24e19fcc4?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Ocean waves
"https://images.unsplash.com/photo-1558618666-fcd25c85cd64?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Desert dunes
"https://images.unsplash.com/photo-1507525428034-b723cf961d3e?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Beach sunset
"https://images.unsplash.com/photo-1519904981063-b0cf448d479e?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Flower field
"https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Foggy forest
"https://images.unsplash.com/photo-1439066615861-d1af74d74000?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Autumn leaves
"https://images.unsplash.com/photo-1501594907352-04cda38ebc29?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Tropical paradise
"https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Wildflower meadow
"https://images.unsplash.com/photo-1518837695005-2083093ee35b?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Mountain lake
"https://images.unsplash.com/photo-1464822759844-d150ad6cbe96?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Rocky coastline
"https://images.unsplash.com/photo-1418489098061-ce87b5dc3aee?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80", // Sunset canyon
"https://images.unsplash.com/photo-1493246507139-91e8fad9978e?ixlib=rb-4.0.3&auto=format&fit=crop&w=400&q=80" // Misty mountains
];
// Garden elements
const shopItems = {
lightning: {
id: "lightning",
name: "Lotus Flow",
icon: "🪷",
description: "Gracefully guide any tile to its destined place",
price: 50,
owned: 0,
maxUses: -1 // -1 = unlimited
}
};
// Initialize the game
function initGame() {
loadGameData();
setupEventListeners();
renderShop();
updatePointsDisplay();
renderQuickPowerups();
newGame();
// Initial background update
setTimeout(updateBackgroundImage, 100);
}
// Load game data from localStorage
function loadGameData() {
const saved = localStorage.getItem("squircle-slider-save");
if (saved) {
const data = JSON.parse(saved);
gameState.points = data.points || 100;
gameState.powerUps = data.powerUps || gameState.powerUps;
Object.keys(shopItems).forEach((key) => {
if (data.powerUps && data.powerUps[key]) {
shopItems[key].owned = data.powerUps[key].owned || 0;
}
});
}
}
// Save game data to localStorage
function saveGameData() {
const data = {
points: gameState.points,
powerUps: gameState.powerUps
};
localStorage.setItem("squircle-slider-save", JSON.stringify(data));
}
// Set up event listeners
function setupEventListeners() {
$("#new-game-btn").on("click", newGame);
$("#shuffle-btn").on("click", shuffleBoard);
$("#change-image-btn").on("click", changeImage);
$("#hint-btn").on("click", showHint);
$("#rules-btn").on("click", showRules);
$("#play-again-btn").on("click", () => {
$("#win-modal").removeClass("show");
newGame();
});
// Debug toggle
$("#debug-toggle").on("click", toggleDebugMode);
// Rules modal
$("#rules-close").on("click", hideRules);
$(document).on("click", ".rules-modal", function (e) {
if (e.target === this) {
hideRules();
}
});
// Rules navigation
$(".rules-nav-btn").on("click", function () {
const section = $(this).data("section");
showRulesSection(section);
});
// Difficulty selector
$(".difficulty-btn").on("click", function () {
const newSize = parseInt($(this).data("size"));
if (newSize !== gameState.size) {
$(".difficulty-btn").removeClass("active");
$(this).addClass("active");
gameState.size = newSize;
newGame();
}
});
// Tile click handler
$(document).on("click", ".puzzle-tile:not(.empty)", function () {
if (gameState.lightningMode) {
handleLightningClick($(this));
return;
}
const index = $(this).data("index");
const pos = indexToPosition(index);
if (canMoveTile(pos)) {
moveTile(pos);
}
});
// Empty space click for lightning mode
$(document).on("click", ".puzzle-tile.empty", function () {
if (gameState.lightningMode) {
handleLightningTarget($(this));
}
});
}
// Start a new game
function newGame() {
gameState.moveCount = 0;
gameState.startTime = Date.now();
gameState.isPlaying = true;
gameState.isComplete = false;
gameState.lightningMode = false;
gameState.emptyPos = { row: gameState.size - 1, col: gameState.size - 1 };
// Select random image
gameState.currentImage =
imageSources[Math.floor(Math.random() * imageSources.length)];
// Update body background to match puzzle image
updateBackgroundImage();
// Clear timer
if (gameState.timerInterval) {
clearInterval(gameState.timerInterval);
}
// Initialize solved board
gameState.board = [];
for (let i = 0; i < gameState.size * gameState.size - 1; i++) {
gameState.board.push(i + 1);
}
gameState.board.push(0); // 0 represents empty space
// Generate solvable puzzle
generateSolvablePuzzle();
// Update UI
updateMoveCount();
startTimer();
renderBoard();
updateDebugInfo();
// Exit lightning mode
$("body").removeClass("lightning-mode");
}
// Generate a solvable puzzle by making random valid moves
function generateSolvablePuzzle() {
const moves = 1000; // Number of random moves to make
for (let i = 0; i < moves; i++) {
const possibleMoves = getValidMoves();
if (possibleMoves.length > 0) {
const randomMove =
possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
swapWithEmpty(randomMove, false); // Don't count these as player moves
}
}
}
// Get all valid moves (tiles adjacent to empty space)
function getValidMoves() {
const moves = [];
const directions = [
{ row: -1, col: 0 }, // up
{ row: 1, col: 0 }, // down
{ row: 0, col: -1 }, // left
{ row: 0, col: 1 } // right
];
directions.forEach((dir) => {
const newRow = gameState.emptyPos.row + dir.row;
const newCol = gameState.emptyPos.col + dir.col;
if (
newRow >= 0 &&
newRow < gameState.size &&
newCol >= 0 &&
newCol < gameState.size
) {
moves.push({ row: newRow, col: newCol });
}
});
return moves;
}
// Check if a tile can move (is adjacent to empty space)
function canMoveTile(pos) {
const rowDiff = Math.abs(pos.row - gameState.emptyPos.row);
const colDiff = Math.abs(pos.col - gameState.emptyPos.col);
return (rowDiff === 1 && colDiff === 0) || (rowDiff === 0 && colDiff === 1);
}
// Move a tile to the empty space
function moveTile(pos) {
if (!gameState.isPlaying || gameState.isComplete) return;
const $tile = $(`.puzzle-tile[data-index="${positionToIndex(pos)}"]`);
// Add sliding animation
$tile.addClass("sliding");
setTimeout(() => {
$tile.removeClass("sliding");
swapWithEmpty(pos, true);
renderBoard();
updateDebugInfo();
// Check for win condition
if (checkWin()) {
gameComplete();
}
}, 150);
}
// Lightning bolt power-up functionality
let selectedTile = null;
function handleLightningClick($tile) {
if (selectedTile) {
selectedTile.removeClass("lightning-target");
}
selectedTile = $tile;
$tile.addClass("lightning-target");
showNotification("Click on an empty space to move the tile!", "success");
}
function handleLightningTarget($emptyTile) {
if (!selectedTile) {
showNotification("Select a tile first!", "error");
return;
}
const tileIndex = selectedTile.data("index");
const emptyIndex = $emptyTile.index();
// Swap the tiles
const tilePos = indexToPosition(tileIndex);
const emptyPos = indexToPosition(emptyIndex);
// Update board state
[gameState.board[tileIndex], gameState.board[emptyIndex]] = [
gameState.board[emptyIndex],
gameState.board[tileIndex]
];
// Update empty position
gameState.emptyPos = tilePos;
// Add move count
gameState.moveCount += 3; // Lightning costs extra moves
updateMoveCount();
// Exit lightning mode
gameState.lightningMode = false;
$("body").removeClass("lightning-mode");
selectedTile.removeClass("lightning-target");
selectedTile = null;
// Re-render board
renderBoard();
updateDebugInfo();
// Award points for using power-up
awardPoints(5);
showNotification("Lightning bolt used! ⚡", "success");
// Check for win
if (checkWin()) {
gameComplete();
}
}
// Swap tile with empty space
function swapWithEmpty(pos, countMove = true) {
const tileIndex = positionToIndex(pos);
const emptyIndex = positionToIndex(gameState.emptyPos);
// Swap values
[gameState.board[tileIndex], gameState.board[emptyIndex]] = [
gameState.board[emptyIndex],
gameState.board[tileIndex]
];
// Update empty position
gameState.emptyPos = { ...pos };
// Count move if it's a player move
if (countMove) {
gameState.moveCount++;
updateMoveCount();
// Award points for regular moves
awardPoints(1);
}
}
// Convert position to array index
function positionToIndex(pos) {
return pos.row * gameState.size + pos.col;
}
// Convert array index to position
function indexToPosition(index) {
return {
row: Math.floor(index / gameState.size),
col: index % gameState.size
};
}
// Render the puzzle board
function renderBoard() {
const $board = $("#puzzle-board");
$board.empty();
$board.css("grid-template-columns", `repeat(${gameState.size}, 1fr)`);
gameState.board.forEach((value, index) => {
const $tile = $("<div>").addClass("puzzle-tile");
if (value === 0) {
$tile.addClass("empty");
} else {
$tile.text(value);
$tile.attr("data-index", index);
// Add image background
if (gameState.currentImage) {
// Calculate the correct position for this tile number in the solved state
const correctRow = Math.floor((value - 1) / gameState.size);
const correctCol = (value - 1) % gameState.size;
// Calculate background position for proper image slicing
// This positions the background so each tile shows its correct portion
const bgPosX =
gameState.size === 1
? 0
: (correctCol * 100) / (gameState.size - 1);
const bgPosY =
gameState.size === 1
? 0
: (correctRow * 100) / (gameState.size - 1);
$tile.css({
"background-image": `url(${gameState.currentImage})`,
"background-position": `${bgPosX}% ${bgPosY}%`,
"background-size": `${gameState.size * 100}% ${
gameState.size * 100
}%`,
"background-repeat": "no-repeat"
});
$tile.addClass("has-image");
}
const pos = indexToPosition(index);
if (canMoveTile(pos)) {
$tile.addClass("movable");
}
}
$board.append($tile);
});
}
// Check if puzzle is solved
function checkWin() {
for (let i = 0; i < gameState.board.length - 1; i++) {
if (gameState.board[i] !== i + 1) {
return false;
}
}
return gameState.board[gameState.board.length - 1] === 0;
}
// Handle game completion
function gameComplete() {
gameState.isComplete = true;
gameState.isPlaying = false;
// Stop timer
if (gameState.timerInterval) {
clearInterval(gameState.timerInterval);
}
// Calculate bonus points
const finalTime = Math.floor((Date.now() - gameState.startTime) / 1000);
const timeBonus = Math.max(0, 300 - finalTime); // Bonus for speed
const moveBonus = Math.max(0, 50 - gameState.moveCount); // Bonus for efficiency
const totalBonus = timeBonus + moveBonus;
awardPoints(totalBonus);
// Update final stats
$("#final-moves").text(gameState.moveCount);
$("#final-time").text(formatTime(finalTime));
// Show win modal with delay for effect
setTimeout(() => {
$("#win-modal").addClass("show");
}, 500);
// Add celebration effect to tiles
$(".puzzle-tile:not(.empty)").each(function (index) {
setTimeout(() => {
$(this).css("transform", "scale(1.1)");
setTimeout(() => {
$(this).css("transform", "");
}, 400);
}, index * 100);
});
showNotification(
`Puzzle completed! +${totalBonus} bonus points! 🎉`,
"success"
);
}
// Points system
function awardPoints(amount) {
gameState.points += amount;
updatePointsDisplay();
saveGameData();
}
function spendPoints(amount) {
if (gameState.points >= amount) {
gameState.points -= amount;
updatePointsDisplay();
saveGameData();
return true;
}
return false;
}
function updatePointsDisplay() {
$("#points-value").text(gameState.points);
renderShop(); // Update shop affordability
renderQuickPowerups(); // Update quick power-ups panel
}
// Shop system
function renderShop() {
const $shopItems = $("#shop-items");
$shopItems.empty();
Object.values(shopItems).forEach((item) => {
const $item = $("<div>").addClass("shop-item");
// Add affordability classes
if (item.owned > 0) {
$item.addClass("owned");
} else if (gameState.points >= item.price) {
$item.addClass("affordable");
} else {
$item.addClass("unaffordable");
}
$item.html(`
<span class="shop-item-icon">${item.icon}</span>
<div class="shop-item-name">${item.name}</div>
<div class="shop-item-description">${item.description}</div>
<div class="shop-item-price">
<span>💎</span>
<span>${item.price}</span>
</div>
${
item.owned > 0
? `<div class="shop-item-uses">Owned: ${item.owned}</div>`
: ""
}
${
item.owned > 0
? `<button class="shop-use-btn" data-item="${item.id}">Use (3 moves)</button>`
: `<button class="shop-buy-btn" data-item="${item.id}" ${
gameState.points < item.price ? "disabled" : ""
}>Buy</button>`
}
`);
$shopItems.append($item);
});
}
// Shop event handlers
$(document).on("click", ".shop-buy-btn", function () {
const itemId = $(this).data("item");
const item = shopItems[itemId];
if (spendPoints(item.price)) {
item.owned++;
gameState.powerUps[itemId].owned = item.owned;
saveGameData();
renderShop();
renderQuickPowerups();
showNotification(`${item.name} purchased! 🛒`, "success");
} else {
showNotification("Not enough points! 💎", "error");
}
});
$(document).on("click", ".shop-use-btn", function () {
const itemId = $(this).data("item");
if (itemId === "lightning") {
if (!gameState.isPlaying) {
showNotification("Start a game first!", "error");
return;
}
gameState.lightningMode = true;
$("body").addClass("lightning-mode");
showNotification(
"Lotus flow awakened! Guide a tile to its destined place 🪷",
"success"
);
}
});
// Render quick power-ups panel
function renderQuickPowerups() {
const $quickPowerups = $("#quick-powerups");
$quickPowerups.empty();
// Get owned power-ups
const ownedPowerups = Object.values(shopItems).filter(
(item) => item.owned > 0
);
if (ownedPowerups.length === 0) {
$quickPowerups.removeClass("show");
return;
}
ownedPowerups.forEach((item) => {
const isActive = gameState.lightningMode && item.id === "lightning";
const $item = $(`
<div class="quick-powerup-item ${
isActive ? "active" : ""
}" data-powerup="${item.id}">
<span class="quick-powerup-icon">${item.icon}</span>
<span class="quick-powerup-name">${item.name}</span>
</div>
`);
$item.on("click", function () {
handleQuickPowerupClick(item.id);
});
$quickPowerups.append($item);
});
$quickPowerups.addClass("show");
}
// Handle quick power-up click
function handleQuickPowerupClick(powerupId) {
if (powerupId === "lightning") {
if (gameState.lightningMode) {
// Exit lightning mode
gameState.lightningMode = false;
$("body").removeClass("lightning-mode");
showNotification("Lightning mode deactivated", "success");
} else {
// Enter lightning mode
gameState.lightningMode = true;
$("body").addClass("lightning-mode");
showNotification(
"Lightning mode activated! Click a tile, then click where to move it.",
"success"
);
}
renderQuickPowerups(); // Update active state
}
}
// Debug mode
function toggleDebugMode() {
gameState.debugMode = !gameState.debugMode;
$("#debug-toggle").toggleClass("active");
$("#debug-info").toggleClass("show");
if (gameState.debugMode) {
// Give debug points
awardPoints(1000);
showNotification("Garden wisdom awakened! +1000 energy 🌿", "success");
}
updateDebugInfo();
}
function updateDebugInfo() {
if (!gameState.debugMode) return;
$("#debug-board").text(gameState.board.join(","));
$("#debug-empty").text(
`(${gameState.emptyPos.row}, ${gameState.emptyPos.col})`
);
$("#debug-solvable").text(checkWin() ? "SOLVED" : "UNSOLVED");
}
// Shuffle the board
function shuffleBoard() {
if (!gameState.isPlaying) return;
// Add penalty moves for shuffling
gameState.moveCount += 5;
updateMoveCount();
// Generate new puzzle
generateSolvablePuzzle();
renderBoard();
updateDebugInfo();
showNotification("Garden ripples spread... +5 flows", "error");
}
// Show hint (highlight movable tiles)
function showHint() {
$(".puzzle-tile.movable").each(function (index) {
setTimeout(() => {
$(this).css(
"background",
"linear-gradient(135deg, rgba(168, 85, 247, 0.4), rgba(228, 147, 179, 0.3))"
);
setTimeout(() => {
$(this).css("background", "");
}, 2000);
}, index * 200);
});
showNotification("Gentle glow reveals the path 💫", "success");
}
// Notification system
function showNotification(message, type = "success") {
const $notification = $("#notification");
$notification.removeClass("show success error").addClass(type);
$notification.text(message);
setTimeout(() => $notification.addClass("show"), 10);
setTimeout(() => $notification.removeClass("show"), 3000);
}
// Timer functions
function startTimer() {
if (gameState.timerInterval) {
clearInterval(gameState.timerInterval);
}
gameState.timerInterval = setInterval(() => {
if (gameState.isPlaying && !gameState.isComplete) {
const elapsed = Math.floor((Date.now() - gameState.startTime) / 1000);
$("#timer").text(formatTime(elapsed));
}
}, 1000);
}
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes
.toString()
.padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`;
}
// Update move count display
function updateMoveCount() {
$("#move-count").text(gameState.moveCount);
}
// Add some visual flair with particle effects
function createParticleEffect(x, y) {
const colors = ["#3b82f6", "#22c55e", "#06b6d4", "#ef4444"];
for (let i = 0; i < 8; i++) {
const particle = $("<div>").css({
position: "fixed",
left: x + "px",
top: y + "px",
width: "6px",
height: "6px",
background: colors[Math.floor(Math.random() * colors.length)],
borderRadius: "50%",
pointerEvents: "none",
zIndex: 9999
});
$("body").append(particle);
// Animate particle
particle.animate(
{
left: x + (Math.random() - 0.5) * 150,
top: y + (Math.random() - 0.5) * 150,
opacity: 0
},
1500,
function () {
$(this).remove();
}
);
}
}
// Particle effects disabled for faster gameplay
// $(document).on('click', '.puzzle-tile.movable', function (e) {
// const rect = this.getBoundingClientRect();
// const x = rect.left + rect.width / 2;
// const y = rect.top + rect.height / 2;
// createParticleEffect(x, y);
// });
// Initialize game when document is ready
initGame();
// Add keyboard support
$(document).on("keydown", function (e) {
if (!gameState.isPlaying || gameState.isComplete || gameState.lightningMode)
return;
let targetPos = { ...gameState.emptyPos };
switch (e.which) {
case 37: // left arrow - move tile from right
targetPos.col++;
break;
case 38: // up arrow - move tile from below
targetPos.row++;
break;
case 39: // right arrow - move tile from left
targetPos.col--;
break;
case 40: // down arrow - move tile from above
targetPos.row--;
break;
default:
return;
}
// Check if target position is valid
if (
targetPos.row >= 0 &&
targetPos.row < gameState.size &&
targetPos.col >= 0 &&
targetPos.col < gameState.size
) {
moveTile(targetPos);
}
e.preventDefault();
});
// Add touch/swipe support for mobile
let touchStartX = 0;
let touchStartY = 0;
$(document).on("touchstart", ".puzzle-tile:not(.empty)", function (e) {
const touch = e.originalEvent.touches[0];
touchStartX = touch.clientX;
touchStartY = touch.clientY;
});
$(document).on("touchend", ".puzzle-tile:not(.empty)", function (e) {
if (!touchStartX || !touchStartY) return;
const touch = e.originalEvent.changedTouches[0];
const touchEndX = touch.clientX;
const touchEndY = touch.clientY;
const diffX = touchStartX - touchEndX;
const diffY = touchStartY - touchEndY;
// Only trigger if swipe is significant
if (Math.abs(diffX) > 30 || Math.abs(diffY) > 30) {
const index = $(this).data("index");
const pos = indexToPosition(index);
if (canMoveTile(pos)) {
moveTile(pos);
}
}
touchStartX = 0;
touchStartY = 0;
});
// Update body background to match puzzle image
function updateBackgroundImage() {
if (gameState.currentImage) {
// Use the exact same image but higher resolution for background
const bgImage = gameState.currentImage.replace(
"w=400&q=80",
"w=1920&q=80"
);
document.documentElement.style.setProperty(
"--puzzle-background",
`url('${bgImage}')`
);
console.log("Updated background image:", bgImage); // Debug log
}
}
// Change to a random new image
function changeImage() {
// Filter out current image to ensure we get a different one
const availableImages = imageSources.filter(
(img) => img !== gameState.currentImage
);
gameState.currentImage =
availableImages[Math.floor(Math.random() * availableImages.length)];
// Re-render the board with new image
renderBoard();
updateBackgroundImage();
showNotification("A new natural scene has bloomed 🦋");
}
// Show rules modal
function showRules() {
$("#rules-modal").addClass("show");
showRulesSection("basics"); // Default to basics section
}
// Hide rules modal
function hideRules() {
$("#rules-modal").removeClass("show");
}
// Show specific rules section
function showRulesSection(sectionName) {
// Update navigation
$(".rules-nav-btn").removeClass("active");
$(`.rules-nav-btn[data-section="${sectionName}"]`).addClass("active");
// Update content
$(".rules-section").removeClass("active");
$(`#section-${sectionName}`).addClass("active");
}
// Enhanced keyboard shortcuts including rules modal
$(document).on("keydown", function (e) {
// Rules modal shortcuts
if ($("#rules-modal").hasClass("show")) {
if (e.key === "Escape") {
hideRules();
return;
}
// Number keys to switch sections
const sectionKeys = {
1: "basics",
2: "controls",
3: "powerups",
4: "scoring",
5: "tips"
};
if (sectionKeys[e.key]) {
showRulesSection(sectionKeys[e.key]);
return;
}
}
// Open rules with F1 or ?
if (e.key === "F1" || e.key === "?") {
showRules();
e.preventDefault();
return;
}
// Change image with 'I' key
if (e.key.toLowerCase() === "i" && !$("#rules-modal").hasClass("show")) {
changeImage();
return;
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Quicksand:wght@300;400;500;600;700&family=Noto+Sans+JP:wght@300;400;500&display=swap");
:root {
/* Light Theme - Professional Purple Marketplace */
--light-primary-rgb: 142, 122, 181;
--light-secondary-rgb: 183, 132, 183;
--light-tertiary-rgb: 228, 147, 179;
--light-accent-rgb: 168, 85, 247;
/* Dark Theme - Satirical Blue Truth */
--dark-primary-rgb: 59, 130, 246;
--dark-secondary-rgb: 34, 197, 94;
--dark-tertiary-rgb: 6, 182, 212;
--dark-accent-rgb: 239, 68, 68;
/* Default to Dark Theme */
--primary-rgb: var(--dark-primary-rgb);
--secondary-rgb: var(--dark-secondary-rgb);
--tertiary-rgb: var(--dark-tertiary-rgb);
--accent-rgb: var(--dark-accent-rgb);
/* Derived Colors */
--primary: rgb(var(--primary-rgb));
--secondary: rgb(var(--secondary-rgb));
--tertiary: rgb(var(--tertiary-rgb));
--accent: rgb(var(--accent-rgb));
/* Dark Theme Backgrounds */
--background: linear-gradient(
135deg,
rgba(15, 23, 42, 0.95) 0%,
rgba(30, 41, 59, 0.9) 50%,
rgba(51, 65, 85, 0.95) 100%
),
url("https://images.unsplash.com/photo-1557683304-673a23048d34?ixlib=rb-4.0.3&auto=format&fit=crop&w=2070&q=80");
--surface: rgba(255, 255, 255, 0.08);
--surface-hover: rgba(255, 255, 255, 0.12);
--surface-active: rgba(255, 255, 255, 0.16);
--text: #ffffff;
--text-light: rgba(255, 255, 255, 0.7);
--text-muted: rgba(255, 255, 255, 0.5);
/* Glass Morphism Effects */
--glass-background: rgba(255, 255, 255, 0.1);
--glass-border: rgba(255, 255, 255, 0.2);
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
/* Droplet Effects */
--droplet-shadow: 0 4px 20px rgba(142, 122, 181, 0.25),
0 2px 8px rgba(142, 122, 181, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.6);
--droplet-hover-shadow: 0 8px 32px rgba(142, 122, 181, 0.35),
0 4px 16px rgba(142, 122, 181, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.8);
/* Spacing */
--space-xs: 0.5rem;
--space-sm: 0.75rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--space-xl: 2rem;
--space-2xl: 3rem;
/* Typography - Zen inspired */
--font-family: "Noto Sans JP", "Inter", "Quicksand", system-ui, -apple-system,
BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--font-size-2xl: 1.5rem;
--font-size-3xl: 2rem;
/* Animation - Fast responsive gameplay */
--transition-fast: 0.15s cubic-bezier(0.25, 0.46, 0.45, 0.94);
--transition-normal: 0.15s cubic-bezier(0.25, 0.46, 0.45, 0.94);
--transition-slow: 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
--transition-bounce: 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
--float-duration: 3s;
--gentle-sway: 6s;
/* Squircle */
--squircle-factor: 0.08;
}
/* Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family);
font-weight: 400;
background: var(--background);
background-attachment: fixed;
background-size: cover;
background-position: center;
color: var(--text);
line-height: 1.6;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-md);
position: relative;
}
/* Dynamic blurry background that matches puzzle image */
body::before {
content: "";
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: var(
--puzzle-background,
url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?ixlib=rb-4.0.3&auto=format&fit=crop&w=1920&q=80")
);
background-size: cover;
background-position: center;
background-attachment: fixed;
filter: blur(8px) brightness(0.4) saturate(0.9);
z-index: -2;
opacity: 0.8;
transition: background-image 0.5s ease;
}
/* Overlay for better contrast */
body::after {
content: "";
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
135deg,
rgba(15, 23, 42, 0.65) 0%,
rgba(30, 41, 59, 0.55) 50%,
rgba(51, 65, 85, 0.65) 100%
);
z-index: -1;
}
/* Game Container */
.game-container {
max-width: 600px;
width: 100%;
background: var(--glass-background);
backdrop-filter: blur(20px);
border-radius: calc(var(--squircle-factor) * 200px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
padding: var(--space-xl);
position: relative;
}
/* Game Header */
.game-header {
text-align: center;
margin-bottom: var(--space-xl);
}
.game-title {
font-size: var(--font-size-3xl);
font-weight: 300;
font-family: "Quicksand", var(--font-family);
letter-spacing: 0.02em;
background: linear-gradient(135deg, var(--primary), var(--tertiary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: var(--space-sm);
animation: gentleFloat var(--float-duration) ease-in-out infinite;
}
.game-subtitle {
font-size: var(--font-size-sm);
font-weight: 300;
font-style: italic;
color: var(--text-light);
text-align: center;
margin-bottom: var(--space-lg);
opacity: 0.8;
animation: gentleFloat var(--gentle-sway) ease-in-out infinite reverse;
}
.game-stats {
display: flex;
justify-content: center;
gap: var(--space-lg);
flex-wrap: wrap;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
background: var(--surface);
padding: var(--space-md);
border-radius: calc(var(--squircle-factor) * 100px);
border: 1px solid var(--glass-border);
min-width: 100px;
backdrop-filter: blur(10px);
}
.stat-icon {
font-size: var(--font-size-xl);
margin-bottom: var(--space-xs);
}
.stat-label {
font-size: var(--font-size-xs);
color: var(--text-muted);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--space-xs);
}
.stat-value {
font-size: var(--font-size-lg);
font-weight: 700;
color: var(--primary);
}
/* Puzzle Board */
.puzzle-board {
display: grid;
gap: 8px;
margin: var(--space-xl) auto;
padding: var(--space-md);
background: var(--surface);
border-radius: calc(var(--squircle-factor) * 120px);
border: 1px solid var(--glass-border);
backdrop-filter: blur(10px);
max-width: 400px;
aspect-ratio: 1;
}
/* Puzzle Tiles - Glassmorphic Droplets */
.puzzle-tile {
background: var(--glass-background);
backdrop-filter: blur(15px);
border-radius: calc(var(--squircle-factor) * 80px);
border: 1px solid var(--glass-border);
box-shadow: var(--droplet-shadow);
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-2xl);
font-weight: 800;
color: var(--primary);
cursor: pointer;
transition: all var(--transition-normal);
position: relative;
overflow: hidden;
user-select: none;
background-size: cover;
background-position: center;
}
.puzzle-tile.has-image {
color: white;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
}
.puzzle-tile.has-image::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
border-radius: inherit;
}
.puzzle-tile::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
135deg,
rgba(255, 255, 255, 0.4) 0%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0.2) 100%
);
opacity: 0;
transition: opacity var(--transition-fast);
border-radius: inherit;
}
.puzzle-tile:hover {
transform: translateY(-2px) scale(1.02);
box-shadow: var(--droplet-hover-shadow);
border-color: rgba(var(--primary-rgb), 0.4);
}
.puzzle-tile:hover::before {
opacity: 1;
}
.puzzle-tile.movable {
background: linear-gradient(
135deg,
rgba(var(--primary-rgb), 0.15) 0%,
rgba(var(--secondary-rgb), 0.1) 100%
);
}
.puzzle-tile.movable:hover {
background: linear-gradient(
135deg,
rgba(var(--primary-rgb), 0.25) 0%,
rgba(var(--secondary-rgb), 0.2) 100%
);
transform: translateY(-4px) scale(1.05);
}
.puzzle-tile.sliding {
transform: scale(0.95);
box-shadow: 0 2px 8px rgba(142, 122, 181, 0.3);
}
.puzzle-tile.empty {
background: rgba(0, 0, 0, 0.1);
border: 2px dashed rgba(var(--primary-rgb), 0.3);
box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.1);
cursor: default;
}
.puzzle-tile.empty:hover {
transform: none;
box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.1);
border-color: rgba(var(--primary-rgb), 0.3);
}
/* Game Controls */
.game-controls {
display: flex;
justify-content: center;
gap: var(--space-md);
margin-bottom: var(--space-lg);
flex-wrap: wrap;
}
.control-btn {
display: flex;
align-items: center;
gap: var(--space-xs);
padding: var(--space-md) var(--space-lg);
border: none;
border-radius: calc(var(--squircle-factor) * 100px);
font-size: var(--font-size-base);
font-weight: 400;
cursor: pointer;
transition: all var(--transition-fast);
backdrop-filter: blur(10px);
position: relative;
overflow: hidden;
}
.control-btn.primary {
background: linear-gradient(135deg, var(--primary), var(--tertiary));
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.control-btn.secondary {
background: var(--surface);
color: var(--text);
border: 1px solid var(--glass-border);
}
.control-btn:hover {
transform: translateY(-2px);
box-shadow: var(--droplet-shadow);
}
.control-btn.primary:hover {
box-shadow: 0 8px 32px rgba(var(--primary-rgb), 0.4);
}
.btn-icon {
font-size: var(--font-size-lg);
}
/* Difficulty Selector */
.difficulty-selector {
text-align: center;
}
.difficulty-label {
display: block;
font-size: var(--font-size-sm);
color: var(--text-muted);
font-weight: 600;
margin-bottom: var(--space-md);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.difficulty-options {
display: flex;
justify-content: center;
gap: var(--space-xs);
background: var(--surface);
padding: var(--space-xs);
border-radius: calc(var(--squircle-factor) * 100px);
border: 1px solid var(--glass-border);
backdrop-filter: blur(10px);
}
.difficulty-btn {
padding: var(--space-sm) var(--space-md);
border: none;
background: transparent;
color: var(--text-muted);
font-size: var(--font-size-sm);
font-weight: 600;
border-radius: calc(var(--squircle-factor) * 80px);
cursor: pointer;
transition: all var(--transition-fast);
position: relative;
}
.difficulty-btn:hover,
.difficulty-btn.active {
background: var(--glass-background);
color: var(--primary);
box-shadow: 0 2px 8px rgba(var(--primary-rgb), 0.2);
}
/* Win Modal */
.win-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
visibility: hidden;
transition: all var(--transition-normal);
z-index: 1000;
}
.win-modal.show {
opacity: 1;
visibility: visible;
}
.modal-content {
background: var(--glass-background);
backdrop-filter: blur(20px);
border-radius: calc(var(--squircle-factor) * 150px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
padding: var(--space-2xl);
text-align: center;
max-width: 400px;
width: 90%;
transform: scale(0.8);
transition: transform var(--transition-bounce);
}
.win-modal.show .modal-content {
transform: scale(1);
}
.win-icon {
font-size: 4rem;
margin-bottom: var(--space-lg);
animation: bounce 2s ease-in-out infinite;
}
.win-title {
font-size: var(--font-size-2xl);
font-weight: 700;
background: linear-gradient(135deg, var(--primary), var(--tertiary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: var(--space-lg);
}
.win-stats {
display: flex;
justify-content: center;
gap: var(--space-lg);
margin-bottom: var(--space-xl);
}
.win-stat {
display: flex;
flex-direction: column;
align-items: center;
background: var(--surface);
padding: var(--space-md);
border-radius: calc(var(--squircle-factor) * 80px);
border: 1px solid var(--glass-border);
backdrop-filter: blur(10px);
min-width: 80px;
}
.win-stat-value {
font-size: var(--font-size-xl);
font-weight: 700;
color: var(--accent);
margin-bottom: var(--space-xs);
}
.win-stat-label {
font-size: var(--font-size-xs);
color: var(--text-muted);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Animations */
@keyframes bounce {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Responsive Design */
@media (max-width: 768px) {
.game-container {
padding: var(--space-lg);
margin: var(--space-sm);
}
.game-title {
font-size: var(--font-size-2xl);
}
.game-stats {
gap: var(--space-md);
}
.stat-item {
min-width: 80px;
padding: var(--space-sm);
}
.puzzle-board {
max-width: 320px;
gap: 6px;
}
.puzzle-tile {
font-size: var(--font-size-xl);
}
.game-controls {
gap: var(--space-sm);
}
.control-btn {
padding: var(--space-sm) var(--space-md);
font-size: var(--font-size-sm);
}
.modal-content {
padding: var(--space-xl);
margin: var(--space-md);
}
.win-stats {
gap: var(--space-md);
}
}
@media (max-width: 480px) {
.puzzle-board {
max-width: 280px;
gap: 4px;
}
.puzzle-tile {
font-size: var(--font-size-lg);
}
.game-controls {
flex-direction: column;
align-items: center;
}
.control-btn {
width: 100%;
max-width: 200px;
justify-content: center;
}
.shop-container {
margin-top: var(--space-md);
}
.shop-item {
padding: var(--space-sm);
}
}
/* Points System */
.points-display {
position: fixed;
top: var(--space-md);
right: var(--space-md);
background: var(--glass-background);
backdrop-filter: blur(20px);
border-radius: calc(var(--squircle-factor) * 100px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
padding: var(--space-md);
display: flex;
align-items: center;
gap: var(--space-sm);
z-index: 100;
}
.points-icon {
font-size: var(--font-size-lg);
}
.points-value {
font-size: var(--font-size-lg);
font-weight: 700;
color: var(--accent);
}
/* Quick Power-ups Panel */
.quick-powerups {
position: fixed;
top: calc(var(--space-md) + 60px);
/* Position under points display */
right: var(--space-md);
background: var(--glass-background);
backdrop-filter: blur(20px);
border-radius: calc(var(--squircle-factor) * 80px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
padding: var(--space-sm);
display: flex;
flex-direction: column;
gap: var(--space-xs);
z-index: 99;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: all var(--transition-fast);
max-width: 200px;
}
.quick-powerups.show {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.quick-powerup-item {
display: flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-xs) var(--space-sm);
background: var(--surface);
border-radius: calc(var(--squircle-factor) * 60px);
border: 1px solid var(--glass-border);
cursor: pointer;
transition: all var(--transition-fast);
font-size: var(--font-size-sm);
}
.quick-powerup-item:hover {
background: var(--surface-hover);
transform: translateX(-3px);
box-shadow: 0 2px 8px rgba(var(--primary-rgb), 0.2);
}
.quick-powerup-item.active {
background: linear-gradient(135deg, var(--secondary), var(--accent));
color: white;
border-color: var(--secondary);
}
.quick-powerup-icon {
font-size: var(--font-size-lg);
min-width: 24px;
text-align: center;
}
.quick-powerup-name {
font-weight: 500;
flex: 1;
}
/* Shop System */
.shop-container {
margin-top: var(--space-lg);
background: var(--surface);
border-radius: calc(var(--squircle-factor) * 120px);
border: 1px solid var(--glass-border);
backdrop-filter: blur(10px);
padding: var(--space-lg);
}
.shop-title {
font-size: var(--font-size-xl);
font-weight: 700;
text-align: center;
margin-bottom: var(--space-md);
background: linear-gradient(135deg, var(--primary), var(--tertiary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.shop-items {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--space-md);
}
.shop-item {
background: var(--glass-background);
backdrop-filter: blur(15px);
border-radius: calc(var(--squircle-factor) * 80px);
border: 1px solid var(--glass-border);
box-shadow: var(--droplet-shadow);
padding: var(--space-lg);
text-align: center;
transition: all var(--transition-normal);
position: relative;
overflow: hidden;
}
.shop-item:hover {
transform: translateY(-4px);
box-shadow: var(--droplet-hover-shadow);
}
.shop-item.owned {
border-color: var(--secondary);
background: linear-gradient(
135deg,
rgba(var(--secondary-rgb), 0.2) 0%,
rgba(var(--primary-rgb), 0.1) 100%
);
}
.shop-item.affordable {
border-color: var(--primary);
}
.shop-item.unaffordable {
opacity: 0.6;
filter: grayscale(0.5);
}
.shop-item-icon {
font-size: 3rem;
margin-bottom: var(--space-sm);
display: block;
}
.shop-item-name {
font-size: var(--font-size-lg);
font-weight: 600;
margin-bottom: var(--space-xs);
color: var(--text);
}
.shop-item-description {
font-size: var(--font-size-xs);
color: var(--text-light);
margin-bottom: var(--space-md);
line-height: 1.4;
}
.shop-item-price {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-xs);
font-size: var(--font-size-base);
font-weight: 600;
color: var(--accent);
margin-bottom: var(--space-sm);
}
.shop-item-uses {
font-size: var(--font-size-xs);
color: var(--text-muted);
margin-bottom: var(--space-sm);
}
.shop-buy-btn,
.shop-use-btn {
width: 100%;
padding: var(--space-sm) var(--space-md);
border: none;
border-radius: calc(var(--squircle-factor) * 60px);
font-size: var(--font-size-sm);
font-weight: 600;
cursor: pointer;
transition: all var(--transition-fast);
backdrop-filter: blur(10px);
}
.shop-buy-btn {
background: linear-gradient(135deg, var(--primary), var(--tertiary));
color: white;
}
.shop-use-btn {
background: linear-gradient(135deg, var(--secondary), var(--accent));
color: white;
}
.shop-buy-btn:hover,
.shop-use-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(var(--primary-rgb), 0.3);
}
.shop-buy-btn:disabled,
.shop-use-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
/* Debug Mode */
.debug-toggle {
position: fixed;
top: var(--space-md);
left: var(--space-md);
background: var(--glass-background);
backdrop-filter: blur(20px);
border-radius: calc(var(--squircle-factor) * 100px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
padding: var(--space-sm) var(--space-md);
display: flex;
align-items: center;
gap: var(--space-sm);
cursor: pointer;
transition: all var(--transition-fast);
z-index: 100;
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--text);
}
.debug-toggle:hover {
background: var(--surface-hover);
transform: translateY(-2px);
}
.debug-toggle.active {
background: linear-gradient(135deg, var(--accent), var(--primary));
color: white;
}
.debug-info {
position: fixed;
bottom: var(--space-md);
left: var(--space-md);
background: var(--glass-background);
backdrop-filter: blur(20px);
border-radius: calc(var(--squircle-factor) * 80px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
padding: var(--space-md);
font-size: var(--font-size-xs);
color: var(--text-light);
z-index: 100;
display: none;
}
.debug-info.show {
display: block;
}
/* Power-up Effects */
.lightning-mode .puzzle-tile:not(.empty) {
cursor: crosshair;
box-shadow: 0 0 20px rgba(255, 255, 0, 0.6), var(--droplet-shadow);
border-color: rgba(255, 255, 0, 0.8);
}
.lightning-mode .puzzle-tile:not(.empty):hover {
box-shadow: 0 0 30px rgba(255, 255, 0, 0.8), var(--droplet-hover-shadow);
transform: translateY(-4px) scale(1.05);
}
.lightning-target {
box-shadow: 0 0 25px rgba(255, 255, 0, 0.9) !important;
animation: lightning-pulse 0.5s ease-in-out infinite alternate;
}
@keyframes lightning-pulse {
from {
box-shadow: 0 0 25px rgba(255, 255, 0, 0.9), var(--droplet-shadow);
}
to {
box-shadow: 0 0 35px rgba(255, 255, 0, 1), var(--droplet-hover-shadow);
}
}
/* Zen Notification - Float in from top right */
.notification {
position: fixed;
top: var(--space-lg);
right: var(--space-lg);
transform: translateX(120%) scale(0.9);
background: var(--glass-background);
backdrop-filter: blur(20px);
border-radius: calc(var(--squircle-factor) * 100px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
padding: var(--space-md) var(--space-lg);
text-align: center;
font-size: var(--font-size-base);
font-weight: 400;
color: var(--text);
z-index: 1001;
opacity: 0;
visibility: hidden;
transition: all var(--transition-normal);
pointer-events: none;
max-width: 300px;
animation: gentleFloat var(--float-duration) ease-in-out infinite;
}
.notification.show {
opacity: 1;
visibility: visible;
transform: translateX(0) scale(1);
}
.notification.success {
border-color: var(--secondary);
color: var(--secondary);
}
.notification.error {
border-color: var(--accent);
color: var(--accent);
}
/* Gentle floating animation */
@keyframes gentleFloat {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-8px);
}
}
/* Ripple effect for buttons */
@keyframes ripple {
0% {
transform: scale(0);
opacity: 1;
}
100% {
transform: scale(2);
opacity: 0;
}
}
/* Rules Modal */
.rules-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
visibility: hidden;
transition: all var(--transition-normal);
z-index: 2000;
padding: var(--space-md);
}
.rules-modal.show {
opacity: 1;
visibility: visible;
}
.rules-content {
background: var(--glass-background);
backdrop-filter: blur(20px);
border-radius: calc(var(--squircle-factor) * 150px);
border: 1px solid var(--glass-border);
box-shadow: var(--glass-shadow);
max-width: 900px;
width: 100%;
max-height: 80vh;
display: flex;
flex-direction: column;
transform: scale(0.8);
transition: transform var(--transition-bounce);
}
.rules-modal.show .rules-content {
transform: scale(1);
}
.rules-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-lg);
border-bottom: 1px solid var(--glass-border);
}
.rules-title {
font-size: var(--font-size-xl);
font-weight: 500;
font-family: "Quicksand", var(--font-family);
letter-spacing: 0.01em;
background: linear-gradient(135deg, var(--primary), var(--tertiary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0;
}
.rules-close {
background: var(--surface);
border: 1px solid var(--glass-border);
color: var(--text);
width: 40px;
height: 40px;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-lg);
font-weight: bold;
transition: all var(--transition-fast);
}
.rules-close:hover {
background: var(--surface-hover);
transform: scale(1.1);
}
.rules-sections {
display: flex;
flex: 1;
min-height: 0;
}
.rules-nav {
background: var(--surface);
border-right: 1px solid var(--glass-border);
padding: var(--space-md);
display: flex;
flex-direction: column;
gap: var(--space-sm);
min-width: 160px;
}
.rules-nav-btn {
background: transparent;
border: 1px solid transparent;
color: var(--text-light);
padding: var(--space-sm) var(--space-md);
border-radius: calc(var(--squircle-factor) * 60px);
cursor: pointer;
transition: all var(--transition-fast);
font-size: var(--font-size-sm);
font-weight: 500;
text-align: left;
white-space: nowrap;
}
.rules-nav-btn:hover,
.rules-nav-btn.active {
background: var(--glass-background);
border-color: var(--glass-border);
color: var(--primary);
transform: translateX(4px);
}
.rules-body {
flex: 1;
padding: var(--space-lg);
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--surface) transparent;
}
.rules-body::-webkit-scrollbar {
width: 6px;
}
.rules-body::-webkit-scrollbar-track {
background: transparent;
}
.rules-body::-webkit-scrollbar-thumb {
background: var(--surface);
border-radius: 3px;
}
.rules-section {
display: none;
}
.rules-section.active {
display: block;
animation: fadeInUp 0.3s ease;
}
.rules-section h3 {
font-size: var(--font-size-lg);
font-weight: 500;
font-family: "Quicksand", var(--font-family);
color: var(--primary);
margin-bottom: var(--space-lg);
display: flex;
align-items: center;
gap: var(--space-sm);
}
.rule-item {
background: var(--surface);
border-radius: calc(var(--squircle-factor) * 80px);
border: 1px solid var(--glass-border);
padding: var(--space-lg);
margin-bottom: var(--space-md);
backdrop-filter: blur(10px);
}
.rule-item h4 {
font-size: var(--font-size-base);
font-weight: 500;
font-family: "Inter", var(--font-family);
color: var(--text);
margin-bottom: var(--space-sm);
display: flex;
align-items: center;
gap: var(--space-xs);
}
.rule-item p {
color: var(--text-light);
line-height: 1.6;
margin-bottom: var(--space-sm);
}
.rule-item ul {
color: var(--text-light);
padding-left: var(--space-lg);
line-height: 1.6;
}
.rule-item li {
margin-bottom: var(--space-xs);
}
.rule-item strong {
color: var(--text);
font-weight: 600;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Responsive Rules Modal */
@media (max-width: 768px) {
.rules-content {
margin: var(--space-sm);
max-height: 90vh;
}
.rules-sections {
flex-direction: column;
}
.rules-nav {
border-right: none;
border-bottom: 1px solid var(--glass-border);
flex-direction: row;
overflow-x: auto;
padding: var(--space-sm);
min-width: auto;
}
.rules-nav-btn {
white-space: nowrap;
flex-shrink: 0;
}
.rules-nav-btn:hover,
.rules-nav-btn.active {
transform: translateY(-2px);
}
.rules-body {
padding: var(--space-md);
}
.rule-item {
padding: var(--space-md);
}
/* Responsive quick power-ups */
.quick-powerups {
right: var(--space-sm);
top: calc(var(--space-sm) + 50px);
max-width: 150px;
}
.quick-powerup-item {
padding: var(--space-xs);
font-size: var(--font-size-xs);
}
.quick-powerup-icon {
font-size: var(--font-size-base);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment