shubham6924 commited on
Commit
00136b5
·
verified ·
1 Parent(s): 5578d97

Add 1 files

Browse files
Files changed (1) hide show
  1. index.html +491 -260
index.html CHANGED
@@ -3,119 +3,167 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Roof Running AC Unit Minigame</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
10
- @keyframes fall {
11
- 0% { transform: translateY(0); opacity: 1; }
12
- 100% { transform: translateY(100px); opacity: 0; }
13
- }
14
-
15
  @keyframes shake {
16
  0%, 100% { transform: translateX(0); }
17
  10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
18
  20%, 40%, 60%, 80% { transform: translateX(5px); }
19
  }
20
 
21
- .cube {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  transition: all 0.3s ease;
23
- cursor: pointer;
24
  }
25
 
26
- .cube:hover {
27
- transform: scale(1.05);
28
- box-shadow: 0 0 10px rgba(255, 255, 255, 0.7);
 
29
  }
30
 
31
- .removing {
32
- animation: fall 0.5s forwards;
 
 
33
  }
34
 
35
- .shake {
36
- animation: shake 0.5s;
 
37
  }
38
 
39
- .progress-bar {
40
- transition: width 0.5s linear;
41
  }
42
 
43
- .bg-red { background-color: #ef4444; }
44
- .bg-green { background-color: #10b981; }
45
- .bg-blue { background-color: #3b82f6; }
 
 
 
 
 
 
 
 
 
 
 
 
46
  </style>
47
  </head>
48
- <body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4">
49
- <div class="max-w-4xl w-full bg-gray-800 rounded-xl shadow-2xl overflow-hidden border-2 border-gray-700">
50
- <!-- Header -->
51
- <div class="bg-gray-700 p-4 flex items-center justify-between">
52
- <div class="flex items-center space-x-3">
53
- <i class="fas fa-screwdriver text-yellow-400 text-2xl"></i>
54
- <h1 class="text-xl font-bold">AC Unit Theft Minigame</h1>
55
- </div>
56
  <div class="flex items-center space-x-4">
57
- <div class="flex items-center space-x-2">
58
  <i class="fas fa-clock text-blue-400"></i>
59
- <span id="timer" class="font-mono">60</span>
60
  </div>
61
- <div class="flex items-center space-x-2">
62
- <i class="fas fa-cubes text-green-400"></i>
63
- <span id="remaining">88</span>
64
  </div>
65
  </div>
66
  </div>
67
 
68
- <!-- Game Area -->
69
- <div class="p-6">
70
- <!-- Instructions -->
71
- <div id="instructions" class="bg-gray-700 rounded-lg p-4 mb-6">
72
- <div class="flex justify-between items-center">
73
- <h2 class="font-bold text-lg mb-2">How to play:</h2>
74
- <button id="close-instructions" class="text-gray-300 hover:text-white">
75
- <i class="fas fa-times"></i>
76
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  </div>
78
- <ul class="list-disc pl-5 space-y-1 text-sm">
79
- <li>Click on groups of 2+ same-colored cubes to remove them</li>
80
- <li>Cubes will fall down and align left after removal</li>
81
- <li>Remove all cubes before time runs out to succeed</li>
82
- <li>Single cubes cannot be removed</li>
83
- </ul>
84
- </div>
85
-
86
- <!-- Game Grid -->
87
- <div class="flex justify-center">
88
- <div id="game-grid" class="grid grid-cols-11 gap-1 bg-gray-900 p-2 rounded-lg border border-gray-700"></div>
89
  </div>
90
 
91
- <!-- Progress Bar -->
92
- <div class="mt-6 bg-gray-700 rounded-full h-4 overflow-hidden">
93
- <div id="progress-bar" class="h-full bg-gradient-to-r from-green-500 to-blue-500 progress-bar" style="width: 100%"></div>
94
- </div>
95
  </div>
96
 
97
  <!-- Controls -->
98
- <div class="bg-gray-700 p-4 flex justify-between items-center">
99
- <button id="start-btn" class="bg-green-600 hover:bg-green-700 px-4 py-2 rounded-lg font-medium flex items-center space-x-2 transition">
100
  <i class="fas fa-play"></i>
101
- <span>Start Game</span>
102
  </button>
103
- <button id="reset-btn" class="bg-gray-600 hover:bg-gray-500 px-4 py-2 rounded-lg font-medium flex items-center space-x-2 transition">
104
  <i class="fas fa-redo"></i>
105
  <span>Reset</span>
106
  </button>
 
 
 
 
 
 
 
 
 
107
  </div>
108
  </div>
109
 
110
  <!-- Result Modal -->
111
  <div id="result-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center hidden z-50">
112
- <div class="bg-gray-800 rounded-xl p-6 max-w-md w-full border-2 border-gray-700 transform transition-all duration-300 scale-95 opacity-0">
113
  <div class="text-center">
114
  <div id="result-icon" class="text-6xl mb-4">
115
  <i class="fas fa-check-circle text-green-500"></i>
116
  </div>
117
  <h2 id="result-title" class="text-2xl font-bold mb-2">Success!</h2>
118
- <p id="result-message" class="text-gray-300 mb-6">You successfully stole the AC unit!</p>
119
  <button id="close-modal" class="bg-blue-600 hover:bg-blue-700 px-6 py-2 rounded-lg font-medium transition">
120
  Continue
121
  </button>
@@ -124,256 +172,424 @@
124
  </div>
125
 
126
  <script>
127
- // Game variables
128
- const GRID_WIDTH = 11;
129
- const GRID_HEIGHT = 8;
130
- const COLORS = ['red', 'green', 'blue'];
131
- const TIME_LIMIT = 60; // seconds
132
-
133
- let gameGrid = [];
134
- let selectedCells = [];
135
- let gameActive = false;
136
- let timeLeft = TIME_LIMIT;
137
- let timerInterval;
138
- let remainingCubes = GRID_WIDTH * GRID_HEIGHT;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
  // DOM elements
141
- const gameGridElement = document.getElementById('game-grid');
142
- const timerElement = document.getElementById('timer');
143
- const remainingElement = document.getElementById('remaining');
144
- const progressBar = document.getElementById('progress-bar');
145
  const startBtn = document.getElementById('start-btn');
146
  const resetBtn = document.getElementById('reset-btn');
147
- const instructions = document.getElementById('instructions');
148
- const closeInstructions = document.getElementById('close-instructions');
 
149
  const resultModal = document.getElementById('result-modal');
150
  const resultTitle = document.getElementById('result-title');
151
  const resultMessage = document.getElementById('result-message');
152
  const resultIcon = document.getElementById('result-icon');
153
  const closeModal = document.getElementById('close-modal');
 
154
 
155
  // Initialize game
156
  function initGame() {
157
- gameGrid = [];
158
- selectedCells = [];
159
- gameGridElement.innerHTML = '';
160
-
161
- // Create grid
162
- for (let y = 0; y < GRID_HEIGHT; y++) {
163
- gameGrid[y] = [];
164
- for (let x = 0; x < GRID_WIDTH; x++) {
165
- const color = COLORS[Math.floor(Math.random() * COLORS.length)];
166
- gameGrid[y][x] = { color, x, y };
167
-
168
- const cube = document.createElement('div');
169
- cube.className = `w-8 h-8 rounded-sm cube bg-${color}`;
170
- cube.dataset.x = x;
171
- cube.dataset.y = y;
172
- cube.addEventListener('click', () => handleCubeClick(x, y));
173
-
174
- gameGridElement.appendChild(cube);
175
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  }
177
 
178
- remainingCubes = GRID_WIDTH * GRID_HEIGHT;
179
- remainingElement.textContent = remainingCubes;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  }
181
 
182
  // Start the game
183
  function startGame() {
184
- if (gameActive) return;
185
 
186
- gameActive = true;
187
- timeLeft = TIME_LIMIT;
188
- timerElement.textContent = timeLeft;
189
- progressBar.style.width = '100%';
190
 
 
191
  startBtn.disabled = true;
192
- startBtn.classList.remove('bg-green-600', 'hover:bg-green-700');
193
  startBtn.classList.add('bg-gray-500', 'cursor-not-allowed');
194
 
195
- timerInterval = setInterval(() => {
196
- timeLeft--;
197
- timerElement.textContent = timeLeft;
198
- progressBar.style.width = `${(timeLeft / TIME_LIMIT) * 100}%`;
199
 
200
- if (timeLeft <= 10) {
201
- progressBar.classList.remove('bg-gradient-to-r', 'from-green-500', 'to-blue-500');
202
- progressBar.classList.add('bg-gradient-to-r', 'from-yellow-500', 'to-red-500');
203
  }
204
 
205
- if (timeLeft <= 0) {
206
  endGame(false);
207
  }
208
  }, 1000);
 
 
 
 
 
 
 
 
209
  }
210
 
211
- // Reset the game
212
- function resetGame() {
213
- clearInterval(timerInterval);
214
- gameActive = false;
215
 
216
- startBtn.disabled = false;
217
- startBtn.classList.add('bg-green-600', 'hover:bg-green-700');
218
- startBtn.classList.remove('bg-gray-500', 'cursor-not-allowed');
 
 
 
219
 
220
- progressBar.classList.remove('bg-gradient-to-r', 'from-yellow-500', 'to-red-500');
221
- progressBar.classList.add('bg-gradient-to-r', 'from-green-500', 'to-blue-500');
 
 
 
 
 
 
 
 
222
 
223
- initGame();
 
 
224
  }
225
 
226
- // Handle cube click
227
- function handleCubeClick(x, y) {
228
- if (!gameActive) return;
229
 
230
- const cube = gameGrid[y][x];
231
- if (!cube) return;
 
 
232
 
233
- // Find connected cubes of the same color
234
- const connected = findConnectedCubes(x, y, cube.color);
235
 
236
- // If less than 2 connected cubes, can't remove
237
- if (connected.length < 2) {
238
- const cubeElement = document.querySelector(`[data-x="${x}"][data-y="${y}"]`);
239
- cubeElement.classList.add('shake');
240
- setTimeout(() => {
241
- cubeElement.classList.remove('shake');
242
- }, 500);
243
- return;
244
- }
245
 
246
- // Mark cubes for removal
247
- connected.forEach(pos => {
248
- const { x, y } = pos;
249
- gameGrid[y][x] = null;
250
-
251
- const cubeElement = document.querySelector(`[data-x="${x}"][data-y="${y}"]`);
252
- cubeElement.classList.add('removing');
253
- setTimeout(() => {
254
- cubeElement.remove();
255
- }, 500);
256
- });
257
 
258
- // Update remaining cubes count
259
- remainingCubes -= connected.length;
260
- remainingElement.textContent = remainingCubes;
261
 
262
- // After animation completes, collapse the grid
263
- setTimeout(() => {
264
- collapseGrid();
265
-
266
- // Check if game is won
267
- if (remainingCubes === 0) {
268
- endGame(true);
269
- }
270
- }, 500);
271
  }
272
 
273
- // Find connected cubes of the same color
274
- function findConnectedCubes(x, y, color, visited = []) {
275
- const key = `${x},${y}`;
276
- if (visited.includes(key)) return [];
277
-
278
- visited.push(key);
279
- const results = [{ x, y }];
280
-
281
- // Check adjacent cells (up, down, left, right)
282
- const directions = [
283
- { dx: 0, dy: -1 }, // up
284
- { dx: 0, dy: 1 }, // down
285
- { dx: -1, dy: 0 }, // left
286
- { dx: 1, dy: 0 } // right
287
- ];
288
-
289
- for (const dir of directions) {
290
- const nx = x + dir.dx;
291
- const ny = y + dir.dy;
292
-
293
- if (
294
- nx >= 0 && nx < GRID_WIDTH &&
295
- ny >= 0 && ny < GRID_HEIGHT &&
296
- gameGrid[ny][nx] &&
297
- gameGrid[ny][nx].color === color
298
- ) {
299
- results.push(...findConnectedCubes(nx, ny, color, visited));
300
- }
301
- }
302
 
303
- return results;
 
 
 
 
 
 
304
  }
305
 
306
- // Collapse the grid after removal
307
- function collapseGrid() {
308
- // First, let columns fall down
309
- for (let x = 0; x < GRID_WIDTH; x++) {
310
- let emptySpots = 0;
 
 
 
 
 
 
 
 
 
311
 
312
- // From bottom to top
313
- for (let y = GRID_HEIGHT - 1; y >= 0; y--) {
314
- if (!gameGrid[y][x]) {
315
- emptySpots++;
316
- } else if (emptySpots > 0) {
317
- // Move cube down
318
- gameGrid[y + emptySpots][x] = gameGrid[y][x];
319
- gameGrid[y][x] = null;
320
  }
321
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  }
 
 
 
 
 
323
 
324
- // Then, shift columns left to fill empty columns
325
- let emptyColumns = 0;
326
- for (let x = 0; x < GRID_WIDTH; x++) {
327
- // Check if column is empty
328
- let columnEmpty = true;
329
- for (let y = 0; y < GRID_HEIGHT; y++) {
330
- if (gameGrid[y][x]) {
331
- columnEmpty = false;
332
- break;
333
- }
334
- }
 
 
 
 
 
 
 
 
 
 
 
335
 
336
- if (columnEmpty) {
337
- emptyColumns++;
338
- } else if (emptyColumns > 0) {
339
- // Shift column left
340
- for (let y = 0; y < GRID_HEIGHT; y++) {
341
- gameGrid[y][x - emptyColumns] = gameGrid[y][x];
342
- gameGrid[y][x] = null;
343
- }
344
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
 
347
- // Rebuild the grid visually
348
- gameGridElement.innerHTML = '';
349
- for (let y = 0; y < GRID_HEIGHT; y++) {
350
- for (let x = 0; x < GRID_WIDTH; x++) {
351
- if (gameGrid[y][x]) {
352
- const cube = gameGrid[y][x];
353
- const cubeElement = document.createElement('div');
354
- cubeElement.className = `w-8 h-8 rounded-sm cube bg-${cube.color}`;
355
- cubeElement.dataset.x = x;
356
- cubeElement.dataset.y = y;
357
- cubeElement.addEventListener('click', () => handleCubeClick(x, y));
358
- gameGridElement.appendChild(cubeElement);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  }
360
- }
361
  }
362
  }
363
 
364
  // End the game
365
  function endGame(success) {
366
- clearInterval(timerInterval);
367
- gameActive = false;
 
 
 
 
 
 
 
 
 
 
 
 
368
 
369
  // Show result modal
 
 
 
 
 
370
  if (success) {
371
  resultTitle.textContent = "Success!";
372
- resultMessage.textContent = "You successfully stole the AC unit!";
373
  resultIcon.innerHTML = '<i class="fas fa-check-circle text-green-500"></i>';
374
  } else {
375
  resultTitle.textContent = "Failed!";
376
- resultMessage.textContent = "You ran out of time! The cops might be coming...";
377
  resultIcon.innerHTML = '<i class="fas fa-times-circle text-red-500"></i>';
378
  }
379
 
@@ -384,25 +600,40 @@
384
  }, 10);
385
  }
386
 
387
- // Event listeners
388
- startBtn.addEventListener('click', () => {
389
- startGame();
390
- instructions.classList.add('hidden');
391
- });
392
-
393
- resetBtn.addEventListener('click', resetGame);
394
-
395
- closeInstructions.addEventListener('click', () => {
396
- instructions.classList.add('hidden');
397
- });
398
-
399
- closeModal.addEventListener('click', () => {
400
  document.querySelector('#result-modal > div').classList.remove('scale-100', 'opacity-100');
401
  document.querySelector('#result-modal > div').classList.add('scale-95', 'opacity-0');
402
  setTimeout(() => {
403
  resultModal.classList.add('hidden');
404
- resetGame();
405
  }, 300);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
  });
407
 
408
  // Initialize on load
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>NoPixel Lockpick Minigame</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
 
 
 
 
 
10
  @keyframes shake {
11
  0%, 100% { transform: translateX(0); }
12
  10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
13
  20%, 40%, 60%, 80% { transform: translateX(5px); }
14
  }
15
 
16
+ @keyframes pulse {
17
+ 0%, 100% { opacity: 1; }
18
+ 50% { opacity: 0.5; }
19
+ }
20
+
21
+ @keyframes rotatePick {
22
+ 0% { transform: rotate(0deg); }
23
+ 100% { transform: rotate(360deg); }
24
+ }
25
+
26
+ .lockpick-shake {
27
+ animation: shake 0.5s;
28
+ }
29
+
30
+ .pin-pulse {
31
+ animation: pulse 1s infinite;
32
+ }
33
+
34
+ .pick-rotate {
35
+ animation: rotatePick 0.5s linear infinite;
36
+ }
37
+
38
+ .pick-break {
39
+ transform: rotate(45deg) scaleY(0.5);
40
+ opacity: 0.5;
41
  transition: all 0.3s ease;
 
42
  }
43
 
44
+ .pin-success {
45
+ background-color: #10B981 !important;
46
+ transform: scale(1.1);
47
+ transition: all 0.3s ease;
48
  }
49
 
50
+ .pin-failure {
51
+ background-color: #EF4444 !important;
52
+ transform: scale(0.9);
53
+ transition: all 0.3s ease;
54
  }
55
 
56
+ .lock-body {
57
+ background: linear-gradient(135deg, #1F2937 0%, #111827 100%);
58
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
59
  }
60
 
61
+ .pick-tool {
62
+ transition: transform 0.1s ease;
63
  }
64
 
65
+ .progress-step {
66
+ transition: all 0.3s ease;
67
+ }
68
+
69
+ .progress-step.active {
70
+ background-color: #3B82F6;
71
+ }
72
+
73
+ .progress-step.completed {
74
+ background-color: #10B981;
75
+ }
76
+
77
+ .progress-step.failed {
78
+ background-color: #EF4444;
79
+ }
80
  </style>
81
  </head>
82
+ <body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4 font-mono">
83
+ <div class="max-w-md w-full">
84
+ <!-- Game Header -->
85
+ <div class="flex justify-between items-center mb-4">
86
+ <h1 class="text-xl font-bold flex items-center">
87
+ <i class="fas fa-lock-open mr-2"></i>
88
+ Lockpick Minigame
89
+ </h1>
90
  <div class="flex items-center space-x-4">
91
+ <div class="flex items-center space-x-1">
92
  <i class="fas fa-clock text-blue-400"></i>
93
+ <span id="timer">30</span>
94
  </div>
95
+ <div class="flex items-center space-x-1">
96
+ <i class="fas fa-bolt text-yellow-400"></i>
97
+ <span id="difficulty">Medium</span>
98
  </div>
99
  </div>
100
  </div>
101
 
102
+ <!-- Lock Container -->
103
+ <div class="relative mb-8">
104
+ <!-- Lock Body -->
105
+ <div class="lock-body rounded-lg p-6 relative overflow-hidden">
106
+ <!-- Pins -->
107
+ <div id="pins-container" class="flex justify-center space-x-6 mb-8"></div>
108
+
109
+ <!-- Lock Cylinder -->
110
+ <div class="relative h-32 w-full flex justify-center items-center">
111
+ <div class="absolute h-32 w-32 rounded-full border-4 border-gray-600 flex justify-center items-center">
112
+ <div class="h-24 w-24 rounded-full border-2 border-gray-500"></div>
113
+ </div>
114
+
115
+ <!-- Lockpick Tool -->
116
+ <div id="lockpick" class="pick-tool absolute h-24 w-1 bg-gray-300 origin-bottom transform -rotate-45">
117
+ <div class="absolute -top-1 left-1/2 transform -translate-x-1/2 w-3 h-3 rounded-full bg-yellow-400"></div>
118
+ <div class="absolute top-6 left-1/2 transform -translate-x-1/2 w-4 h-1 bg-gray-400 rounded"></div>
119
+ </div>
120
+
121
+ <!-- Rotation Guide -->
122
+ <div class="absolute h-28 w-28 rounded-full border border-dashed border-gray-500 opacity-50"></div>
123
+ </div>
124
+
125
+ <!-- Instructions -->
126
+ <div id="instructions" class="text-center text-sm text-gray-400 mt-4">
127
+ <p>Rotate the lockpick with mouse or arrow keys to find the sweet spot</p>
128
+ <p class="text-xs mt-1">Hold <span class="font-bold">SPACE</span> to attempt pick</p>
129
  </div>
 
 
 
 
 
 
 
 
 
 
 
130
  </div>
131
 
132
+ <!-- Progress Steps -->
133
+ <div id="progress-steps" class="flex justify-center space-x-2 mt-4"></div>
 
 
134
  </div>
135
 
136
  <!-- Controls -->
137
+ <div class="flex justify-between">
138
+ <button id="start-btn" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg font-medium flex items-center space-x-2 transition">
139
  <i class="fas fa-play"></i>
140
+ <span>Start</span>
141
  </button>
142
+ <button id="reset-btn" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg font-medium flex items-center space-x-2 transition">
143
  <i class="fas fa-redo"></i>
144
  <span>Reset</span>
145
  </button>
146
+ <div class="flex items-center space-x-2">
147
+ <span class="text-sm">Difficulty:</span>
148
+ <select id="difficulty-select" class="bg-gray-800 text-white rounded px-2 py-1 text-sm">
149
+ <option value="easy">Easy</option>
150
+ <option value="medium" selected>Medium</option>
151
+ <option value="hard">Hard</option>
152
+ <option value="expert">Expert</option>
153
+ </select>
154
+ </div>
155
  </div>
156
  </div>
157
 
158
  <!-- Result Modal -->
159
  <div id="result-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center hidden z-50">
160
+ <div class="bg-gray-800 rounded-xl p-6 max-w-sm w-full border-2 border-gray-700 transform transition-all duration-300 scale-95 opacity-0">
161
  <div class="text-center">
162
  <div id="result-icon" class="text-6xl mb-4">
163
  <i class="fas fa-check-circle text-green-500"></i>
164
  </div>
165
  <h2 id="result-title" class="text-2xl font-bold mb-2">Success!</h2>
166
+ <p id="result-message" class="text-gray-300 mb-6">Lock picked successfully!</p>
167
  <button id="close-modal" class="bg-blue-600 hover:bg-blue-700 px-6 py-2 rounded-lg font-medium transition">
168
  Continue
169
  </button>
 
172
  </div>
173
 
174
  <script>
175
+ // Game configuration
176
+ const DIFFICULTY_LEVELS = {
177
+ easy: {
178
+ pinCount: 4,
179
+ timeLimit: 40,
180
+ sweetSpotRange: 20,
181
+ forceThreshold: 3000,
182
+ feedbackRange: 30
183
+ },
184
+ medium: {
185
+ pinCount: 5,
186
+ timeLimit: 30,
187
+ sweetSpotRange: 15,
188
+ forceThreshold: 2500,
189
+ feedbackRange: 25
190
+ },
191
+ hard: {
192
+ pinCount: 6,
193
+ timeLimit: 25,
194
+ sweetSpotRange: 10,
195
+ forceThreshold: 2000,
196
+ feedbackRange: 20
197
+ },
198
+ expert: {
199
+ pinCount: 7,
200
+ timeLimit: 20,
201
+ sweetSpotRange: 8,
202
+ forceThreshold: 1500,
203
+ feedbackRange: 15
204
+ }
205
+ };
206
+
207
+ // Game state
208
+ let gameState = {
209
+ active: false,
210
+ currentPin: 0,
211
+ pins: [],
212
+ sweetSpots: [],
213
+ pickAngle: -45,
214
+ holdingPosition: false,
215
+ holdStartTime: 0,
216
+ timeLeft: 0,
217
+ difficulty: 'medium',
218
+ timerInterval: null,
219
+ forceInterval: null,
220
+ success: false
221
+ };
222
 
223
  // DOM elements
224
+ const pinsContainer = document.getElementById('pins-container');
225
+ const lockpick = document.getElementById('lockpick');
226
+ const progressSteps = document.getElementById('progress-steps');
 
227
  const startBtn = document.getElementById('start-btn');
228
  const resetBtn = document.getElementById('reset-btn');
229
+ const timerElement = document.getElementById('timer');
230
+ const difficultyElement = document.getElementById('difficulty');
231
+ const difficultySelect = document.getElementById('difficulty-select');
232
  const resultModal = document.getElementById('result-modal');
233
  const resultTitle = document.getElementById('result-title');
234
  const resultMessage = document.getElementById('result-message');
235
  const resultIcon = document.getElementById('result-icon');
236
  const closeModal = document.getElementById('close-modal');
237
+ const instructions = document.getElementById('instructions');
238
 
239
  // Initialize game
240
  function initGame() {
241
+ // Reset game state
242
+ gameState.active = false;
243
+ gameState.currentPin = 0;
244
+ gameState.pins = [];
245
+ gameState.sweetSpots = [];
246
+ gameState.pickAngle = -45;
247
+ gameState.holdingPosition = false;
248
+ gameState.holdStartTime = 0;
249
+ gameState.timeLeft = DIFFICULTY_LEVELS[gameState.difficulty].timeLimit;
250
+ gameState.success = false;
251
+
252
+ // Clear intervals
253
+ if (gameState.timerInterval) clearInterval(gameState.timerInterval);
254
+ if (gameState.forceInterval) clearInterval(gameState.forceInterval);
255
+
256
+ // Update UI
257
+ timerElement.textContent = gameState.timeLeft;
258
+ difficultyElement.textContent = difficultySelect.options[difficultySelect.selectedIndex].text;
259
+
260
+ // Generate pins
261
+ pinsContainer.innerHTML = '';
262
+ const pinCount = DIFFICULTY_LEVELS[gameState.difficulty].pinCount;
263
+
264
+ for (let i = 0; i < pinCount; i++) {
265
+ const pin = document.createElement('div');
266
+ pin.className = 'w-4 h-8 bg-gray-500 rounded-t-full relative';
267
+ pin.dataset.index = i;
268
+ pinsContainer.appendChild(pin);
269
+ gameState.pins.push(pin);
270
+
271
+ // Generate random sweet spot for each pin (between -90 and 90 degrees)
272
+ const sweetSpot = Math.floor(Math.random() * 180) - 90;
273
+ gameState.sweetSpots.push(sweetSpot);
274
  }
275
 
276
+ // Initialize progress steps
277
+ progressSteps.innerHTML = '';
278
+ for (let i = 0; i < pinCount; i++) {
279
+ const step = document.createElement('div');
280
+ step.className = 'w-6 h-2 rounded-full bg-gray-600 progress-step';
281
+ step.dataset.index = i;
282
+ progressSteps.appendChild(step);
283
+ }
284
+
285
+ // Reset lockpick position
286
+ lockpick.style.transform = 'rotate(-45deg)';
287
+ lockpick.className = 'pick-tool absolute h-24 w-1 bg-gray-300 origin-bottom transform -rotate-45';
288
+
289
+ // Enable start button
290
+ startBtn.disabled = false;
291
+ startBtn.classList.remove('bg-gray-500', 'cursor-not-allowed');
292
+ startBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
293
  }
294
 
295
  // Start the game
296
  function startGame() {
297
+ if (gameState.active) return;
298
 
299
+ gameState.active = true;
300
+ gameState.timeLeft = DIFFICULTY_LEVELS[gameState.difficulty].timeLimit;
301
+ timerElement.textContent = gameState.timeLeft;
 
302
 
303
+ // Disable start button
304
  startBtn.disabled = true;
305
+ startBtn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
306
  startBtn.classList.add('bg-gray-500', 'cursor-not-allowed');
307
 
308
+ // Start timer
309
+ gameState.timerInterval = setInterval(() => {
310
+ gameState.timeLeft--;
311
+ timerElement.textContent = gameState.timeLeft;
312
 
313
+ if (gameState.timeLeft <= 10) {
314
+ timerElement.classList.add('text-red-400');
 
315
  }
316
 
317
+ if (gameState.timeLeft <= 0) {
318
  endGame(false);
319
  }
320
  }, 1000);
321
+
322
+ // Set up event listeners
323
+ document.addEventListener('keydown', handleKeyDown);
324
+ document.addEventListener('keyup', handleKeyUp);
325
+ document.addEventListener('mousemove', handleMouseMove);
326
+
327
+ // Highlight first pin
328
+ highlightCurrentPin();
329
  }
330
 
331
+ // Handle key down events
332
+ function handleKeyDown(e) {
333
+ if (!gameState.active) return;
 
334
 
335
+ // Rotate pick with arrow keys
336
+ if (e.key === 'ArrowLeft') {
337
+ rotatePick(-5);
338
+ } else if (e.key === 'ArrowRight') {
339
+ rotatePick(5);
340
+ }
341
 
342
+ // Attempt pick with space
343
+ if (e.key === ' ' && !gameState.holdingPosition) {
344
+ startHolding();
345
+ e.preventDefault(); // Prevent scrolling
346
+ }
347
+ }
348
+
349
+ // Handle key up events
350
+ function handleKeyUp(e) {
351
+ if (!gameState.active) return;
352
 
353
+ if (e.key === ' ') {
354
+ stopHolding();
355
+ }
356
  }
357
 
358
+ // Handle mouse movement
359
+ function handleMouseMove(e) {
360
+ if (!gameState.active) return;
361
 
362
+ // Calculate angle based on mouse position relative to lock
363
+ const lockRect = document.querySelector('.lock-body').getBoundingClientRect();
364
+ const centerX = lockRect.left + lockRect.width / 2;
365
+ const centerY = lockRect.top + lockRect.height / 2;
366
 
367
+ const deltaX = e.clientX - centerX;
368
+ const deltaY = e.clientY - centerY;
369
 
370
+ // Calculate angle in degrees (-180 to 180)
371
+ let angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
372
+ angle = (angle + 90) % 360; // Adjust so 0 is at the top
373
+ if (angle > 180) angle -= 360;
 
 
 
 
 
374
 
375
+ // Limit angle to reasonable range (-90 to 90)
376
+ angle = Math.max(-90, Math.min(90, angle));
 
 
 
 
 
 
 
 
 
377
 
378
+ // Update pick position
379
+ gameState.pickAngle = angle;
380
+ lockpick.style.transform = `rotate(${angle}deg)`;
381
 
382
+ // Check if we're close to sweet spot
383
+ checkSweetSpotProximity();
 
 
 
 
 
 
 
384
  }
385
 
386
+ // Rotate the lockpick
387
+ function rotatePick(degrees) {
388
+ gameState.pickAngle += degrees;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
 
390
+ // Limit angle to reasonable range (-90 to 90)
391
+ gameState.pickAngle = Math.max(-90, Math.min(90, gameState.pickAngle));
392
+
393
+ lockpick.style.transform = `rotate(${gameState.pickAngle}deg)`;
394
+
395
+ // Check if we're close to sweet spot
396
+ checkSweetSpotProximity();
397
  }
398
 
399
+ // Start holding position (attempting to pick)
400
+ function startHolding() {
401
+ if (!gameState.active || gameState.holdingPosition) return;
402
+
403
+ gameState.holdingPosition = true;
404
+ gameState.holdStartTime = Date.now();
405
+
406
+ // Check if we're in the sweet spot
407
+ const currentSweetSpot = gameState.sweetSpots[gameState.currentPin];
408
+ const sweetSpotRange = DIFFICULTY_LEVELS[gameState.difficulty].sweetSpotRange;
409
+
410
+ if (Math.abs(gameState.pickAngle - currentSweetSpot) <= sweetSpotRange) {
411
+ // In sweet spot - start picking
412
+ lockpick.classList.add('pick-rotate');
413
 
414
+ // Set timeout for successful pick
415
+ setTimeout(() => {
416
+ if (gameState.holdingPosition) {
417
+ pickSuccess();
 
 
 
 
418
  }
419
+ }, 1000);
420
+ } else {
421
+ // Not in sweet spot - start force timer
422
+ gameState.forceInterval = setInterval(() => {
423
+ const holdDuration = Date.now() - gameState.holdStartTime;
424
+ const forceThreshold = DIFFICULTY_LEVELS[gameState.difficulty].forceThreshold;
425
+
426
+ if (holdDuration >= forceThreshold) {
427
+ pickFailure();
428
+ } else {
429
+ // Show warning as we get closer to breaking
430
+ const progress = holdDuration / forceThreshold;
431
+ if (progress > 0.7) {
432
+ lockpick.classList.add('lockpick-shake');
433
+ setTimeout(() => {
434
+ lockpick.classList.remove('lockpick-shake');
435
+ }, 500);
436
+ }
437
+ }
438
+ }, 100);
439
  }
440
+ }
441
+
442
+ // Stop holding position
443
+ function stopHolding() {
444
+ if (!gameState.holdingPosition) return;
445
 
446
+ gameState.holdingPosition = false;
447
+ lockpick.classList.remove('pick-rotate');
448
+
449
+ if (gameState.forceInterval) {
450
+ clearInterval(gameState.forceInterval);
451
+ gameState.forceInterval = null;
452
+ }
453
+ }
454
+
455
+ // Check if we're close to the sweet spot
456
+ function checkSweetSpotProximity() {
457
+ if (gameState.currentPin >= gameState.pins.length) return;
458
+
459
+ const currentSweetSpot = gameState.sweetSpots[gameState.currentPin];
460
+ const feedbackRange = DIFFICULTY_LEVELS[gameState.difficulty].feedbackRange;
461
+ const difference = Math.abs(gameState.pickAngle - currentSweetSpot);
462
+
463
+ const pin = gameState.pins[gameState.currentPin];
464
+
465
+ if (difference <= feedbackRange) {
466
+ // Close to sweet spot - give feedback
467
+ pin.classList.add('pin-pulse');
468
 
469
+ // Change color based on proximity
470
+ const proximity = 1 - (difference / feedbackRange);
471
+ const red = Math.floor(255 * (1 - proximity));
472
+ const green = Math.floor(255 * proximity);
473
+ pin.style.backgroundColor = `rgb(${red}, ${green}, 0)`;
474
+ } else {
475
+ // Not close - reset feedback
476
+ pin.classList.remove('pin-pulse');
477
+ pin.style.backgroundColor = '';
478
+ }
479
+ }
480
+
481
+ // Successful pick
482
+ function pickSuccess() {
483
+ if (!gameState.active) return;
484
+
485
+ // Play sound (optional)
486
+ playSound('click');
487
+
488
+ // Mark pin as success
489
+ const pin = gameState.pins[gameState.currentPin];
490
+ pin.classList.remove('pin-pulse');
491
+ pin.classList.add('pin-success');
492
+
493
+ // Mark progress step as completed
494
+ const steps = progressSteps.querySelectorAll('.progress-step');
495
+ steps[gameState.currentPin].classList.add('completed');
496
+
497
+ // Move to next pin
498
+ gameState.currentPin++;
499
+
500
+ // Stop holding
501
+ stopHolding();
502
+
503
+ // Check if all pins are picked
504
+ if (gameState.currentPin >= gameState.pins.length) {
505
+ endGame(true);
506
+ } else {
507
+ // Highlight next pin
508
+ highlightCurrentPin();
509
  }
510
+ }
511
+
512
+ // Failed pick
513
+ function pickFailure() {
514
+ if (!gameState.active) return;
515
+
516
+ // Play sound (optional)
517
+ playSound('break');
518
+
519
+ // Mark pin as failure
520
+ const pin = gameState.pins[gameState.currentPin];
521
+ pin.classList.remove('pin-pulse');
522
+ pin.classList.add('pin-failure');
523
+
524
+ // Mark progress step as failed
525
+ const steps = progressSteps.querySelectorAll('.progress-step');
526
+ steps[gameState.currentPin].classList.add('failed');
527
 
528
+ // Break lockpick
529
+ lockpick.classList.add('pick-break');
530
+ stopHolding();
531
+
532
+ // End game
533
+ setTimeout(() => {
534
+ endGame(false);
535
+ }, 500);
536
+ }
537
+
538
+ // Highlight current pin
539
+ function highlightCurrentPin() {
540
+ // Reset all pins
541
+ gameState.pins.forEach(pin => {
542
+ pin.classList.remove('pin-pulse');
543
+ pin.style.backgroundColor = '';
544
+ });
545
+
546
+ // Highlight current pin
547
+ if (gameState.currentPin < gameState.pins.length) {
548
+ const pin = gameState.pins[gameState.currentPin];
549
+ pin.style.backgroundColor = '#3B82F6';
550
+
551
+ // Highlight current progress step
552
+ const steps = progressSteps.querySelectorAll('.progress-step');
553
+ steps.forEach((step, index) => {
554
+ if (index === gameState.currentPin) {
555
+ step.classList.add('active');
556
+ } else {
557
+ step.classList.remove('active');
558
  }
559
+ });
560
  }
561
  }
562
 
563
  // End the game
564
  function endGame(success) {
565
+ gameState.active = false;
566
+ gameState.success = success;
567
+
568
+ // Clear intervals
569
+ clearInterval(gameState.timerInterval);
570
+ clearInterval(gameState.forceInterval);
571
+
572
+ // Remove event listeners
573
+ document.removeEventListener('keydown', handleKeyDown);
574
+ document.removeEventListener('keyup', handleKeyUp);
575
+ document.removeEventListener('mousemove', handleMouseMove);
576
+
577
+ // Play sound
578
+ playSound(success ? 'unlock' : 'fail');
579
 
580
  // Show result modal
581
+ showResultModal(success);
582
+ }
583
+
584
+ // Show result modal
585
+ function showResultModal(success) {
586
  if (success) {
587
  resultTitle.textContent = "Success!";
588
+ resultMessage.textContent = "Lock picked successfully!";
589
  resultIcon.innerHTML = '<i class="fas fa-check-circle text-green-500"></i>';
590
  } else {
591
  resultTitle.textContent = "Failed!";
592
+ resultMessage.textContent = "The lockpick broke! Try again...";
593
  resultIcon.innerHTML = '<i class="fas fa-times-circle text-red-500"></i>';
594
  }
595
 
 
600
  }, 10);
601
  }
602
 
603
+ // Close result modal
604
+ function closeResultModal() {
 
 
 
 
 
 
 
 
 
 
 
605
  document.querySelector('#result-modal > div').classList.remove('scale-100', 'opacity-100');
606
  document.querySelector('#result-modal > div').classList.add('scale-95', 'opacity-0');
607
  setTimeout(() => {
608
  resultModal.classList.add('hidden');
609
+ initGame(); // Reset game
610
  }, 300);
611
+ }
612
+
613
+ // Play sound effects
614
+ function playSound(type) {
615
+ // In a real implementation, you would use the Web Audio API or preloaded audio elements
616
+ console.log(`Playing sound: ${type}`);
617
+ // Example implementation:
618
+ /*
619
+ const sounds = {
620
+ click: new Audio('click.mp3'),
621
+ break: new Audio('break.mp3'),
622
+ unlock: new Audio('unlock.mp3'),
623
+ fail: new Audio('fail.mp3')
624
+ };
625
+ sounds[type].currentTime = 0;
626
+ sounds[type].play();
627
+ */
628
+ }
629
+
630
+ // Event listeners
631
+ startBtn.addEventListener('click', startGame);
632
+ resetBtn.addEventListener('click', initGame);
633
+ closeModal.addEventListener('click', closeResultModal);
634
+ difficultySelect.addEventListener('change', () => {
635
+ gameState.difficulty = difficultySelect.value;
636
+ initGame();
637
  });
638
 
639
  // Initialize on load