sugakrit6 commited on
Commit
0db74d9
·
verified ·
1 Parent(s): 9b84199

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +786 -0
index.html ADDED
@@ -0,0 +1,786 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { Volume2, VolumeX, Power, Camera, X, Lightbulb, Upload } from 'lucide-react';
3
+
4
+ export default function FNAFGame() {
5
+ const [gameState, setGameState] = useState('upload');
6
+ const [currentView, setCurrentView] = useState('office');
7
+ const [selectedCamera, setSelectedCamera] = useState('CAM1');
8
+ const [power, setPower] = useState(100);
9
+ const [hour, setHour] = useState(0);
10
+ const [soundEnabled, setSoundEnabled] = useState(true);
11
+ const [animatronicPositions, setAnimatronicPositions] = useState({
12
+ char1: 'CAM1', // Starts in Left Hall
13
+ char2: 'CAM1', // Starts in Left Hall
14
+ char3: 'CAM3', // Starts in Left Corner
15
+ char4: 'CAM2' // Invisible - doesn't show in cameras
16
+ });
17
+ const [leftLightOn, setLeftLightOn] = useState(false);
18
+ const [rightLightOn, setRightLightOn] = useState(false);
19
+ const [leftDoorClosed, setLeftDoorClosed] = useState(false);
20
+ const [rightDoorClosed, setRightDoorClosed] = useState(false);
21
+ const [jumpscareCharacter, setJumpscareCharacter] = useState(null);
22
+ const [showTryAgain, setShowTryAgain] = useState(false);
23
+
24
+ const [images, setImages] = useState({
25
+ officeLeft: null,
26
+ officeRight: null,
27
+ officeBoth: null,
28
+ jumpscare1: null,
29
+ jumpscare2: null,
30
+ jumpscare3: null,
31
+ jumpscare4: null,
32
+ cam1: null,
33
+ cam2: null,
34
+ cam3: null,
35
+ // Specific animatronic in camera images
36
+ char1_cam1: null, // Character 1 in Left Hall
37
+ char2_cam1: null, // Character 2 in Left Hall
38
+ char3_cam3: null // Character 3 in Left Corner
39
+ // Character 4 doesn't appear in cameras (invisible/teleporter)
40
+ });
41
+
42
+ const cameras = {
43
+ CAM1: { name: 'Left Hall' },
44
+ CAM2: { name: 'Right Hall' },
45
+ CAM3: { name: 'Left Corner' }
46
+ };
47
+
48
+ const handleImageUpload = (key, file) => {
49
+ if (file) {
50
+ const reader = new FileReader();
51
+ reader.onload = (e) => {
52
+ setImages(prev => ({ ...prev, [key]: e.target.result }));
53
+ };
54
+ reader.readAsDataURL(file);
55
+ }
56
+ };
57
+
58
+ const playJumpscareSound = () => {
59
+ if (!soundEnabled) return;
60
+ const ctx = new (window.AudioContext || window.webkitAudioContext)();
61
+ const oscillator = ctx.createOscillator();
62
+ const gainNode = ctx.createGain();
63
+
64
+ oscillator.connect(gainNode);
65
+ gainNode.connect(ctx.destination);
66
+
67
+ oscillator.type = 'sawtooth';
68
+ oscillator.frequency.value = 80;
69
+ gainNode.gain.setValueAtTime(0.8, ctx.currentTime);
70
+ gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 2);
71
+ oscillator.start(ctx.currentTime);
72
+ oscillator.stop(ctx.currentTime + 2);
73
+ };
74
+
75
+ const playSound = (type) => {
76
+ if (!soundEnabled) return;
77
+ const ctx = new (window.AudioContext || window.webkitAudioContext)();
78
+ const oscillator = ctx.createOscillator();
79
+ const gainNode = ctx.createGain();
80
+
81
+ oscillator.connect(gainNode);
82
+ gainNode.connect(ctx.destination);
83
+
84
+ switch(type) {
85
+ case 'camera':
86
+ oscillator.frequency.value = 800;
87
+ gainNode.gain.setValueAtTime(0.2, ctx.currentTime);
88
+ gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.2);
89
+ oscillator.start(ctx.currentTime);
90
+ oscillator.stop(ctx.currentTime + 0.2);
91
+ break;
92
+ case 'door':
93
+ oscillator.frequency.value = 400;
94
+ gainNode.gain.setValueAtTime(0.2, ctx.currentTime);
95
+ gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.3);
96
+ oscillator.start(ctx.currentTime);
97
+ oscillator.stop(ctx.currentTime + 0.3);
98
+ break;
99
+ case 'light':
100
+ oscillator.frequency.value = 600;
101
+ gainNode.gain.setValueAtTime(0.15, ctx.currentTime);
102
+ gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.1);
103
+ oscillator.start(ctx.currentTime);
104
+ oscillator.stop(ctx.currentTime + 0.1);
105
+ break;
106
+ case 'move':
107
+ oscillator.frequency.value = 200;
108
+ gainNode.gain.setValueAtTime(0.15, ctx.currentTime);
109
+ gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.5);
110
+ oscillator.start(ctx.currentTime);
111
+ oscillator.stop(ctx.currentTime + 0.5);
112
+ break;
113
+ }
114
+ };
115
+
116
+ useEffect(() => {
117
+ if (gameState !== 'playing') return;
118
+
119
+ const interval = setInterval(() => {
120
+ setPower(prev => {
121
+ let drain = 0.15;
122
+ if (currentView === 'camera') drain += 0.25;
123
+ if (leftDoorClosed) drain += 0.15;
124
+ if (rightDoorClosed) drain += 0.15;
125
+ if (leftLightOn) drain += 0.1;
126
+ if (rightLightOn) drain += 0.1;
127
+
128
+ const newPower = Math.max(0, prev - drain);
129
+ if (newPower === 0) {
130
+ setGameState('gameover');
131
+ }
132
+ return newPower;
133
+ });
134
+ }, 1000);
135
+
136
+ return () => clearInterval(interval);
137
+ }, [gameState, currentView, leftDoorClosed, rightDoorClosed, leftLightOn, rightLightOn]);
138
+
139
+ useEffect(() => {
140
+ if (gameState !== 'playing') return;
141
+
142
+ const interval = setInterval(() => {
143
+ setHour(prev => {
144
+ const newHour = prev + 1;
145
+ if (newHour >= 6) {
146
+ setGameState('win');
147
+ }
148
+ return newHour;
149
+ });
150
+ }, 90000);
151
+
152
+ return () => clearInterval(interval);
153
+ }, [gameState]);
154
+
155
+ useEffect(() => {
156
+ if (gameState !== 'playing') return;
157
+
158
+ const interval = setInterval(() => {
159
+ const moveChance = currentView === 'office' ? 0.3 : 0.1;
160
+
161
+ setAnimatronicPositions(prev => {
162
+ const updated = { ...prev };
163
+ const characters = ['char1', 'char2', 'char3', 'char4'];
164
+
165
+ characters.forEach(char => {
166
+ if (Math.random() < moveChance) {
167
+ const currentPos = updated[char];
168
+
169
+ if (currentPos === 'CAM5') {
170
+ if (!leftDoorClosed && currentView === 'office' && Math.random() < 0.6) {
171
+ triggerJumpscare(char);
172
+ return;
173
+ }
174
+ updated[char] = leftDoorClosed ? 'CAM1' : 'CAM5';
175
+ } else if (currentPos === 'CAM6') {
176
+ if (!rightDoorClosed && currentView === 'office' && Math.random() < 0.6) {
177
+ triggerJumpscare(char);
178
+ return;
179
+ }
180
+ updated[char] = rightDoorClosed ? 'CAM1' : 'CAM6';
181
+ } else {
182
+ const possibleMoves = ['CAM1', 'CAM2', 'CAM3', 'CAM4', 'CAM5', 'CAM6'];
183
+ const validMoves = possibleMoves.filter(cam => cam !== currentPos);
184
+ updated[char] = validMoves[Math.floor(Math.random() * validMoves.length)];
185
+ playSound('move');
186
+ }
187
+ }
188
+ });
189
+
190
+ return updated;
191
+ });
192
+ }, 6000);
193
+
194
+ return () => clearInterval(interval);
195
+ }, [gameState, currentView, leftDoorClosed, rightDoorClosed]);
196
+
197
+ const triggerJumpscare = (character) => {
198
+ playJumpscareSound();
199
+ setJumpscareCharacter(character);
200
+ setGameState('jumpscare');
201
+
202
+ setTimeout(() => {
203
+ setShowTryAgain(true);
204
+ }, 2000);
205
+ };
206
+
207
+ const startGame = () => {
208
+ setGameState('playing');
209
+ setPower(100);
210
+ setHour(0);
211
+ setCurrentView('office');
212
+ setLeftDoorClosed(false);
213
+ setRightDoorClosed(false);
214
+ setLeftLightOn(false);
215
+ setRightLightOn(false);
216
+ setShowTryAgain(false);
217
+ setJumpscareCharacter(null);
218
+ setAnimatronicPositions({
219
+ char1: 'CAM1',
220
+ char2: 'CAM1',
221
+ char3: 'CAM3',
222
+ char4: 'CAM2'
223
+ });
224
+ };
225
+
226
+ const toggleCamera = () => {
227
+ if (currentView === 'office') {
228
+ setCurrentView('camera');
229
+ playSound('camera');
230
+ } else {
231
+ setCurrentView('office');
232
+ playSound('camera');
233
+ }
234
+ };
235
+
236
+ const toggleDoor = (side) => {
237
+ playSound('door');
238
+ if (side === 'left') {
239
+ setLeftDoorClosed(!leftDoorClosed);
240
+ } else {
241
+ setRightDoorClosed(!rightDoorClosed);
242
+ }
243
+ };
244
+
245
+ const toggleLight = (side) => {
246
+ playSound('light');
247
+ if (side === 'left') {
248
+ setLeftLightOn(!leftLightOn);
249
+ } else {
250
+ setRightLightOn(!rightLightOn);
251
+ }
252
+ };
253
+
254
+ const getOfficeImage = () => {
255
+ if (leftLightOn && rightLightOn && images.officeBoth) return images.officeBoth;
256
+ if (leftLightOn && images.officeLeft) return images.officeLeft;
257
+ if (rightLightOn && images.officeRight) return images.officeRight;
258
+ return images.officeBoth || images.officeLeft || images.officeRight;
259
+ };
260
+
261
+ const getJumpscareImage = () => {
262
+ switch(jumpscareCharacter) {
263
+ case 'char1': return images.jumpscare1;
264
+ case 'char2': return images.jumpscare2;
265
+ case 'char3': return images.jumpscare3;
266
+ case 'char4': return images.jumpscare4;
267
+ default: return images.jumpscare1;
268
+ }
269
+ };
270
+
271
+ // Upload screen
272
+ if (gameState === 'upload') {
273
+ return (
274
+ <div className="w-full min-h-screen bg-gradient-to-b from-gray-900 to-black p-6 overflow-y-auto">
275
+ <div className="max-w-6xl mx-auto">
276
+ <h1 className="text-5xl font-bold text-red-600 mb-2 text-center">NIGHT SHIFT</h1>
277
+ <p className="text-gray-400 mb-8 text-center text-lg">Upload Your Images (Drag & Drop)</p>
278
+
279
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
280
+ {/* Office Images */}
281
+ <div className="bg-gray-800 rounded-lg p-4">
282
+ <h3 className="text-white font-bold mb-2">Office - Left Light</h3>
283
+ <input
284
+ type="file"
285
+ accept="image/*"
286
+ onChange={(e) => handleImageUpload('officeLeft', e.target.files[0])}
287
+ className="hidden"
288
+ id="officeLeft"
289
+ />
290
+ <label htmlFor="officeLeft" className="cursor-pointer block">
291
+ {images.officeLeft ? (
292
+ <img src={images.officeLeft} alt="Office Left" className="w-full h-32 object-cover rounded mb-2" />
293
+ ) : (
294
+ <div className="w-full h-32 bg-gray-700 rounded mb-2 flex items-center justify-center">
295
+ <Upload className="text-gray-500" size={32} />
296
+ </div>
297
+ )}
298
+ <p className="text-gray-400 text-sm text-center">Click to upload</p>
299
+ </label>
300
+ </div>
301
+
302
+ <div className="bg-gray-800 rounded-lg p-4">
303
+ <h3 className="text-white font-bold mb-2">Office - Right Light</h3>
304
+ <input
305
+ type="file"
306
+ accept="image/*"
307
+ onChange={(e) => handleImageUpload('officeRight', e.target.files[0])}
308
+ className="hidden"
309
+ id="officeRight"
310
+ />
311
+ <label htmlFor="officeRight" className="cursor-pointer block">
312
+ {images.officeRight ? (
313
+ <img src={images.officeRight} alt="Office Right" className="w-full h-32 object-cover rounded mb-2" />
314
+ ) : (
315
+ <div className="w-full h-32 bg-gray-700 rounded mb-2 flex items-center justify-center">
316
+ <Upload className="text-gray-500" size={32} />
317
+ </div>
318
+ )}
319
+ <p className="text-gray-400 text-sm text-center">Click to upload</p>
320
+ </label>
321
+ </div>
322
+
323
+ <div className="bg-gray-800 rounded-lg p-4">
324
+ <h3 className="text-white font-bold mb-2">Office - Both Lights</h3>
325
+ <input
326
+ type="file"
327
+ accept="image/*"
328
+ onChange={(e) => handleImageUpload('officeBoth', e.target.files[0])}
329
+ className="hidden"
330
+ id="officeBoth"
331
+ />
332
+ <label htmlFor="officeBoth" className="cursor-pointer block">
333
+ {images.officeBoth ? (
334
+ <img src={images.officeBoth} alt="Office Both" className="w-full h-32 object-cover rounded mb-2" />
335
+ ) : (
336
+ <div className="w-full h-32 bg-gray-700 rounded mb-2 flex items-center justify-center">
337
+ <Upload className="text-gray-500" size={32} />
338
+ </div>
339
+ )}
340
+ <p className="text-gray-400 text-sm text-center">Click to upload</p>
341
+ </label>
342
+ </div>
343
+
344
+ {/* Jumpscare Images */}
345
+ {[1, 2, 3, 4].map(num => (
346
+ <div key={num} className="bg-gray-800 rounded-lg p-4">
347
+ <h3 className="text-white font-bold mb-2">Jumpscare {num}</h3>
348
+ <input
349
+ type="file"
350
+ accept="image/*"
351
+ onChange={(e) => handleImageUpload(`jumpscare${num}`, e.target.files[0])}
352
+ className="hidden"
353
+ id={`jumpscare${num}`}
354
+ />
355
+ <label htmlFor={`jumpscare${num}`} className="cursor-pointer block">
356
+ {images[`jumpscare${num}`] ? (
357
+ <img src={images[`jumpscare${num}`]} alt={`Jumpscare ${num}`} className="w-full h-32 object-cover rounded mb-2" />
358
+ ) : (
359
+ <div className="w-full h-32 bg-gray-700 rounded mb-2 flex items-center justify-center">
360
+ <Upload className="text-gray-500" size={32} />
361
+ </div>
362
+ )}
363
+ <p className="text-gray-400 text-sm text-center">Click to upload</p>
364
+ </label>
365
+ </div>
366
+ ))}
367
+
368
+ {/* Camera Images */}
369
+ {[1, 2, 3].map(num => (
370
+ <div key={num} className="bg-gray-800 rounded-lg p-4">
371
+ <h3 className="text-white font-bold mb-2">CAM{num} - {cameras[`CAM${num}`].name} (Empty)</h3>
372
+ <input
373
+ type="file"
374
+ accept="image/*"
375
+ onChange={(e) => handleImageUpload(`cam${num}`, e.target.files[0])}
376
+ className="hidden"
377
+ id={`cam${num}`}
378
+ />
379
+ <label htmlFor={`cam${num}`} className="cursor-pointer block">
380
+ {images[`cam${num}`] ? (
381
+ <img src={images[`cam${num}`]} alt={`Camera ${num}`} className="w-full h-32 object-cover rounded mb-2" />
382
+ ) : (
383
+ <div className="w-full h-32 bg-gray-700 rounded mb-2 flex items-center justify-center">
384
+ <Upload className="text-gray-500" size={32} />
385
+ </div>
386
+ )}
387
+ <p className="text-gray-400 text-sm text-center">Click to upload</p>
388
+ </label>
389
+ </div>
390
+ ))}
391
+
392
+ {/* Animatronic in Camera Images */}
393
+ <div className="col-span-full">
394
+ <h2 className="text-2xl font-bold text-red-500 mb-4 mt-6">Animatronics in Cameras</h2>
395
+ </div>
396
+
397
+ <div className="bg-gray-700 rounded-lg p-4">
398
+ <h3 className="text-xl font-bold text-yellow-400 mb-4">Animatronic 1 (in Left Hall)</h3>
399
+ <input
400
+ type="file"
401
+ accept="image/*"
402
+ onChange={(e) => handleImageUpload('char1_cam1', e.target.files[0])}
403
+ className="hidden"
404
+ id="char1_cam1"
405
+ />
406
+ <label htmlFor="char1_cam1" className="cursor-pointer block">
407
+ {images.char1_cam1 ? (
408
+ <img src={images.char1_cam1} alt="Char 1 CAM1" className="w-full h-40 object-cover rounded mb-2" />
409
+ ) : (
410
+ <div className="w-full h-40 bg-gray-800 rounded mb-2 flex items-center justify-center">
411
+ <Upload className="text-gray-500" size={32} />
412
+ </div>
413
+ )}
414
+ <p className="text-gray-400 text-sm text-center">Click to upload</p>
415
+ </label>
416
+ </div>
417
+
418
+ <div className="bg-gray-700 rounded-lg p-4">
419
+ <h3 className="text-xl font-bold text-yellow-400 mb-4">Animatronic 2 (in Left Hall)</h3>
420
+ <input
421
+ type="file"
422
+ accept="image/*"
423
+ onChange={(e) => handleImageUpload('char2_cam1', e.target.files[0])}
424
+ className="hidden"
425
+ id="char2_cam1"
426
+ />
427
+ <label htmlFor="char2_cam1" className="cursor-pointer block">
428
+ {images.char2_cam1 ? (
429
+ <img src={images.char2_cam1} alt="Char 2 CAM1" className="w-full h-40 object-cover rounded mb-2" />
430
+ ) : (
431
+ <div className="w-full h-40 bg-gray-800 rounded mb-2 flex items-center justify-center">
432
+ <Upload className="text-gray-500" size={32} />
433
+ </div>
434
+ )}
435
+ <p className="text-gray-400 text-sm text-center">Click to upload</p>
436
+ </label>
437
+ </div>
438
+
439
+ <div className="bg-gray-700 rounded-lg p-4">
440
+ <h3 className="text-xl font-bold text-yellow-400 mb-4">Animatronic 3 (in Left Corner)</h3>
441
+ <input
442
+ type="file"
443
+ accept="image/*"
444
+ onChange={(e) => handleImageUpload('char3_cam3', e.target.files[0])}
445
+ className="hidden"
446
+ id="char3_cam3"
447
+ />
448
+ <label htmlFor="char3_cam3" className="cursor-pointer block">
449
+ {images.char3_cam3 ? (
450
+ <img src={images.char3_cam3} alt="Char 3 CAM3" className="w-full h-40 object-cover rounded mb-2" />
451
+ ) : (
452
+ <div className="w-full h-40 bg-gray-800 rounded mb-2 flex items-center justify-center">
453
+ <Upload className="text-gray-500" size={32} />
454
+ </div>
455
+ )}
456
+ <p className="text-gray-400 text-sm text-center">Click to upload</p>
457
+ </label>
458
+ </div>
459
+
460
+ <div className="bg-gray-700 rounded-lg p-4">
461
+ <h3 className="text-xl font-bold text-yellow-400 mb-4">Animatronic 4 (Doesn't Show in Cameras)</h3>
462
+ <div className="w-full h-40 bg-gray-800 rounded flex items-center justify-center">
463
+ <p className="text-gray-500 text-center">This character is invisible<br/>and never appears in cameras!</p>
464
+ </div>
465
+ </div>
466
+ </div>
467
+
468
+ <div className="mt-8 text-center">
469
+ <button
470
+ onClick={() => setGameState('menu')}
471
+ className="bg-red-600 hover:bg-red-700 text-white px-12 py-4 rounded-lg text-2xl font-bold"
472
+ >
473
+ START GAME →
474
+ </button>
475
+ <p className="text-gray-500 mt-4">You can skip images - placeholders will be used</p>
476
+ </div>
477
+ </div>
478
+ </div>
479
+ );
480
+ }
481
+
482
+ // Menu
483
+ if (gameState === 'menu') {
484
+ return (
485
+ <div className="w-full h-screen bg-black flex items-center justify-center">
486
+ <div className="text-center">
487
+ <h1 className="text-6xl font-bold text-red-600 mb-4 animate-pulse">
488
+ NIGHT SHIFT
489
+ </h1>
490
+ <p className="text-gray-400 mb-8 text-xl">Survive Until 6 AM</p>
491
+ <div className="space-y-4">
492
+ <button
493
+ onClick={startGame}
494
+ className="block mx-auto bg-red-600 hover:bg-red-700 text-white px-8 py-4 rounded text-2xl font-bold"
495
+ >
496
+ START NIGHT
497
+ </button>
498
+ <button
499
+ onClick={() => setGameState('upload')}
500
+ className="block mx-auto bg-gray-700 hover:bg-gray-600 text-white px-8 py-3 rounded text-lg"
501
+ >
502
+ Change Images
503
+ </button>
504
+ </div>
505
+ <div className="mt-8">
506
+ <button
507
+ onClick={() => setSoundEnabled(!soundEnabled)}
508
+ className="text-gray-400 hover:text-white"
509
+ >
510
+ {soundEnabled ? <Volume2 size={32} /> : <VolumeX size={32} />}
511
+ </button>
512
+ </div>
513
+ </div>
514
+ </div>
515
+ );
516
+ }
517
+
518
+ // Jumpscare
519
+ if (gameState === 'jumpscare') {
520
+ const jumpImg = getJumpscareImage();
521
+ return (
522
+ <div className="w-full h-screen bg-black flex items-center justify-center relative overflow-hidden">
523
+ {jumpImg ? (
524
+ <img
525
+ src={jumpImg}
526
+ alt="Jumpscare"
527
+ className="absolute inset-0 w-full h-full object-contain animate-pulse"
528
+ style={{
529
+ filter: 'blur(2px)',
530
+ imageRendering: 'pixelated',
531
+ animation: 'shake 0.1s infinite'
532
+ }}
533
+ />
534
+ ) : (
535
+ <div className="text-9xl animate-pulse">💀</div>
536
+ )}
537
+ {showTryAgain && (
538
+ <>
539
+ <div
540
+ className="absolute inset-0 bg-red-600"
541
+ style={{ animation: 'fadeIn 1s ease-in' }}
542
+ />
543
+ <div className="relative z-20 text-center">
544
+ <h1 className="text-6xl font-bold text-white mb-8">
545
+ TRY AGAIN
546
+ </h1>
547
+ <button
548
+ onClick={() => {
549
+ setShowTryAgain(false);
550
+ setGameState('menu');
551
+ }}
552
+ className="bg-white text-black px-8 py-4 rounded text-xl font-bold hover:bg-gray-200"
553
+ >
554
+ Back to Menu
555
+ </button>
556
+ </div>
557
+ </>
558
+ )}
559
+ <style>{`
560
+ @keyframes shake {
561
+ 0%, 100% { transform: translate(0, 0) rotate(0deg); }
562
+ 25% { transform: translate(-5px, 5px) rotate(-2deg); }
563
+ 50% { transform: translate(5px, -5px) rotate(2deg); }
564
+ 75% { transform: translate(-5px, -5px) rotate(-1deg); }
565
+ }
566
+ @keyframes fadeIn {
567
+ from { opacity: 0; }
568
+ to { opacity: 1; }
569
+ }
570
+ `}</style>
571
+ </div>
572
+ );
573
+ }
574
+
575
+ // Game over
576
+ if (gameState === 'gameover') {
577
+ return (
578
+ <div className="w-full h-screen bg-black flex items-center justify-center">
579
+ <div className="text-center">
580
+ <h1 className="text-5xl font-bold text-red-600 mb-4">POWER OUT</h1>
581
+ <p className="text-gray-400 mb-8 text-xl">You ran out of power...</p>
582
+ <button
583
+ onClick={() => setGameState('menu')}
584
+ className="bg-red-600 hover:bg-red-700 text-white px-6 py-3 rounded text-xl"
585
+ >
586
+ Main Menu
587
+ </button>
588
+ </div>
589
+ </div>
590
+ );
591
+ }
592
+
593
+ // Win
594
+ if (gameState === 'win') {
595
+ return (
596
+ <div className="w-full h-screen bg-black flex items-center justify-center">
597
+ <div className="text-center">
598
+ <h1 className="text-5xl font-bold text-green-500 mb-4">6 AM</h1>
599
+ <p className="text-gray-400 mb-8 text-xl">You survived the night!</p>
600
+ <button
601
+ onClick={() => setGameState('menu')}
602
+ className="bg-green-600 hover:bg-green-700 text-white px-6 py-3 rounded text-xl"
603
+ >
604
+ Main Menu
605
+ </button>
606
+ </div>
607
+ </div>
608
+ );
609
+ }
610
+
611
+ // Office view
612
+ if (currentView === 'office') {
613
+ const officeImg = getOfficeImage();
614
+ return (
615
+ <div className="w-full h-screen bg-black relative overflow-hidden">
616
+ {officeImg ? (
617
+ <img
618
+ src={officeImg}
619
+ alt="Office"
620
+ className="absolute inset-0 w-full h-full object-cover"
621
+ />
622
+ ) : (
623
+ <div className="absolute inset-0 bg-gradient-to-b from-gray-800 to-black" />
624
+ )}
625
+
626
+ <div className="absolute top-4 left-4 text-white z-10">
627
+ <div className="bg-black bg-opacity-75 p-3 rounded">
628
+ <div className="flex items-center gap-2 mb-2">
629
+ <Power size={20} />
630
+ <span className="font-mono text-xl">{Math.floor(power)}%</span>
631
+ </div>
632
+ <div className="text-sm text-gray-400">
633
+ {hour} AM - {6 - hour} hours left
634
+ </div>
635
+ </div>
636
+ </div>
637
+
638
+ <div className="absolute left-4 top-1/2 transform -translate-y-1/2 z-10 space-y-4">
639
+ <button
640
+ onClick={() => toggleLight('left')}
641
+ className={`px-4 py-3 rounded font-bold flex items-center gap-2 ${
642
+ leftLightOn ? 'bg-yellow-500 text-black' : 'bg-gray-700 text-white'
643
+ }`}
644
+ >
645
+ <Lightbulb size={20} />
646
+ Light
647
+ </button>
648
+ <button
649
+ onClick={() => toggleDoor('left')}
650
+ className={`px-4 py-3 rounded font-bold ${
651
+ leftDoorClosed ? 'bg-red-600 text-white' : 'bg-green-600 text-white'
652
+ }`}
653
+ >
654
+ Door<br/>{leftDoorClosed ? 'CLOSED' : 'OPEN'}
655
+ </button>
656
+ </div>
657
+
658
+ <div className="absolute right-4 top-1/2 transform -translate-y-1/2 z-10 space-y-4">
659
+ <button
660
+ onClick={() => toggleLight('right')}
661
+ className={`px-4 py-3 rounded font-bold flex items-center gap-2 ${
662
+ rightLightOn ? 'bg-yellow-500 text-black' : 'bg-gray-700 text-white'
663
+ }`}
664
+ >
665
+ <Lightbulb size={20} />
666
+ Light
667
+ </button>
668
+ <button
669
+ onClick={() => toggleDoor('right')}
670
+ className={`px-4 py-3 rounded font-bold ${
671
+ rightDoorClosed ? 'bg-red-600 text-white' : 'bg-green-600 text-white'
672
+ }`}
673
+ >
674
+ Door<br/>{rightDoorClosed ? 'CLOSED' : 'OPEN'}
675
+ </button>
676
+ </div>
677
+
678
+ <button
679
+ onClick={toggleCamera}
680
+ className="absolute bottom-8 left-1/2 transform -translate-x-1/2 bg-green-600 hover:bg-green-700 text-white px-8 py-4 rounded-lg text-xl font-bold flex items-center gap-2 z-10"
681
+ >
682
+ <Camera size={24} />
683
+ CAMERAS
684
+ </button>
685
+ </div>
686
+ );
687
+ }
688
+
689
+ // Camera view
690
+ const animatronicsInView = Object.entries(animatronicPositions)
691
+ .filter(([char, pos]) => pos === selectedCamera)
692
+ .map(([char]) => char);
693
+
694
+ return (
695
+ <div className="w-full h-screen bg-black relative">
696
+ <div className="absolute inset-0 flex items-center justify-center p-8">
697
+ <div className="w-full max-w-4xl">
698
+ <div className="bg-gray-900 border-4 border-green-500 rounded-lg overflow-hidden aspect-video relative">
699
+ {/* Base camera background */}
700
+ {images[`cam${selectedCamera.slice(-1)}`] ? (
701
+ <img
702
+ src={images[`cam${selectedCamera.slice(-1)}`]}
703
+ alt={`Camera ${selectedCamera}`}
704
+ className="absolute inset-0 w-full h-full object-cover"
705
+ />
706
+ ) : (
707
+ <div className="absolute inset-0 bg-gray-800" />
708
+ )}
709
+
710
+ {/* Show animatronic image if present */}
711
+ {animatronicsInView.length > 0 && animatronicsInView.map(char => {
712
+ const animImage = images[`${char}_${selectedCamera.toLowerCase()}`];
713
+ return animImage ? (
714
+ <img
715
+ key={char}
716
+ src={animImage}
717
+ alt={`${char} in ${selectedCamera}`}
718
+ className="absolute inset-0 w-full h-full object-cover"
719
+ />
720
+ ) : null;
721
+ })}
722
+
723
+ <div className="absolute inset-0 bg-gradient-to-b from-transparent via-green-500 to-transparent opacity-10 animate-pulse pointer-events-none" />
724
+
725
+ <div className="absolute top-4 left-4 text-green-400 font-mono z-20">
726
+ {selectedCamera}
727
+ </div>
728
+ <div className="absolute top-4 right-4 text-green-400 font-mono animate-pulse z-20">
729
+ REC ●
730
+ </div>
731
+
732
+ {animatronicsInView.length > 0 && (
733
+ <div className="absolute bottom-4 left-4 right-4 text-center z-20">
734
+ <p className="text-red-500 text-xl font-bold animate-pulse">
735
+ ⚠️ MOVEMENT DETECTED
736
+ </p>
737
+ </div>
738
+ )}
739
+ </div>
740
+ </div>
741
+ </div>
742
+
743
+ <div className="absolute bottom-8 left-8 right-8">
744
+ <div className="grid grid-cols-3 gap-4">
745
+ {Object.keys(cameras).map(cam => {
746
+ const count = Object.values(animatronicPositions).filter(pos => pos === cam).length;
747
+ return (
748
+ <button
749
+ key={cam}
750
+ onClick={() => {
751
+ setSelectedCamera(cam);
752
+ playSound('camera');
753
+ }}
754
+ className={`p-4 rounded ${
755
+ selectedCamera === cam
756
+ ? 'bg-green-600 text-white'
757
+ : 'bg-gray-800 text-gray-400 hover:bg-gray-700'
758
+ } ${count > 0 ? 'border-2 border-red-500' : ''}`}
759
+ >
760
+ <div className="font-bold">{cam}</div>
761
+ {count > 0 && <div className="text-xs text-red-400 mt-1">{count} here</div>}
762
+ </button>
763
+ );
764
+ })}
765
+ </div>
766
+ </div>
767
+
768
+ <button
769
+ onClick={toggleCamera}
770
+ className="absolute top-4 right-4 bg-red-600 hover:bg-red-700 text-white p-3 rounded-full"
771
+ >
772
+ <X size={24} />
773
+ </button>
774
+
775
+ <div className="absolute top-4 left-4 text-white">
776
+ <div className="bg-black bg-opacity-75 p-3 rounded">
777
+ <div className="flex items-center gap-2 mb-2">
778
+ <Power size={20} />
779
+ <span className="font-mono text-xl">{Math.floor(power)}%</span>
780
+ </div>
781
+ <div className="text-sm text-gray-400">{hour} AM</div>
782
+ </div>
783
+ </div>
784
+ </div>
785
+ );
786
+ }