Sliding Puzzle game with squircle cells with arrow key moves
A Pen by Philip Walsh on CodePen.
| <!-- 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); | |
| } | |
| } |
Sliding Puzzle game with squircle cells with arrow key moves
A Pen by Philip Walsh on CodePen.