feat: implement Pixel Slide puzzle game with procedural art generation and Web Audio engine

This commit is contained in:
shancheas
2026-08-27 17:36:00 +07:00
commit 4170748438
6 changed files with 3466 additions and 0 deletions
+328
View File
@@ -0,0 +1,328 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Pixel Slide — Premium 15-Puzzle Game</title>
<meta name="description" content="Experience the ultimate sliding puzzle game powered by PixiJS. Slices any custom image into dynamic sliding tiles across multiple difficulty levels." />
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@500;600;700;800;900&display=swap" rel="stylesheet" />
<!-- PixiJS v8.20.0 -->
<script src="https://cdn.jsdelivr.net/npm/pixi.js@8.20.0/dist/pixi.min.js"></script>
<!-- Stylesheet -->
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- Ambient background glow particles / decorative elements -->
<div class="ambient-bg">
<div class="glow-orb orb-1"></div>
<div class="glow-orb orb-2"></div>
<div class="glow-orb orb-3"></div>
<div class="grid-overlay"></div>
</div>
<div id="app-root">
<!-- TOP NAVIGATION BAR -->
<header class="app-header">
<div class="logo-group" id="btn-nav-home" role="button" tabindex="0" title="Return to Home">
<div class="logo-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="7" rx="1.5"></rect>
<rect x="14" y="3" width="7" height="7" rx="1.5"></rect>
<rect x="3" y="14" width="7" height="7" rx="1.5"></rect>
<rect x="14" y="14" width="7" height="7" rx="1.5" stroke-dasharray="2 2"></rect>
</svg>
</div>
<span class="logo-text">PIXEL<span class="logo-highlight">SLIDE</span></span>
</div>
<div class="header-controls">
<button id="btn-sound-toggle" class="icon-btn" title="Toggle Sound (M)" aria-label="Toggle Sound">
<span id="sound-icon">🔊</span>
</button>
<button id="btn-header-leaderboard" class="glass-pill-btn" title="View Leaderboard">
<span>🏆</span> Leaderboard
</button>
</div>
</header>
<!-- SCREEN: HOME -->
<main id="screen-home" class="screen active">
<div class="hero-section">
<h1 class="hero-title">
Master the <span class="gradient-text">Art of Sliding</span>
</h1>
<p class="hero-subtitle">
Upload any picture or choose from gallery presets. Sliced, shuffled, and rendered with ultra-smooth 60FPS physics.
</p>
<!-- Image source selection -->
<div class="upload-container">
<div class="upload-dropzone" id="upload-dropzone">
<input type="file" id="file-input" accept="image/*" class="visually-hidden" />
<div class="dropzone-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
</div>
<div class="dropzone-text">
<strong>Click to upload</strong> or drag and drop an image
</div>
<div class="dropzone-hint">Supports PNG, JPG, WebP, GIF (Max 15MB)</div>
</div>
<div class="preset-divider">
<span>OR CHOOSE A PRESET IMAGE</span>
</div>
<div class="preset-gallery" id="preset-gallery">
<!-- Preset items will be injected by JS -->
</div>
</div>
</div>
</main>
<!-- SCREEN: LEVEL SELECT -->
<section id="screen-level-select" class="screen">
<div class="screen-header">
<button id="btn-back-to-home" class="back-btn">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</svg>
Back
</button>
<div class="screen-title-group">
<h2>Select Difficulty</h2>
<p>Pick your grid complexity and test your spatial reasoning.</p>
</div>
</div>
<div class="level-selection-layout">
<div class="image-preview-card">
<div class="preview-header">
<span>Selected Image</span>
<button id="btn-change-image" class="text-link-btn">Change</button>
</div>
<div class="preview-canvas-wrapper">
<canvas id="preview-canvas"></canvas>
<div class="preview-grid-overlay" id="preview-grid-overlay"></div>
</div>
</div>
<div class="level-cards-grid" id="level-cards-grid">
<!-- Level Cards rendered by JS -->
</div>
</div>
</section>
<!-- SCREEN: GAMEPLAY -->
<section id="screen-game" class="screen">
<!-- HUD BAR -->
<div class="game-hud">
<div class="hud-left">
<button id="btn-game-pause" class="hud-btn pause-hud-btn" title="Pause Game (Esc)">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2">
<rect x="6" y="4" width="4" height="16" rx="1"></rect>
<rect x="14" y="4" width="4" height="16" rx="1"></rect>
</svg>
<span>Pause</span>
</button>
<div class="level-badge" id="hud-level-badge">Medium 4×4</div>
</div>
<div class="hud-center">
<div class="stat-box timer-box">
<span class="stat-label">TIME</span>
<span class="stat-value" id="hud-timer">00:00.00</span>
</div>
<div class="stat-box moves-box">
<span class="stat-label">MOVES</span>
<span class="stat-value" id="hud-moves">0</span>
</div>
</div>
<div class="hud-right">
<button id="btn-toggle-numbers" class="hud-btn icon-only" title="Toggle Tile Numbers (N)" aria-label="Toggle Numbers">
<span class="btn-icon">#️⃣</span>
</button>
<button id="btn-toggle-hint" class="hud-btn icon-only" title="Toggle Image Ghost Hint (H)" aria-label="Toggle Hint">
<span class="btn-icon">👁️</span>
</button>
<button id="btn-game-restart-quick" class="hud-btn icon-only" title="Restart Level (R)" aria-label="Restart Level">
<span class="btn-icon">🔄</span>
</button>
</div>
</div>
<!-- MAIN PUZZLE CANVAS CONTAINER -->
<div class="puzzle-viewport-container">
<div class="puzzle-board-frame" id="puzzle-board-frame">
<div id="pixi-canvas-mount"></div>
<!-- Ghost image overlay for hints -->
<div id="ghost-overlay" class="ghost-overlay"></div>
<!-- Shuffling overlay -->
<div id="shuffle-overlay" class="shuffle-overlay">
<div class="shuffle-spinner"></div>
<span>Shuffling tiles...</span>
</div>
</div>
</div>
<!-- BOTTOM CONTROL BAR -->
<div class="game-footer-info">
<span class="tip-text">💡 Tap adjacent tiles or use <kbd>Arrow Keys</kbd> / <kbd>WASD</kbd> to slide</span>
</div>
</section>
<!-- SCREEN: LEADERBOARD -->
<section id="screen-leaderboard" class="screen">
<div class="screen-header">
<button id="btn-back-from-leaderboard" class="back-btn">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</svg>
Back
</button>
<div class="screen-title-group">
<h2>Hall of Fame</h2>
<p>Global high scores sorted by fastest completion time and move count.</p>
</div>
</div>
<div class="leaderboard-container glass-card">
<div class="leaderboard-tabs" id="leaderboard-tabs">
<button class="tab-btn active" data-level="all">All Levels</button>
<button class="tab-btn" data-level="debug">Debug</button>
<button class="tab-btn" data-level="easy">Easy (3×3)</button>
<button class="tab-btn" data-level="medium">Medium (4×4)</button>
<button class="tab-btn" data-level="hard">Hard (5×5)</button>
<button class="tab-btn" data-level="insane">Insane (6×6)</button>
</div>
<div class="leaderboard-table-wrapper">
<table class="leaderboard-table">
<thead>
<tr>
<th>Rank</th>
<th>Player</th>
<th>Difficulty</th>
<th>Time</th>
<th>Moves</th>
<th>Date</th>
</tr>
</thead>
<tbody id="leaderboard-tbody">
<!-- Entries injected by JS -->
</tbody>
</table>
<div id="leaderboard-empty" class="leaderboard-empty">
<div class="empty-icon">🏆</div>
<p>No records found for this category.</p>
<span class="empty-sub">Complete a puzzle to claim the first spot!</span>
</div>
</div>
<div class="leaderboard-footer">
<button id="btn-clear-leaderboard" class="danger-link-btn">Clear Records</button>
</div>
</div>
</section>
<!-- OVERLAY: PAUSE MODAL -->
<div id="overlay-pause" class="modal-overlay">
<div class="modal-card pause-card">
<div class="modal-icon-badge"></div>
<h2>Game Paused</h2>
<p>Take a breath. The timer is currently stopped.</p>
<div class="modal-stats-row">
<div class="modal-stat">
<span class="m-label">Elapsed Time</span>
<span class="m-val" id="pause-modal-time">00:00.00</span>
</div>
<div class="modal-stat">
<span class="m-label">Moves Made</span>
<span class="m-val" id="pause-modal-moves">0</span>
</div>
</div>
<div class="modal-actions-stacked">
<button id="btn-pause-resume" class="btn btn-primary btn-glow">
<span></span> Resume Game
</button>
<button id="btn-pause-restart" class="btn btn-secondary">
<span>🔄</span> Restart Puzzle
</button>
<button id="btn-pause-change-level" class="btn btn-outline">
<span>🎮</span> Change Difficulty
</button>
<button id="btn-pause-quit" class="btn btn-danger-outline">
<span>🏠</span> Quit to Menu
</button>
</div>
</div>
</div>
<!-- OVERLAY: VICTORY MODAL -->
<div id="overlay-victory" class="modal-overlay">
<canvas id="confetti-canvas" class="confetti-canvas"></canvas>
<div class="modal-card victory-card">
<div class="victory-header">
<div class="crown-icon">👑</div>
<h2>Puzzle Solved!</h2>
<p class="victory-tagline">Flawless spatial mastery. Here are your final stats:</p>
</div>
<div class="victory-stats-grid">
<div class="v-stat-card highlight">
<span class="v-label">TIME</span>
<span class="v-value" id="victory-time">00:00.00</span>
</div>
<div class="v-stat-card">
<span class="v-label">MOVES</span>
<span class="v-value" id="victory-moves">0</span>
</div>
<div class="v-stat-card">
<span class="v-label">DIFFICULTY</span>
<span class="v-value" id="victory-difficulty">Medium</span>
</div>
</div>
<!-- Name submit form -->
<form id="victory-form" class="victory-form" onsubmit="return false;">
<label for="player-name-input">Save to Leaderboard</label>
<div class="input-with-btn">
<input type="text" id="player-name-input" maxlength="20" placeholder="Enter your name" required autocomplete="off" />
<button type="submit" id="btn-save-score" class="btn btn-primary">
Save Score
</button>
</div>
<div id="save-feedback" class="save-feedback"></div>
</form>
<div class="victory-actions">
<button id="btn-victory-replay" class="btn btn-secondary">
<span>🔄</span> Play Again
</button>
<button id="btn-victory-levels" class="btn btn-primary">
<span>🎮</span> Select Level
</button>
<button id="btn-victory-leaderboard" class="btn btn-outline">
<span>🏆</span> Leaderboard
</button>
</div>
</div>
</div>
</div>
<!-- Game script -->
<script src="game.js"></script>
</body>
</html>