NIGHT SHIFT — Five Nights
by LunarPanda233035 lines49.7 KB
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>NIGHT SHIFT — Five Nights</title>
<style>
*{
box-sizing:border-box;
-webkit-tap-highlight-color:transparent;
user-select:none;
}
html,body{
margin:0;
width:100%;
height:100%;
background:#050708;
color:#d9e3e3;
font-family:Arial,Helvetica,sans-serif;
overflow:hidden;
}
button{
font-family:inherit;
cursor:pointer;
}
#game{
position:relative;
width:100vw;
height:100vh;
min-height:520px;
overflow:hidden;
background:#080d0e;
}
/* =========================
INTRO
========================= */
.screen{
position:absolute;
inset:0;
display:flex;
align-items:center;
justify-content:center;
background:
radial-gradient(circle at 50% 45%,#182426 0%,#080c0d 45%,#020303 100%);
}
.panel{
width:min(560px,90vw);
border:2px solid #536365;
background:rgba(8,13,14,.96);
padding:34px;
box-shadow:0 0 60px rgba(0,0,0,.8);
}
.logo{
font-size:clamp(30px,7vw,64px);
letter-spacing:7px;
font-weight:900;
text-align:center;
color:#e5eeee;
}
.subtitle{
text-align:center;
color:#829294;
letter-spacing:4px;
margin:10px 0 35px;
}
.menuBtn{
width:100%;
padding:17px;
margin:8px 0;
background:#111a1b;
border:1px solid #657577;
color:#dbe5e5;
font-size:17px;
letter-spacing:2px;
}
.menuBtn:hover{
background:#1c292b;
}
.small{
text-align:center;
font-size:11px;
color:#647375;
margin-top:25px;
line-height:1.7;
}
/* =========================
HUD
========================= */
#hud{
position:absolute;
top:0;
left:0;
right:0;
height:92px;
z-index:50;
border-bottom:3px solid #3e4d4f;
background:
linear-gradient(90deg,#090f10,#101718,#090f10);
display:flex;
align-items:center;
justify-content:space-between;
padding:12px 24px;
}
#clock{
font-size:clamp(30px,5vw,54px);
font-weight:bold;
}
.hudSmall{
color:#91a4a4;
font-size:12px;
letter-spacing:3px;
}
#powerBox{
width:180px;
text-align:left;
}
#power{
font-size:27px;
font-weight:bold;
text-align:right;
}
.powerBar{
height:7px;
border:1px solid #78898a;
margin-top:5px;
}
#powerFill{
height:100%;
width:100%;
background:#b9c9bd;
transition:.3s;
}
/* =========================
OFFICE
========================= */
#office{
position:absolute;
top:92px;
bottom:0;
left:0;
right:0;
overflow:hidden;
background:
radial-gradient(ellipse at 50% 45%,#202b2d 0%,#121a1b 42%,#050809 100%);
}
#ceiling{
position:absolute;
top:0;
left:5%;
right:5%;
height:33%;
background:
linear-gradient(150deg,#0a1011,#172022 45%,#080c0d);
clip-path:polygon(10% 0,90% 0,72% 100%,28% 100%);
opacity:.85;
}
#backWall{
position:absolute;
top:30%;
left:17%;
right:17%;
bottom:22%;
background:
repeating-linear-gradient(
90deg,
rgba(255,255,255,.025) 0,
rgba(255,255,255,.025) 2px,
transparent 2px,
transparent 70px
),
linear-gradient(#182123,#11191a);
border-left:2px solid #273436;
border-right:2px solid #273436;
}
.floor{
position:absolute;
left:0;
right:0;
bottom:0;
height:28%;
background:
repeating-linear-gradient(
90deg,
transparent 0,
transparent 80px,
rgba(150,160,160,.09) 81px,
transparent 83px
),
repeating-linear-gradient(
0deg,
transparent 0,
transparent 65px,
rgba(150,160,160,.08) 66px,
transparent 68px
),
linear-gradient(#0d1516,#050708);
transform:perspective(450px) rotateX(5deg);
transform-origin:bottom;
}
/* =========================
DOORS
========================= */
.doorArea{
position:absolute;
top:35%;
width:19%;
height:51%;
min-width:130px;
}
#leftArea{
left:1.5%;
}
#rightArea{
right:1.5%;
}
.doorFrame{
position:absolute;
inset:0;
border:7px solid #59686a;
background:#070b0c;
box-shadow:
inset 0 0 30px #000,
0 0 20px rgba(0,0,0,.7);
}
.door{
position:absolute;
inset:8px;
background:
repeating-linear-gradient(
90deg,
#12191a 0,
#12191a 13px,
#1d2728 14px,
#0d1314 18px
);
transition:.18s;
}
.door.closed{
box-shadow:
inset 0 0 35px #000,
0 0 15px rgba(180,40,40,.15);
}
.door.closed:after{
content:"";
position:absolute;
inset:0;
background:
repeating-linear-gradient(
90deg,
rgba(255,255,255,.025) 0,
rgba(255,255,255,.025) 2px,
transparent 2px,
transparent 18px
);
}
.door.open{
transform:translateY(-82%);
opacity:.25;
}
.doorLabel{
position:absolute;
top:-30px;
width:100%;
text-align:center;
letter-spacing:3px;
color:#8b9b9c;
font-size:12px;
}
/* WINDOWS */
.window{
position:absolute;
top:12%;
width:45%;
height:28%;
border:5px solid #647476;
background:#020405;
overflow:hidden;
z-index:8;
}
#leftWindow{
right:-54%;
}
#rightWindow{
left:-54%;
}
.windowGlass{
position:absolute;
inset:5px;
background:
radial-gradient(ellipse at 50% 50%,rgba(70,85,87,.35),transparent 65%),
#060b0c;
overflow:hidden;
}
.windowLight{
position:absolute;
inset:0;
background:
radial-gradient(circle at center,#d8e1ce 0%,rgba(180,200,180,.35) 35%,rgba(100,130,110,.12) 65%,transparent 100%);
opacity:0;
transition:.1s;
}
.window.on .windowLight{
opacity:1;
}
.windowText{
position:absolute;
bottom:3px;
left:0;
right:0;
text-align:center;
font-size:9px;
color:#718080;
letter-spacing:2px;
}
/* monster silhouettes in windows */
.windowMonster{
position:absolute;
left:50%;
bottom:-8px;
transform:translateX(-50%) scale(.9);
width:60%;
height:85%;
opacity:0;
transition:.15s;
}
.window.on .windowMonster.visible{
opacity:1;
}
.monsterBody{
position:absolute;
left:20%;
right:20%;
bottom:0;
height:65%;
border-radius:40% 40% 8% 8%;
background:#263234;
}
.monsterHead{
position:absolute;
width:68%;
height:45%;
left:16%;
top:2%;
border-radius:48% 48% 42% 42%;
background:#354345;
box-shadow:inset 0 -8px 0 rgba(0,0,0,.3);
}
.eye{
position:absolute;
top:38%;
width:12px;
height:12px;
border-radius:50%;
background:#d8e4cf;
box-shadow:0 0 14px #dcead5;
}
.eye.left{left:24%}
.eye.right{right:24%}
.mouth{
position:absolute;
left:20%;
right:20%;
bottom:13%;
height:14%;
background:#060809;
border-radius:50%;
}
.monsterA .monsterHead{
border-radius:55% 45% 35% 50%;
background:#34474a;
}
.monsterA .monsterBody{
background:#22383a;
}
.monsterB .monsterHead{
border-radius:35% 55% 48% 45%;
background:#443a35;
}
.monsterB .monsterBody{
background:#332923;
}
.monsterC .monsterHead{
border-radius:48% 48% 55% 35%;
background:#3a3848;
}
.monsterC .monsterBody{
background:#292839;
}
.monsterD .monsterHead{
border-radius:42% 58% 40% 42%;
background:#3e343b;
}
.monsterD .monsterBody{
background:#30252c;
}
.ear{
position:absolute;
top:-18%;
width:28%;
height:35%;
background:inherit;
clip-path:polygon(50% 0,100% 100%,0 100%);
}
.ear.left{left:0}
.ear.right{right:0}
/* =========================
PHYSICAL CONTROLS
========================= */
.control{
position:absolute;
z-index:20;
width:125px;
background:#0a1112;
border:2px solid #526264;
padding:8px;
box-shadow:0 8px 20px #000;
}
#leftControl{
left:calc(1.5% + 24px);
top:calc(35% + 55%);
}
#rightControl{
right:calc(1.5% + 24px);
top:calc(35% + 55%);
}
.controlTitle{
text-align:center;
font-size:10px;
color:#8b999a;
letter-spacing:2px;
margin-bottom:6px;
}
.ctrlBtn{
width:100%;
padding:12px 4px;
margin:4px 0;
border:2px solid #59696a;
background:#111a1b;
color:#dbe5e5;
font-weight:bold;
font-size:12px;
}
.ctrlBtn.active{
background:#702c31;
border-color:#d05a5f;
}
.ctrlBtn.lightActive{
background:#6e7049;
border-color:#d7d89b;
color:#fff;
}
/* =========================
DESK
========================= */
#desk{
position:absolute;
bottom:8%;
left:50%;
transform:translateX(-50%);
width:240px;
height:100px;
background:#101819;
border:3px solid #38484a;
z-index:10;
box-shadow:0 10px 30px #000;
}
#terminal{
position:absolute;
width:90px;
height:65px;
left:50%;
top:-62px;
transform:translateX(-50%);
background:#0b1112;
border:3px solid #445355;
display:flex;
align-items:center;
justify-content:center;
font-size:10px;
color:#93a1a2;
letter-spacing:2px;
}
#monitorButton{
position:absolute;
bottom:-68px;
left:50%;
transform:translateX(-50%);
width:220px;
height:55px;
border:3px solid #667778;
background:#10191a;
color:#dce7e5;
font-size:16px;
font-weight:bold;
letter-spacing:2px;
}
/* =========================
CAMERA
========================= */
#camera{
position:absolute;
inset:0;
z-index:40;
display:none;
background:#070b0c;
}
#camera.active{
display:block;
}
.camView{
position:absolute;
top:0;
left:0;
right:0;
bottom:130px;
overflow:hidden;
background:
radial-gradient(ellipse at center,#1b2526,#0b1011 60%,#030405);
}
.camRoom{
position:absolute;
top:0;
bottom:0;
width:150%;
left:-25%;
transition:left .25s ease;
}
.camRoom.wall{
background:
repeating-linear-gradient(
90deg,
rgba(255,255,255,.045) 0,
rgba(255,255,255,.045) 2px,
transparent 2px,
transparent 80px
),
linear-gradient(90deg,#11191a,#252f30 50%,#0e1516);
}
.camRoom.storage{
background:
linear-gradient(90deg,#101616,#252d2d 45%,#111718);
}
.camRoom.dining{
background:
radial-gradient(ellipse at 50% 35%,#39423c,#151c1a 65%,#080b0b);
}
.camRoom.stage{
background:
linear-gradient(90deg,#090a0d,#2b1e24 48%,#090a0d);
}
.camRoom.back{
background:
linear-gradient(90deg,#111,#202426,#090b0c);
}
.roomCeiling{
position:absolute;
top:0;
left:0;
right:0;
height:25%;
background:linear-gradient(#06090a,#202728);
}
.roomFloor{
position:absolute;
bottom:0;
left:0;
right:0;
height:35%;
background:
repeating-linear-gradient(
90deg,
transparent 0,
transparent 90px,
rgba(255,255,255,.07) 91px,
transparent 93px
),
linear-gradient(#151d1e,#080b0c);
transform:perspective(500px) rotateX(5deg);
}
.camObject{
position:absolute;
border:3px solid #4c5c5e;
background:#11191a;
box-shadow:0 8px 20px #000;
}
.camLabel{
position:absolute;
top:18px;
left:25px;
padding:8px 14px;
border:2px solid #667779;
background:rgba(0,0,0,.65);
color:#b6c3c3;
letter-spacing:3px;
font-size:12px;
z-index:5;
}
.static{
position:absolute;
inset:0;
pointer-events:none;
opacity:.10;
background-image:
repeating-linear-gradient(
0deg,
rgba(255,255,255,.3) 0,
rgba(255,255,255,.3) 1px,
transparent 1px,
transparent 4px
);
animation:static .08s infinite;
}
@keyframes static{
0%{transform:translateY(0)}
50%{transform:translateY(3px)}
100%{transform:translateY(-2px)}
}
/* camera monster */
.camMonster{
position:absolute;
width:140px;
height:260px;
bottom:23%;
left:50%;
transform:translateX(-50%);
filter:drop-shadow(0 12px 18px #000);
transition:.4s;
}
.camMonster.hidden{
display:none;
}
.camMonster .body{
position:absolute;
bottom:0;
left:20%;
width:60%;
height:65%;
border-radius:45% 45% 10% 10%;
background:#293638;
}
.camMonster .head{
position:absolute;
top:0;
left:15%;
width:70%;
height:42%;
border-radius:48%;
background:#39484a;
}
.camMonster .eye{
position:absolute;
width:13px;
height:13px;
background:#e4f0d9;
border-radius:50%;
top:42%;
box-shadow:0 0 18px #fff;
}
.camMonster .eye.left{left:23%}
.camMonster .eye.right{right:23%}
.camMonster .mouth{
position:absolute;
bottom:12%;
left:20%;
right:20%;
height:17%;
background:#040606;
border-radius:50%;
}
/* different camera appearances */
.camA .head{
border-radius:55% 45% 35% 55%;
background:#394a4b;
}
.camA .body{background:#263b3d}
.camB .head{
border-radius:35% 55% 50% 40%;
background:#514239;
}
.camB .body{background:#392d26}
.camC .head{
border-radius:48% 48% 55% 35%;
background:#423e52;
}
.camC .body{background:#2c2939}
.camD .head{
border-radius:42% 58% 40% 42%;
background:#493943;
}
.camD .body{background:#30252e}
/* =========================
CAMERA BOTTOM BAR
========================= */
#camBottom{
position:absolute;
left:0;
right:0;
bottom:0;
height:130px;
border-top:3px solid #546567;
background:#090f10;
display:flex;
align-items:center;
justify-content:center;
gap:15px;
padding:12px;
}
.camArrow{
width:65px;
height:70px;
background:#111b1c;
border:2px solid #607173;
color:#dce6e5;
font-size:32px;
}
.camArrow:active{
background:#283637;
}
#camName{
width:210px;
text-align:center;
font-size:15px;
font-weight:bold;
letter-spacing:3px;
}
#closeCam{
position:absolute;
right:15px;
top:-55px;
width:120px;
height:43px;
background:#1a2021;
border:2px solid #657577;
color:#e3eaea;
}
/* =========================
MAP
========================= */
#map{
position:absolute;
right:25px;
top:80px;
width:240px;
min-height:230px;
z-index:8;
border:2px solid #5a6b6d;
background:rgba(5,9,10,.9);
padding:12px;
}
.mapTitle{
text-align:center;
font-size:12px;
letter-spacing:3px;
margin-bottom:10px;
}
.mapGrid{
display:grid;
grid-template-columns:1fr 1fr;
gap:7px;
}
.mapBtn{
min-height:52px;
background:#11191a;
border:2px solid #536466;
color:#ccd8d7;
font-size:10px;
line-height:1.2;
}
.mapBtn.current{
border-color:#d8dfdf;
box-shadow:0 0 8px rgba(210,220,220,.35);
}
.mapBtn.danger{
border-color:#9e4248;
color:#f0a1a3;
}
#playerMarker{
margin-top:10px;
text-align:center;
font-size:10px;
color:#879697;
}
/* =========================
JUMPSCARE
========================= */
#jumpscare{
position:absolute;
inset:0;
z-index:200;
display:none;
background:#020202;
align-items:center;
justify-content:center;
overflow:hidden;
}
#jumpscare.show{
display:flex;
animation:shake .07s infinite;
}
@keyframes shake{
0%{transform:translate(0)}
25%{transform:translate(8px,-5px)}
50%{transform:translate(-7px,6px)}
75%{transform:translate(5px,4px)}
100%{transform:translate(-4px,-6px)}
}
.scareFace{
position:relative;
width:min(500px,90vw);
height:min(500px,75vh);
border-radius:45%;
background:
radial-gradient(circle at 35% 40%,#eee 0 3%,transparent 4%),
radial-gradient(circle at 65% 40%,#eee 0 3%,transparent 4%),
radial-gradient(ellipse at center,#465456,#171c1d 65%,#050606);
box-shadow:0 0 100px #000;
transform:scale(1.15);
}
.scareFace:before,
.scareFace:after{
content:"";
position:absolute;
top:28%;
width:18%;
height:18%;
border-radius:50%;
background:#e9f4df;
box-shadow:0 0 35px #fff;
}
.scareFace:before{left:25%}
.scareFace:after{right:25%}
.scareMouth{
position:absolute;
left:20%;
right:20%;
bottom:18%;
height:27%;
background:#020202;
border-radius:50%;
border:5px solid #252b2c;
}
.scareText{
position:absolute;
bottom:25px;
left:0;
right:0;
text-align:center;
font-size:16px;
letter-spacing:5px;
color:#fff;
}
/* =========================
END SCREEN
========================= */
#endScreen{
display:none;
z-index:300;
}
#endScreen.show{
display:flex;
}
.endTitle{
font-size:42px;
text-align:center;
letter-spacing:5px;
margin-bottom:15px;
}
.endText{
text-align:center;
color:#9eacad;
line-height:1.7;
}
.hidden{
display:none!important;
}
/* =========================
RESPONSIVE
========================= */
@media(max-width:700px){
#hud{
height:75px;
padding:8px 12px;
}
#office{
top:75px;
}
.hudSmall{
font-size:8px;
letter-spacing:2px;
}
#powerBox{
width:110px;
}
#power{
font-size:20px;
}
.doorArea{
top:35%;
width:24%;
min-width:92px;
height:43%;
}
#leftArea{left:1%}
#rightArea{right:1%}
.control{
width:105px;
padding:5px;
}
#leftControl{
left:calc(1% + 5px);
top:calc(35% + 44%);
}
#rightControl{
right:calc(1% + 5px);
top:calc(35% + 44%);
}
.ctrlBtn{
padding:9px 2px;
font-size:10px;
}
#desk{
width:170px;
height:75px;
bottom:11%;
}
#terminal{
width:65px;
height:48px;
top:-48px;
}
#monitorButton{
width:175px;
height:47px;
bottom:-55px;
font-size:12px;
}
#map{
width:180px;
right:8px;
top:60px;
padding:8px;
}
.mapBtn{
min-height:45px;
font-size:8px;
}
#camBottom{
height:105px;
}
.camArrow{
width:55px;
height:55px;
font-size:25px;
}
#camName{
width:125px;
font-size:11px;
}
#closeCam{
top:-43px;
width:100px;
height:36px;
font-size:10px;
}
}
</style>
</head>
<body>
<div id="game">
<!-- =========================
MENU
========================= -->
<div id="menu" class="screen">
<div class="panel">
<div class="logo">NIGHT SHIFT</div>
<div class="subtitle">SECURITY TERMINAL</div>
<button class="menuBtn" onclick="startNight(1)">
START NIGHT 01
</button>
<button class="menuBtn" onclick="showNights()">
SELECT NIGHT
</button>
<div id="nightSelect" class="hidden">
<button class="menuBtn" onclick="startNight(1)">NIGHT 01</button>
<button class="menuBtn" onclick="startNight(2)">NIGHT 02</button>
<button class="menuBtn" onclick="startNight(3)">NIGHT 03</button>
<button class="menuBtn" onclick="startNight(4)">NIGHT 04</button>
<button class="menuBtn" onclick="startNight(5)">NIGHT 05</button>
</div>
<div class="small">
ORIGINAL SURVIVAL HORROR EXPERIENCE<br>
WATCH THE CAMERAS • MANAGE POWER • SURVIVE UNTIL 6 AM
</div>
</div>
</div>
<!-- =========================
HUD
========================= -->
<div id="hud" class="hidden">
<div>
<div class="hudSmall">NIGHT SHIFT / SECURITY NETWORK</div>
<div id="clock">12 AM</div>
<div class="hudSmall">SYSTEM ONLINE</div>
</div>
<div id="powerBox">
<div class="hudSmall">
NIGHT <span id="nightNumber">01</span>
</div>
<div id="power">100%</div>
<div class="powerBar">
<div id="powerFill"></div>
</div>
</div>
</div>
<!-- =========================
OFFICE
========================= -->
<div id="office" class="hidden">
<div id="ceiling"></div>
<div id="backWall"></div>
<div class="floor"></div>
<!-- LEFT DOOR -->
<div id="leftArea" class="doorArea">
<div class="doorLabel">WEST / LEFT</div>
<div class="doorFrame">
<div id="leftDoor" class="door open"></div>
<div id="leftWindow" class="window">
<div class="windowGlass">
<div class="windowLight"></div>
<div id="leftMonster" class="windowMonster monsterA">
<div class="monsterBody"></div>
<div class="monsterHead">
<div class="ear left"></div>
<div class="ear right"></div>
<div class="eye left"></div>
<div class="eye right"></div>
<div class="mouth"></div>
</div>
</div>
<div class="windowText">VISUAL CHECK</div>
</div>
</div>
</div>
</div>
<!-- RIGHT DOOR -->
<div id="rightArea" class="doorArea">
<div class="doorLabel">EAST / RIGHT</div>
<div class="doorFrame">
<div id="rightDoor" class="door open"></div>
<div id="rightWindow" class="window">
<div class="windowGlass">
<div class="windowLight"></div>
<div id="rightMonster" class="windowMonster monsterB">
<div class="monsterBody"></div>
<div class="monsterHead">
<div class="ear left"></div>
<div class="ear right"></div>
<div class="eye left"></div>
<div class="eye right"></div>
<div class="mouth"></div>
</div>
</div>
<div class="windowText">VISUAL CHECK</div>
</div>
</div>
</div>
</div>
<!-- LEFT PHYSICAL CONTROL -->
<div id="leftControl" class="control">
<div class="controlTitle">WEST CONTROL</div>
<button id="leftDoorBtn"
class="ctrlBtn"
onclick="toggleDoor('left')">
CLOSE DOOR
</button>
<button id="leftLightBtn"
class="ctrlBtn"
onclick="toggleLight('left')">
LIGHT OFF
</button>
</div>
<!-- RIGHT PHYSICAL CONTROL -->
<div id="rightControl" class="control">
<div class="controlTitle">EAST CONTROL</div>
<button id="rightDoorBtn"
class="ctrlBtn"
onclick="toggleDoor('right')">
CLOSE DOOR
</button>
<button id="rightLightBtn"
class="ctrlBtn"
onclick="toggleLight('right')">
LIGHT OFF
</button>
</div>
<!-- DESK -->
<div id="desk">
<div id="terminal">
SECURITY<br>
TERMINAL
</div>
<button id="monitorButton" onclick="openCamera()">
OPEN CAMERA SYSTEM
</button>
</div>
</div>
<!-- =========================
CAMERA SYSTEM
========================= -->
<div id="camera">
<div class="camView">
<div id="camRoom" class="camRoom wall">
<div class="roomCeiling"></div>
<div class="roomFloor"></div>
<div id="roomObjects"></div>
<div id="camMonster"
class="camMonster hidden">
<div class="body"></div>
<div class="head">
<div class="eye left"></div>
<div class="eye right"></div>
<div class="mouth"></div>
</div>
</div>
<div id="camLabel" class="camLabel">
CAM 01 / SHOWROOM
</div>
<div class="static"></div>
</div>
<!-- MAP -->
<div id="map">
<div class="mapTitle">
FACILITY MAP
</div>
<div class="mapGrid">
<button class="mapBtn current"
onclick="selectCam(1)">
CAM 01<br>SHOWROOM
</button>
<button class="mapBtn"
onclick="selectCam(2)">
CAM 02<br>STORAGE
</button>
<button class="mapBtn"
onclick="selectCam(3)">
CAM 03<br>WORKSHOP
</button>
<button class="mapBtn"
onclick="selectCam(4)">
CAM 04<br>DINING
</button>
<button class="mapBtn"
onclick="selectCam(5)">
CAM 05<br>WEST HALL
</button>
<button class="mapBtn"
onclick="selectCam(6)">
CAM 06<br>EAST HALL
</button>
<button class="mapBtn"
onclick="selectCam(7)">
CAM 07<br>BACK ROOM
</button>
<button class="mapBtn"
onclick="selectCam(8)">
CAM 08<br>STAGE
</button>
<button class="mapBtn"
onclick="selectCam(9)">
CAM 09<br>LOADING
</button>
</div>
<div id="playerMarker">
● YOU — SECURITY OFFICE
</div>
</div>
</div>
<!-- CAMERA BAR -->
<div id="camBottom">
<button class="camArrow"
onclick="moveCamera(-1)">
◀
</button>
<div id="camName">
CAM 01 / SHOWROOM
</div>
<button class="camArrow"
onclick="moveCamera(1)">
▶
</button>
<button id="closeCam"
onclick="closeCamera()">
CLOSE CAMERAS
</button>
</div>
</div>
<!-- =========================
JUMPSCARE
========================= -->
<div id="jumpscare">
<div class="scareFace">
<div class="scareMouth"></div>
</div>
<div class="scareText">
SIGNAL LOST
</div>
</div>
<!-- =========================
END
========================= -->
<div id="endScreen" class="screen">
<div class="panel">
<div id="endTitle" class="endTitle">
6 AM
</div>
<div id="endText" class="endText">
SHIFT COMPLETE
</div>
<button class="menuBtn"
onclick="location.reload()">
RETURN TO MENU
</button>
</div>
</div>
</div>
<script>
/* =========================================================
NIGHT SHIFT
ORIGINAL SINGLE-FILE HORROR GAME
========================================================= */
/* =========================
GAME STATE
========================= */
const state = {
running:false,
night:1,
hour:0,
minute:0,
power:100,
cameraOpen:false,
camera:1,
cameraSide:0,
leftDoor:false,
rightDoor:false,
leftLight:false,
rightLight:false,
gameOver:false,
elapsed:0,
lastTime:0
};
/* =========================
CAMERA DATA
========================= */
const cameras = {
1:{
name:"CAM 01 / SHOWROOM",
type:"showroom",
monsterPositions:{
1:{x:50,y:58},
2:{x:67,y:57},
3:{x:35,y:58},
4:{x:80,y:57}
}
},
2:{
name:"CAM 02 / STORAGE",
type:"storage",
monsterPositions:{
1:{x:34,y:57},
2:{x:70,y:58},
3:{x:48,y:60},
4:{x:82,y:57}
}
},
3:{
name:"CAM 03 / WORKSHOP",
type:"workshop",
monsterPositions:{
1:{x:58,y:59},
2:{x:31,y:60},
3:{x:76,y:59},
4:{x:48,y:58}
}
},
4:{
name:"CAM 04 / DINING",
type:"dining",
monsterPositions:{
1:{x:45,y:58},
2:{x:75,y:59},
3:{x:30,y:60},
4:{x:61,y:58}
}
},
5:{
name:"CAM 05 / WEST HALL",
type:"hall",
monsterPositions:{
1:{x:62,y:60},
2:{x:40,y:58},
3:{x:75,y:59},
4:{x:50,y:60}
}
},
6:{
name:"CAM 06 / EAST HALL",
type:"hall",
monsterPositions:{
1:{x:40,y:60},
2:{x:64,y:58},
3:{x:75,y:59},
4:{x:50,y:59}
}
},
7:{
name:"CAM 07 / BACK ROOM",
type:"back",
monsterPositions:{
1:{x:54,y:59},
2:{x:72,y:59},
3:{x:33,y:58},
4:{x:62,y:60}
}
},
8:{
name:"CAM 08 / STAGE",
type:"stage",
monsterPositions:{
1:{x:48,y:57},
2:{x:64,y:58},
3:{x:35,y:58},
4:{x:78,y:58}
}
},
9:{
name:"CAM 09 / LOADING",
type:"loading",
monsterPositions:{
1:{x:35,y:60},
2:{x:67,y:60},
3:{x:52,y:58},
4:{x:80,y:59}
}
}
};
/* =========================
MONSTERS
========================= */
const monsters = [
{
id:1,
name:"MARA",
style:"camA",
path:[1,2,3,5,"LEFT"],
position:0,
aggression:[1,2,3,4,5],
moveBase:21000,
lastMove:0
},
{
id:2,
name:"BRICK",
style:"camB",
path:[8,4,6,7,"RIGHT"],
position:0,
aggression:[1,2,3,4,5],
moveBase:26000,
lastMove:0
},
{
id:3,
name:"VEX",
style:"camC",
path:[2,3,4,6,7,"RIGHT"],
position:0,
aggression:[2,3,4,5],
moveBase:30000,
lastMove:0
},
{
id:4,
name:"NULL",
style:"camD",
path:[7,9,5,"LEFT"],
position:0,
aggression:[3,4,5],
moveBase:34000,
lastMove:0
}
];
/* =========================
AUDIO ENGINE
========================= */
let audioCtx=null;
function initAudio(){
if(!audioCtx){
audioCtx =
new (window.AudioContext ||
window.webkitAudioContext)();
}
if(audioCtx.state==="suspended"){
audioCtx.resume();
}
}
function tone(freq,duration,type="sine",volume=.04){
try{
initAudio();
const osc=audioCtx.createOscillator();
const gain=audioCtx.createGain();
osc.type=type;
osc.frequency.value=freq;
gain.gain.setValueAtTime(volume,audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(
.001,
audioCtx.currentTime+duration
);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime+duration);
}catch(e){}
}
function noise(duration=.15,volume=.04){
try{
initAudio();
const buffer=audioCtx.createBuffer(
1,
audioCtx.sampleRate*duration,
audioCtx.sampleRate
);
const data=buffer.getChannelData(0);
for(let i=0;i<data.length;i++){
data[i]=(Math.random()*2-1);
}
const source=audioCtx.createBufferSource();
const gain=audioCtx.createGain();
source.buffer=buffer;
gain.gain.value=volume;
source.connect(gain);
gain.connect(audioCtx.destination);
source.start();
}catch(e){}
}
function soundDoor(){
tone(80,.15,"square",.07);
setTimeout(()=>{
tone(45,.2,"sawtooth",.04);
},70);
}
function soundLight(){
tone(420,.06,"square",.025);
}
function soundCamera(){
noise(.18,.05);
tone(120,.12,"square",.025);
}
function soundStep(){
tone(55,.12,"triangle",.04);
}
function soundWarning(){
tone(70,.3,"sawtooth",.04);
setTimeout(()=>{
tone(55,.3,"sawtooth",.04);
},320);
}
function soundJumpscare(){
noise(.8,.22);
tone(45,.8,"sawtooth",.18);
setTimeout(()=>{
tone(850,.15,"square",.12);
},100);
}
/* =========================
NIGHT START
========================= */
function startNight(n){
initAudio();
state.night=n;
state.hour=0;
state.minute=0;
state.power=100;
state.cameraOpen=false;
state.camera=1;
state.cameraSide=0;
state.leftDoor=false;
state.rightDoor=false;
state.leftLight=false;
state.rightLight=false;
state.gameOver=false;
state.running=true;
monsters.forEach((m,i)=>{
m.position=0;
m.lastMove=performance.now()+i*3000;
});
document.getElementById("menu")
.classList.add("hidden");
document.getElementById("endScreen")
.classList.remove("show");
document.getElementById("hud")
.classList.remove("hidden");
document.getElementById("office")
.classList.remove("hidden");
document.getElementById("camera")
.classList.remove("active");
document.getElementById("nightNumber")
.textContent=
String(n).padStart(2,"0");
updateOffice();
state.lastTime=performance.now();
requestAnimationFrame(gameLoop);
}
/* =========================
NIGHT DIFFICULTY
========================= */
function difficulty(){
return {
timeScale:
[1.0,.95,.9,.84,.78][state.night-1],
aggression:
[0.45,.65,.82,1.0,1.18][state.night-1],
powerDrain:
[0.75,.9,1.05,1.2,1.4][state.night-1]
};
}
/* =========================
GAME LOOP
========================= */
function gameLoop(now){
if(!state.running || state.gameOver)return;
const dt=now-state.lastTime;
state.lastTime=now;
state.elapsed+=dt;
updateClock();
updatePower(dt);
updateMonsters(now);
updateOffice();
if(state.power<=0){
state.power=0;
state.leftDoor=false;
state.rightDoor=false;
state.leftLight=false;
state.rightLight=false;
closeCamera();
setTimeout(()=>{
loseGame("POWER FAILURE");
},3500);
return;
}
requestAnimationFrame(gameLoop);
}
/* =========================
CLOCK
========================= */
function updateClock(){
/*
A full night lasts about 9 minutes.
Each hour = 90 seconds.
Night 1 is deliberately slower.
*/
const d=difficulty();
const hourLength=
90000*d.timeScale;
state.hour=
Math.floor(state.elapsed/hourLength);
if(state.hour>=6){
winNight();
return;
}
const partial=
state.elapsed%hourLength;
state.minute=
Math.floor(
partial/hourLength*60
);
let text;
if(state.hour===0){
text="12 AM";
}else{
text=state.hour+" AM";
}
document.getElementById("clock")
.textContent=text;
}
/* =========================
POWER
========================= */
function updatePower(dt){
const d=difficulty();
let drain=.0022*d.powerDrain;
if(state.cameraOpen)
drain+=.0013*d.powerDrain;
if(state.leftDoor)
drain+=.0028*d.powerDrain;
if(state.rightDoor)
drain+=.0028*d.powerDrain;
if(state.leftLight)
drain+=.0018*d.powerDrain;
if(state.rightLight)
drain+=.0018*d.powerDrain;
state.power-=drain*(dt/16.67);
if(state.power<0)
state.power=0;
document.getElementById("power")
.textContent=
Math.ceil(state.power)+"%";
document.getElementById("powerFill")
.style.width=
Math.max(0,state.power)+"%";
}
/* =========================
MONSTER AI
========================= */
function updateMonsters(now){
const d=difficulty();
monsters.forEach(m=>{
const aggression=
d.aggression *
(m.aggression.includes(state.night)?1:.45);
const interval=
m.moveBase/
Math.max(.25,aggression);
if(now-m.lastMove<interval)
return;
m.lastMove=now;
/*
Random chance to move.
Higher nights = more likely.
*/
const chance=
.30+
aggression*.10;
if(Math.random()>chance)
return;
if(m.position<m.path.length-1){
/*
Different monsters don't move together:
each has its own timer + random chance.
*/
m.position++;
soundStep();
if(
m.path[m.position]==="LEFT" ||
m.path[m.position]==="RIGHT"
){
setTimeout(()=>{
checkAttack(m);
},800+Math.random()*1400);
}
}
});
}
/* =========================
ATTACK CHECK
========================= */
function checkAttack(m){
if(state.gameOver)return;
const destination=
m.path[m.position];
if(destination==="LEFT"){
if(state.leftDoor){
/*
Closed door blocks attack.
Monster retreats after a short time.
*/
soundDoor();
if(Math.random()<.7){
m.position=Math.max(0,m.position-1);
}
}else{
loseGame(m.name);
}
}
if(destination==="RIGHT"){
if(state.rightDoor){
soundDoor();
if(Math.random()<.7){
m.position=Math.max(0,m.position-1);
}
}else{
loseGame(m.name);
}
}
}
/* =========================
DOORS
========================= */
function toggleDoor(side){
if(state.gameOver)return;
initAudio();
if(side==="left"){
state.leftDoor=
!state.leftDoor;
}else{
state.rightDoor=
!state.rightDoor;
}
soundDoor();
updateOffice();
}
/* =========================
LIGHTS
========================= */
function toggleLight(side){
if(state.gameOver)return;
initAudio();
if(side==="left"){
state.leftLight=
!state.leftLight;
}else{
state.rightLight=
!state.rightLight;
}
soundLight();
updateOffice();
}
/* =========================
OFFICE UPDATE
========================= */
function updateOffice(){
const ld=
document.getElementById("leftDoor");
const rd=
document.getElementById("rightDoor");
const lw=
document.getElementById("leftWindow");
const rw=
document.getElementById("rightWindow");
ld.classList.toggle(
"closed",
state.leftDoor
);
ld.classList.toggle(
"open",
!state.leftDoor
);
rd.classList.toggle(
"closed",
state.rightDoor
);
rd.classList.toggle(
"open",
!state.rightDoor
);
const leftMonster=
monsters.find(m=>
m.path[m.position]==="LEFT"
);
const rightMonster=
monsters.find(m=>
m.path[m.position]==="RIGHT"
);
document.getElementById("leftMonster")
.classList.toggle(
"visible",
!!leftMonster
);
document.getElementById("rightMonster")
.classList.toggle(
"visible",
!!rightMonster
);
lw.classList.toggle(
"on",
state.leftLight
);
rw.classList.toggle(
"on",
state.rightLight
);
const lBtn=
document.getElementById("leftDoorBtn");
const rBtn=
document.getElementById("rightDoorBtn");
const llBtn=
document.getElementById("leftLightBtn");
const rlBtn=
document.getElementById("rightLightBtn");
lBtn.textContent=
state.leftDoor?
"OPEN DOOR":
"CLOSE DOOR";
rBtn.textContent=
state.rightDoor?
"OPEN DOOR":
"CLOSE DOOR";
llBtn.textContent=
state.leftLight?
"LIGHT ON":
"LIGHT OFF";
rlBtn.textContent=
state.rightLight?
"LIGHT ON":
"LIGHT OFF";
lBtn.classList.toggle(
"active",
state.leftDoor
);
rBtn.classList.toggle(
"active",
state.rightDoor
);
llBtn.classList.toggle(
"lightActive",
state.leftLight
);
rlBtn.classList.toggle(
"lightActive",
state.rightLight
);
}
/* =========================
CAMERA
========================= */
function openCamera(){
if(state.gameOver)return;
initAudio();
if(state.power<=0)return;
state.cameraOpen=true;
document.getElementById("camera")
.classList.add("active");
soundCamera();
renderCamera();
}
function closeCamera(){
state.cameraOpen=false;
document.getElementById("camera")
.classList.remove("active");
}
/* =========================
CAMERA SELECTION
========================= */
function selectCam(n){
if(state.gameOver)return;
initAudio();
state.camera=n;
state.cameraSide=0;
soundCamera();
renderCamera();
}
function moveCamera(direction){
state.cameraSide+=direction;
if(state.cameraSide<-1)
state.cameraSide=-1;
if(state.cameraSide>1)
state.cameraSide=1;
const room=
document.getElementById("camRoom");
room.style.left=
(-25 + state.cameraSide*18)+"%";
soundCamera();
}
/* =========================
CAMERA RENDER
========================= */
function renderCamera(){
const data=
cameras[state.camera];
const room=
document.getElementById("camRoom");
const label=
document.getElementById("camLabel");
const name=
document.getElementById("camName");
room.className=
"camRoom "+data.type;
room.style.left="-25%";
state.cameraSide=0;
label.textContent=
data.name;
name.textContent=
data.name;
/*
Make every room visually different.
*/
const objects=
document.getElementById("roomObjects");
objects.innerHTML="";
if(data.type==="showroom"){
createObject(
objects,
"display shelf",
15,40,25,28
);
createObject(
objects,
"display shelf",
60,43,25,25
);
createObject(
objects,
"poster",
42,30,16,15
);
}
if(data.type==="storage"){
createObject(
objects,
"crate",
12,45,20,22
);
createObject(
objects,
"crate",
38,52,18,18
);
createObject(
objects,
"crate",
68,42,22,25
);
}
if(data.type==="workshop"){
createObject(
objects,
"table",
30,50,40,12
);
createObject(
objects,
"machine",
70,35,14,30
);
createObject(
objects,
"cabinet",
8,36,18,35
);
}
if(data.type==="dining"){
createObject(
objects,
"table",
35,50,30,9
);
createObject(
objects,
"chair",
30,57,8,20
);
createObject(
objects,
"chair",
63,57,8,20
);
}
if(data.type==="hall"){
createObject(
objects,
"door",
43,25,14,47
);
createObject(
objects,
"pipe",
20,20,5,55
);
createObject(
objects,
"pipe",
75,20,5,55
);
}
if(data.type==="back"){
createObject(
objects,
"cabinet",
12,32,20,40
);
createObject(
objects,
"cabinet",
70,30,20,42
);
createObject(
objects,
"box",
42,55,15,15
);
}
if(data.type==="stage"){
createObject(
objects,
"curtain",
5,15,25,65
);
createObject(
objects,
"curtain",
70,15,25,65
);
createObject(
objects,
"platform",
30,56,40,10
);
}
if(data.type==="loading"){
createObject(
objects,
"container",
10,35,28,30
);
createObject(
objects,
"container",
58,40,30,27
);
}
/*
Determine whether a monster is actually here.
*/
const active=
monsters.find(m=>
m.path[m.position]===state.camera
);
const visual=
document.getElementById("camMonster");
if(active){
visual.className=
"camMonster "+active.style;
visual.classList.remove("hidden");
const pos=
data.monsterPositions[active.id];
visual.style.left=
pos.x+"%";
visual.style.bottom=
(100-pos.y)+"%";
}else{
visual.classList.add("hidden");
}
updateMap();
}
/* =========================
CAMERA OBJECT
========================= */
function createObject(parent,name,x,y,w,h){
const obj=
document.createElement("div");
obj.className=
"camObject";
obj.style.left=x+"%";
obj.style.top=y+"%";
obj.style.width=w+"%";
obj.style.height=h+"%";
parent.appendChild(obj);
}
/* =========================
MAP UPDATE
========================= */
function updateMap(){
document.querySelectorAll(".mapBtn")
.forEach((btn,i)=>{
const cam=i+1;
btn.classList.toggle(
"current",
cam===state.camera
);
const danger=
monsters.some(m=>
m.path[m.position]===cam
);
btn.classList.toggle(
"danger",
danger
);
});
}
/* =========================
LOSE
========================= */
function loseGame(reason){
if(state.gameOver)return;
state.gameOver=true;
state.running=false;
closeCamera();
soundJumpscare();
const scare=
document.getElementById("jumpscare");
scare.classList.add("show");
setTimeout(()=>{
scare.classList.remove("show");
document.getElementById("office")
.classList.add("hidden");
document.getElementById("hud")
.classList.add("hidden");
const end=
document.getElementById("endScreen");
document.getElementById("endTitle")
.textContent=
"SHIFT FAILED";
document.getElementById("endText")
.innerHTML=
"THE SECURITY SYSTEM LOST CONTACT.<br><br>"+
"CAUSE: "+reason+
"<br><br>NIGHT "+
String(state.night).padStart(2,"0");
end.classList.add("show");
},1600);
}
/* =========================
WIN
========================= */
function winNight(){
if(state.gameOver)return;
state.gameOver=true;
state.running=false;
closeCamera();
document.getElementById("office")
.classList.add("hidden");
document.getElementById("hud")
.classList.add("hidden");
const end=
document.getElementById("endScreen");
document.getElementById("endTitle")
.textContent=
"6 AM";
let message=
"NIGHT "+
String(state.night).padStart(2,"0")+
" COMPLETE";
if(state.night<5){
message+=
"<br><br>"+
"THE NEXT SHIFT WILL BE MORE ACTIVE.";
}else{
message+=
"<br><br>"+
"ALL FIVE NIGHTS SURVIVED.<br>"+
"YOU MADE IT.";
}
document.getElementById("endText")
.innerHTML=message;
end.classList.add("show");
}
/* =========================
NIGHT MENU
========================= */
function showNights(){
document.getElementById("nightSelect")
.classList.toggle("hidden");
}
/* =========================
KEYBOARD
========================= */
document.addEventListener("keydown",e=>{
if(!state.running)return;
if(e.key==="a")
toggleDoor("left");
if(e.key==="d")
toggleDoor("right");
if(e.key==="q")
toggleLight("left");
if(e.key==="e")
toggleLight("right");
if(e.key===" ")
openCamera();
if(e.key==="Escape")
closeCamera();
if(state.cameraOpen){
if(e.key==="ArrowLeft")
moveCamera(-1);
if(e.key==="ArrowRight")
moveCamera(1);
if(e.key>="1" && e.key<="9")
selectCam(Number(e.key));
}
});
/* =========================
PREVENT DOUBLE TAP ZOOM
========================= */
let lastTouch=0;
document.addEventListener(
"touchend",
e=>{
const now=Date.now();
if(now-lastTouch<=300){
e.preventDefault();
}
lastTouch=now;
},
{passive:false}
);
/* =========================
STARTUP
========================= */
updateMap();
</script>
</body>
</html>Game Source: NIGHT SHIFT — Five Nights
Creator: LunarPanda23
Libraries: none
Complexity: complex (3035 lines, 49.7 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: night-shift-five-nights-lunarpanda23" to link back to the original. Then publish at arcadelab.ai/publish.