NIGHT SHIFT — Security Network
by LunarPanda232833 lines49.0 KB
<!DOCTYPE html>
<html lang="he" dir="ltr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<title>NIGHT SHIFT — Security Network</title>
<style>
*{
box-sizing:border-box;
-webkit-tap-highlight-color:transparent;
}
html,body{
margin:0;
width:100%;
height:100%;
overflow:hidden;
background:#050708;
color:#d9e2e2;
font-family:Arial,Helvetica,sans-serif;
}
button{
font:inherit;
cursor:pointer;
user-select:none;
}
:root{
--panel:#111719;
--panel2:#182023;
--line:#637174;
--bright:#dce8e5;
--dim:#8d9b9b;
--red:#c95353;
--red2:#7d2c2c;
--green:#8ebba3;
--yellow:#d1b86c;
}
/* =========================
MENU
========================= */
#menu{
position:fixed;
inset:0;
display:flex;
align-items:center;
justify-content:center;
background:
radial-gradient(circle at center,#172022 0,#080b0c 55%,#030404 100%);
z-index:1000;
}
.menuBox{
width:min(720px,90vw);
border:1px solid #59686a;
background:rgba(11,15,16,.96);
box-shadow:0 0 60px #000;
padding:42px;
position:relative;
}
.menuTop{
border-bottom:1px solid #4f5c5e;
padding-bottom:20px;
margin-bottom:30px;
}
.menuSmall{
color:#81908f;
letter-spacing:5px;
font-size:13px;
}
.menuTitle{
font-size:clamp(42px,8vw,82px);
font-weight:800;
letter-spacing:7px;
margin:12px 0 4px;
}
.menuSub{
color:#8da49a;
letter-spacing:4px;
font-size:13px;
}
.nightSelect{
display:grid;
grid-template-columns:repeat(5,1fr);
gap:8px;
margin:20px 0;
}
.nightSelect button{
min-height:62px;
border:1px solid #4e5d5e;
color:#aebbbb;
background:#12191b;
}
.nightSelect button.unlocked{
border-color:#78908b;
}
.nightSelect button.selected{
border-color:#d5e5df;
box-shadow:inset 0 0 0 2px #263437;
color:white;
}
.nightSelect button.locked{
opacity:.28;
cursor:not-allowed;
}
.mainButtons{
display:grid;
gap:10px;
}
.mainButtons button{
height:62px;
border:1px solid #718080;
background:#172022;
color:#dce7e4;
letter-spacing:3px;
font-weight:bold;
}
.mainButtons button:hover{
background:#202c2e;
}
#continueBtn{
border-color:#8da99b;
}
.menuInfo{
margin-top:25px;
border-top:1px solid #303b3d;
padding-top:15px;
color:#748181;
font-size:12px;
line-height:1.7;
}
/* =========================
GAME
========================= */
#game{
display:none;
position:fixed;
inset:0;
background:#050708;
}
#game.active{
display:block;
}
#office{
position:absolute;
inset:0;
overflow:hidden;
background:
linear-gradient(90deg,
#090d0e 0%,
#151b1d 13%,
#202729 28%,
#151b1d 72%,
#090d0e 87%,
#050708 100%);
}
/* ceiling */
.ceiling{
position:absolute;
left:12%;
right:12%;
top:12%;
height:23%;
background:
linear-gradient(145deg,#202729,#0c1112 65%);
clip-path:polygon(0 0,100% 0,78% 100%,22% 100%);
border-bottom:2px solid #343f40;
}
/* back wall */
.backWall{
position:absolute;
left:18%;
right:18%;
top:34%;
bottom:19%;
background:
repeating-linear-gradient(
90deg,
rgba(255,255,255,.025) 0,
rgba(255,255,255,.025) 2px,
transparent 2px,
transparent 55px
),
linear-gradient(#171e20,#101617);
border-left:2px solid #273133;
border-right:2px solid #273133;
}
/* floor */
.floor{
position:absolute;
left:0;
right:0;
bottom:0;
height:31%;
background:
repeating-linear-gradient(90deg,
transparent 0,
transparent 70px,
rgba(150,170,170,.09) 71px,
transparent 73px),
repeating-linear-gradient(0deg,
transparent 0,
transparent 68px,
rgba(150,170,170,.07) 69px,
transparent 71px),
linear-gradient(#11191a,#070a0b);
transform:perspective(500px) rotateX(9deg);
transform-origin:bottom;
}
/* top HUD */
.hud{
position:absolute;
top:0;
left:0;
right:0;
height:92px;
background:rgba(5,8,9,.94);
border-bottom:2px solid #475254;
display:flex;
align-items:center;
justify-content:space-between;
padding:15px 28px;
z-index:30;
}
.hudLeft small,
.hudRight small{
display:block;
color:#879594;
letter-spacing:4px;
font-size:11px;
}
.time{
font-size:40px;
font-weight:800;
letter-spacing:2px;
}
.power{
text-align:right;
}
.powerNumber{
font-size:30px;
font-weight:bold;
}
.powerBar{
width:190px;
height:7px;
border:1px solid #687978;
margin-top:6px;
}
.powerFill{
width:100%;
height:100%;
background:#a8c8b8;
}
/* =========================
DOORS
========================= */
.door{
position:absolute;
top:39%;
bottom:25%;
width:15%;
min-width:115px;
background:#070a0b;
border:4px solid #566264;
box-shadow:inset 0 0 30px #000;
z-index:12;
}
.door.left{
left:2%;
}
.door.right{
right:2%;
}
.doorFrame{
position:absolute;
inset:5px;
border:2px solid #242e30;
}
.doorWindow{
position:absolute;
left:12%;
right:12%;
top:7%;
height:45%;
border:4px solid #687879;
background:
linear-gradient(rgba(15,22,23,.9),rgba(2,4,5,.98));
overflow:hidden;
}
.doorWindow .windowGlow{
position:absolute;
inset:0;
background:radial-gradient(circle,#aabda9 0,rgba(110,145,125,.22) 30%,transparent 68%);
opacity:0;
transition:.15s;
}
.door.lightOn .windowGlow{
opacity:.9;
}
.door.lightOn .doorWindow{
background:#283331;
}
.door.lightOn .doorWindow::after{
content:"";
position:absolute;
inset:0;
background:linear-gradient(90deg,rgba(220,240,220,.2),transparent,rgba(220,240,220,.12));
}
.doorClosed{
position:absolute;
left:0;
right:0;
top:0;
height:0;
background:
repeating-linear-gradient(90deg,#151c1d 0,#151c1d 18px,#0c1112 20px);
border-bottom:3px solid #737f80;
overflow:hidden;
transition:height .28s ease;
z-index:5;
}
.door.closed .doorClosed{
height:100%;
}
.doorLabel{
position:absolute;
bottom:10px;
width:100%;
text-align:center;
color:#728080;
font-size:10px;
letter-spacing:3px;
}
.creatureAtDoor{
position:absolute;
left:50%;
bottom:-5px;
transform:translateX(-50%);
opacity:0;
transition:.12s;
z-index:3;
}
.door.lightOn .creatureAtDoor{
opacity:1;
}
/* =========================
CREATURE CSS MODELS
========================= */
.monster{
position:relative;
width:70px;
height:130px;
filter:drop-shadow(0 5px 8px #000);
}
.monster .head{
position:absolute;
width:65px;
height:67px;
left:3px;
top:0;
border-radius:35% 35% 42% 42%;
background:#222;
border:3px solid #0b0c0c;
}
.monster .eye{
position:absolute;
width:9px;
height:13px;
background:#d8e6b4;
border-radius:50%;
top:25px;
box-shadow:0 0 9px #d8e6b4;
}
.monster .eye.a{left:16px}
.monster .eye.b{right:16px}
.monster .mouth{
position:absolute;
left:14px;
right:14px;
bottom:8px;
height:13px;
background:#080909;
border-radius:50%;
}
.monster .body{
position:absolute;
left:14px;
top:58px;
width:43px;
height:72px;
background:#272e2e;
border:3px solid #0b0c0c;
border-radius:16px 16px 4px 4px;
}
.monster .arm{
position:absolute;
top:68px;
width:13px;
height:55px;
background:#202727;
border:3px solid #0b0c0c;
}
.monster .arm.a{
left:3px;
transform:rotate(10deg);
}
.monster .arm.b{
right:3px;
transform:rotate(-10deg);
}
.monster.red .head,
.monster.red .body{background:#4b2727}
.monster.blue .head,
.monster.blue .body{background:#263d47}
.monster.gold .head,
.monster.gold .body{background:#4b452b}
.monster.white .head,
.monster.white .body{background:#404848}
.monster.tall{
transform:scaleY(1.2);
}
.monster.round .head{
border-radius:48%;
}
.monster.long .head{
width:56px;
left:7px;
border-radius:42% 42% 32% 32%;
}
.monster.long .body{
width:36px;
left:18px;
}
/* =========================
CONTROLS
========================= */
.control{
position:absolute;
top:52%;
width:118px;
padding:8px;
background:rgba(9,13,14,.96);
border:2px solid #566466;
z-index:25;
}
.control.left{
left:calc(2% + 8px);
}
.control.right{
right:calc(2% + 8px);
}
.controlTitle{
text-align:center;
color:#879493;
font-size:10px;
letter-spacing:3px;
margin-bottom:7px;
}
.control button{
width:100%;
min-height:47px;
margin:3px 0;
background:#131b1d;
border:1px solid #657476;
color:#dce5e3;
font-weight:bold;
font-size:11px;
}
.control button.active{
background:#703131;
border-color:#d05c5c;
}
.control button.lightActive{
background:#566347;
border-color:#b3c58f;
color:white;
}
/* =========================
DESK
========================= */
.desk{
position:absolute;
left:50%;
bottom:13%;
transform:translateX(-50%);
width:300px;
height:100px;
background:#0a0e0f;
border:2px solid #343e40;
z-index:15;
box-shadow:0 -12px 30px #000;
}
.monitor{
position:absolute;
left:50%;
top:-55px;
transform:translateX(-50%);
width:100px;
height:65px;
background:#080c0d;
border:3px solid #4b595a;
display:flex;
align-items:center;
justify-content:center;
font-size:10px;
color:#879494;
letter-spacing:2px;
}
/* =========================
CAMERA BAR
========================= */
.cameraBar{
position:absolute;
left:50%;
bottom:0;
transform:translateX(-50%);
width:min(700px,86%);
height:86px;
background:rgba(9,13,14,.98);
border:2px solid #536163;
border-bottom:0;
z-index:100;
display:grid;
grid-template-columns:70px 1fr 70px;
align-items:center;
padding:8px 14px;
}
.cameraBar button{
height:54px;
background:#121a1c;
border:2px solid #617072;
color:#d9e3e1;
font-size:30px;
}
.cameraBar button:active{
transform:scale(.96);
}
.cameraName{
text-align:center;
font-weight:bold;
letter-spacing:5px;
font-size:13px;
}
.cameraHint{
color:#718080;
font-size:9px;
letter-spacing:2px;
text-align:center;
margin-top:5px;
}
/* =========================
CAMERA SCREEN
========================= */
#cameraScreen{
display:none;
position:absolute;
inset:0;
z-index:80;
background:#101516;
}
#cameraScreen.open{
display:block;
}
.cameraScene{
position:absolute;
left:0;
right:0;
top:92px;
bottom:86px;
overflow:hidden;
background:#101718;
}
.camCeiling{
position:absolute;
left:0;
right:0;
top:0;
height:22%;
background:linear-gradient(#1e2829,#0d1213);
border-bottom:2px solid #465254;
}
.camRoom{
position:absolute;
left:5%;
right:5%;
top:20%;
bottom:0;
border:2px solid #354143;
background:
repeating-linear-gradient(
90deg,
transparent 0,
transparent 70px,
rgba(190,200,200,.045) 71px,
transparent 73px
),
linear-gradient(135deg,#222b2c,#111718 65%);
}
.camFloor{
position:absolute;
left:0;
right:0;
bottom:0;
height:38%;
background:
repeating-linear-gradient(90deg,
transparent 0,
transparent 60px,
rgba(190,200,200,.07) 61px,
transparent 63px),
repeating-linear-gradient(0deg,
transparent 0,
transparent 55px,
rgba(190,200,200,.06) 56px,
transparent 58px);
transform:perspective(400px) rotateX(15deg);
transform-origin:bottom;
}
.camObject{
position:absolute;
border:2px solid #4b595a;
background:#101516;
}
.camObject.one{
width:25%;
height:24%;
left:10%;
bottom:25%;
}
.camObject.two{
width:17%;
height:32%;
right:13%;
bottom:22%;
}
.camObject.three{
width:32%;
height:11%;
left:34%;
top:34%;
}
.camLabel{
position:absolute;
left:18px;
top:18px;
border:1px solid #697878;
background:rgba(6,9,10,.85);
padding:9px 14px;
font-size:12px;
letter-spacing:3px;
z-index:10;
}
.signal{
position:absolute;
right:18px;
top:18px;
color:#a6bbb1;
font-size:11px;
letter-spacing:2px;
}
.static{
position:absolute;
inset:0;
pointer-events:none;
opacity:.16;
background:
repeating-linear-gradient(
0deg,
transparent 0,
transparent 2px,
rgba(255,255,255,.12) 3px,
transparent 4px
);
animation:staticMove .12s infinite;
}
@keyframes staticMove{
from{transform:translateY(0)}
to{transform:translateY(4px)}
}
.camMonster{
position:absolute;
z-index:8;
left:50%;
bottom:19%;
transform:translateX(-50%) scale(1.35);
}
.camMonster.hidden{
display:none;
}
/* =========================
MAP
========================= */
.map{
position:absolute;
right:20px;
top:120px;
width:270px;
background:rgba(7,10,11,.95);
border:2px solid #5a696b;
padding:14px;
z-index:20;
}
.mapTitle{
color:#9eacab;
font-size:11px;
letter-spacing:4px;
margin-bottom:10px;
}
.mapGrid{
display:grid;
grid-template-columns:1fr 1fr;
gap:7px;
}
.map button{
min-height:54px;
background:#11191a;
border:2px solid #566567;
color:#ccd7d5;
font-size:10px;
line-height:1.2;
}
.map button.current{
border-color:#e2ece9;
box-shadow:0 0 10px rgba(210,230,225,.25);
}
.map button.danger{
border-color:#9b3d3d;
color:#e18d8d;
}
.you{
margin-top:11px;
padding:8px;
border-top:1px solid #364244;
color:#9cb4aa;
font-size:10px;
letter-spacing:2px;
}
.closeCams{
position:absolute;
left:20px;
top:120px;
z-index:30;
padding:12px 18px;
background:#101819;
border:2px solid #647274;
color:#dce5e3;
font-weight:bold;
}
/* =========================
STATUS / ALERT
========================= */
.status{
position:absolute;
top:105px;
left:20px;
z-index:50;
font-size:10px;
color:#778686;
letter-spacing:2px;
}
.alert{
position:absolute;
top:130px;
left:50%;
transform:translateX(-50%);
z-index:200;
padding:10px 20px;
border:1px solid #8b4a4a;
background:rgba(48,20,20,.95);
color:#dfaaaa;
opacity:0;
pointer-events:none;
transition:.2s;
}
.alert.show{
opacity:1;
}
/* =========================
JUMPSCARE
========================= */
#jumpscare{
position:fixed;
inset:0;
display:none;
z-index:10000;
background:#050505;
align-items:center;
justify-content:center;
overflow:hidden;
}
#jumpscare.show{
display:flex;
animation:shake .07s infinite;
}
.scareMonster{
transform:scale(4);
}
.scareFlash{
position:absolute;
inset:0;
background:#eee;
opacity:0;
animation:flash .7s;
}
.scareText{
position:absolute;
bottom:30px;
color:#fff;
font-size:11px;
letter-spacing:5px;
opacity:.65;
}
@keyframes shake{
0%{transform:translate(0)}
25%{transform:translate(-12px,8px)}
50%{transform:translate(9px,-7px)}
75%{transform:translate(-7px,-9px)}
100%{transform:translate(10px,5px)}
}
@keyframes flash{
0%{opacity:0}
15%{opacity:.9}
30%{opacity:.05}
45%{opacity:.8}
100%{opacity:0}
}
/* =========================
NIGHT COMPLETE
========================= */
#nightComplete{
position:fixed;
inset:0;
display:none;
align-items:center;
justify-content:center;
background:rgba(3,5,6,.96);
z-index:9000;
}
.completeBox{
width:min(600px,90vw);
border:2px solid #617072;
padding:38px;
background:#0c1112;
text-align:center;
}
.completeBox h1{
font-size:48px;
letter-spacing:6px;
margin:0 0 10px;
}
.completeBox p{
color:#899797;
letter-spacing:2px;
}
.completeBox button{
margin-top:20px;
width:100%;
height:55px;
background:#182223;
border:1px solid #778687;
color:#e2e9e7;
}
/* =========================
RESPONSIVE
========================= */
@media(max-width:650px){
.hud{
height:78px;
padding:9px 14px;
}
.time{
font-size:31px;
}
.powerNumber{
font-size:22px;
}
.powerBar{
width:110px;
}
.door{
width:20%;
min-width:90px;
top:37%;
bottom:27%;
}
.control{
width:95px;
padding:5px;
top:54%;
}
.control.left{
left:1%;
}
.control.right{
right:1%;
}
.control button{
min-height:42px;
font-size:9px;
}
.doorWindow{
top:8%;
height:40%;
}
.desk{
width:180px;
height:70px;
bottom:16%;
}
.monitor{
width:75px;
height:48px;
top:-42px;
font-size:8px;
}
.cameraBar{
width:94%;
height:76px;
grid-template-columns:55px 1fr 55px;
}
.cameraBar button{
height:48px;
font-size:24px;
}
.cameraName{
font-size:10px;
letter-spacing:3px;
}
.map{
width:220px;
right:8px;
top:95px;
padding:8px;
}
.map button{
min-height:47px;
font-size:9px;
}
.closeCams{
top:95px;
left:8px;
padding:9px 12px;
font-size:10px;
}
.cameraScene{
top:78px;
bottom:76px;
}
}
@media(max-height:650px){
.door{
top:31%;
bottom:22%;
}
.control{
top:47%;
}
.cameraBar{
height:70px;
}
}
</style>
</head>
<body>
<!-- ================= MENU ================= -->
<div id="menu">
<div class="menuBox">
<div class="menuTop">
<div class="menuSmall">SECURITY NETWORK / ENTERTAINMENT FACILITY</div>
<div class="menuTitle">NIGHT SHIFT</div>
<div class="menuSub">ORIGINAL SURVIVAL HORROR</div>
</div>
<div style="color:#82908f;font-size:11px;letter-spacing:3px">
SELECT NIGHT
</div>
<div class="nightSelect" id="nightSelect">
<button data-night="1">01</button>
<button data-night="2">02</button>
<button data-night="3">03</button>
<button data-night="4">04</button>
<button data-night="5">05</button>
</div>
<div class="mainButtons">
<button id="startBtn">START NIGHT</button>
<button id="continueBtn">CONTINUE</button>
<button id="resetBtn">RESET PROGRESS</button>
</div>
<div class="menuInfo">
Survive from 12 AM to 6 AM.<br>
Watch the cameras. Listen carefully. Check both doors.<br>
Every night becomes more aggressive.
</div>
</div>
</div>
<!-- ================= GAME ================= -->
<div id="game">
<div id="office">
<div class="hud">
<div class="hudLeft">
<small>NIGHT SHIFT / SECURITY NETWORK</small>
<div class="time" id="clock">12 AM</div>
<small>SYSTEM ONLINE</small>
</div>
<div class="hudRight">
<small id="nightLabel">NIGHT 01</small>
<div class="powerNumber"><span id="power">100</span>%</div>
<div class="powerBar">
<div class="powerFill" id="powerFill"></div>
</div>
</div>
</div>
<div class="backWall"></div>
<div class="ceiling"></div>
<div class="floor"></div>
<!-- LEFT DOOR -->
<div class="door left" id="leftDoor">
<div class="doorFrame"></div>
<div class="doorWindow">
<div class="windowGlow"></div>
<div class="creatureAtDoor" id="leftCreature">
<div class="monster blue">
<div class="head">
<div class="eye a"></div>
<div class="eye b"></div>
<div class="mouth"></div>
</div>
<div class="body"></div>
<div class="arm a"></div>
<div class="arm b"></div>
</div>
</div>
</div>
<div class="doorClosed"></div>
</div>
<!-- RIGHT DOOR -->
<div class="door right" id="rightDoor">
<div class="doorFrame"></div>
<div class="doorWindow">
<div class="windowGlow"></div>
<div class="creatureAtDoor" id="rightCreature">
<div class="monster red long">
<div class="head">
<div class="eye a"></div>
<div class="eye b"></div>
<div class="mouth"></div>
</div>
<div class="body"></div>
<div class="arm a"></div>
<div class="arm b"></div>
</div>
</div>
</div>
<div class="doorClosed"></div>
</div>
<!-- LEFT CONTROLS -->
<div class="control left">
<div class="controlTitle">WEST CONTROL</div>
<button id="leftDoorBtn">CLOSE DOOR</button>
<button id="leftLightBtn">LIGHT OFF</button>
</div>
<!-- RIGHT CONTROLS -->
<div class="control right">
<div class="controlTitle">EAST CONTROL</div>
<button id="rightDoorBtn">CLOSE DOOR</button>
<button id="rightLightBtn">LIGHT OFF</button>
</div>
<div class="desk">
<div class="monitor">SECURITY</div>
</div>
<div class="status" id="status">
SYSTEM READY / LISTEN FOR MOVEMENT
</div>
<div class="alert" id="alert">
WARNING
</div>
<!-- CAMERA BAR -->
<div class="cameraBar">
<button id="prevCam">◀</button>
<div>
<div class="cameraName" id="cameraName">CAM 01 / SHOWROOM</div>
<div class="cameraHint">
CAMERA SYSTEM / LEFT & RIGHT TO CHANGE VIEW
</div>
</div>
<button id="nextCam">▶</button>
</div>
</div>
<!-- CAMERA SCREEN -->
<div id="cameraScreen">
<div class="cameraScene">
<div class="camCeiling"></div>
<div class="camRoom">
<div class="camObject one"></div>
<div class="camObject two"></div>
<div class="camObject three"></div>
<div class="camFloor"></div>
<div class="camMonster hidden" id="camMonster">
<div class="monster gold" id="cameraMonsterVisual">
<div class="head">
<div class="eye a"></div>
<div class="eye b"></div>
<div class="mouth"></div>
</div>
<div class="body"></div>
<div class="arm a"></div>
<div class="arm b"></div>
</div>
</div>
</div>
<div class="camLabel" id="camLabel">
CAM 01 / SHOWROOM
</div>
<div class="signal">
SIGNAL ● STABLE
</div>
<div class="static"></div>
</div>
<!-- MAP -->
<div class="map">
<div class="mapTitle">FACILITY MAP</div>
<div class="mapGrid" id="mapGrid">
<button data-cam="2">CAM 02<br>STORAGE</button>
<button data-cam="1">CAM 01<br>SHOWROOM</button>
<button data-cam="4">CAM 04<br>DINING</button>
<button data-cam="3">CAM 03<br>WORKSHOP</button>
<button data-cam="6">CAM 06<br>EAST HALL</button>
<button data-cam="5">CAM 05<br>WEST HALL</button>
<button data-cam="8">CAM 08<br>STAGE</button>
<button data-cam="7">CAM 07<br>BACK ROOM</button>
<button data-cam="9">CAM 09<br>LOADING</button>
</div>
<div class="you">
● YOU — SECURITY OFFICE
</div>
</div>
<button class="closeCams" id="closeCams">
CLOSE CAMERAS
</button>
</div>
</div>
<!-- ================= JUMPSCARE ================= -->
<div id="jumpscare">
<div class="scareMonster">
<div class="monster red tall">
<div class="head">
<div class="eye a"></div>
<div class="eye b"></div>
<div class="mouth"></div>
</div>
<div class="body"></div>
<div class="arm a"></div>
<div class="arm b"></div>
</div>
</div>
<div class="scareFlash"></div>
<div class="scareText">
SIGNAL LOST
</div>
</div>
<!-- ================= NIGHT COMPLETE ================= -->
<div id="nightComplete">
<div class="completeBox">
<h1>6 AM</h1>
<p id="completeText">
NIGHT COMPLETE
</p>
<button id="nextNightBtn">
CONTINUE TO NEXT NIGHT
</button>
<button id="menuAfterNight">
RETURN TO MENU
</button>
</div>
</div>
<script>
/* =========================================================
NIGHT SHIFT
COMPLETE GAME ENGINE
========================================================= */
const CAMERAS = [
{
id:1,
name:"CAM 01 / SHOWROOM",
zone:"showroom"
},
{
id:2,
name:"CAM 02 / STORAGE",
zone:"storage"
},
{
id:3,
name:"CAM 03 / WORKSHOP",
zone:"workshop"
},
{
id:4,
name:"CAM 04 / DINING",
zone:"dining"
},
{
id:5,
name:"CAM 05 / WEST HALL",
zone:"westHall"
},
{
id:6,
name:"CAM 06 / EAST HALL",
zone:"eastHall"
},
{
id:7,
name:"CAM 07 / BACK ROOM",
zone:"backRoom"
},
{
id:8,
name:"CAM 08 / STAGE",
zone:"stage"
},
{
id:9,
name:"CAM 09 / LOADING",
zone:"loading"
}
];
/* ---------------------------------------------------------
FOUR DIFFERENT CHARACTERS
--------------------------------------------------------- */
const characters = [
{
id:"MILO",
name:"MILO",
side:"left",
color:"blue",
route:[8,7,2,5,"LEFT_DOOR"],
position:0,
aggression:1
},
{
id:"RAVEN",
name:"RAVEN",
side:"right",
color:"red",
route:[1,3,6,"RIGHT_DOOR"],
position:0,
aggression:1
},
{
id:"BRASS",
name:"BRASS",
side:"left",
color:"gold",
route:[4,2,5,"LEFT_DOOR"],
position:0,
aggression:1
},
{
id:"NOVA",
name:"NOVA",
side:"right",
color:"white",
route:[9,7,6,"RIGHT_DOOR"],
position:0,
aggression:1
}
];
/* ---------------------------------------------------------
STATE
--------------------------------------------------------- */
let selectedNight = 1;
let currentNight = Number(localStorage.getItem("nightCurrent")) || 1;
let highestNight = Number(localStorage.getItem("nightUnlocked")) || 1;
let running = false;
let cameraOpen = false;
let currentCam = 1;
let power = 100;
let hour = 0;
let minute = 0;
let gameSeconds = 0;
let nightTimer = null;
let aiTimer = null;
let powerTimer = null;
let soundContext = null;
let leftDoorClosed = false;
let rightDoorClosed = false;
let leftLight = false;
let rightLight = false;
/* =========================================================
ELEMENTS
========================================================= */
const menu = document.getElementById("menu");
const game = document.getElementById("game");
const clock = document.getElementById("clock");
const powerText = document.getElementById("power");
const powerFill = document.getElementById("powerFill");
const nightLabel = document.getElementById("nightLabel");
const leftDoor = document.getElementById("leftDoor");
const rightDoor = document.getElementById("rightDoor");
const leftCreature = document.getElementById("leftCreature");
const rightCreature = document.getElementById("rightCreature");
const leftDoorBtn = document.getElementById("leftDoorBtn");
const rightDoorBtn = document.getElementById("rightDoorBtn");
const leftLightBtn = document.getElementById("leftLightBtn");
const rightLightBtn = document.getElementById("rightLightBtn");
const cameraScreen = document.getElementById("cameraScreen");
const cameraName = document.getElementById("cameraName");
const camLabel = document.getElementById("camLabel");
const camMonster = document.getElementById("camMonster");
const closeCams = document.getElementById("closeCams");
const alertBox = document.getElementById("alert");
const statusText = document.getElementById("status");
const jumpscare = document.getElementById("jumpscare");
const nightComplete = document.getElementById("nightComplete");
const completeText = document.getElementById("completeText");
const nextNightBtn = document.getElementById("nextNightBtn");
/* =========================================================
MENU
========================================================= */
function refreshMenu(){
document.querySelectorAll(".nightSelect button").forEach(btn=>{
const n = Number(btn.dataset.night);
btn.classList.remove("unlocked","selected","locked");
if(n <= highestNight){
btn.classList.add("unlocked");
}else{
btn.classList.add("locked");
}
if(n === selectedNight){
btn.classList.add("selected");
}
});
document.getElementById("continueBtn").disabled =
currentNight > 5;
}
document.querySelectorAll(".nightSelect button").forEach(btn=>{
btn.addEventListener("click",()=>{
const n = Number(btn.dataset.night);
if(n <= highestNight){
selectedNight = n;
refreshMenu();
}
});
});
document.getElementById("startBtn").onclick=()=>{
if(selectedNight <= highestNight){
startNight(selectedNight);
}
};
document.getElementById("continueBtn").onclick=()=>{
const n = Math.min(5,Math.max(1,currentNight));
startNight(n);
};
document.getElementById("resetBtn").onclick=()=>{
localStorage.removeItem("nightUnlocked");
localStorage.removeItem("nightCurrent");
highestNight=1;
currentNight=1;
selectedNight=1;
refreshMenu();
};
/* =========================================================
AUDIO ENGINE
No external files required.
========================================================= */
function initAudio(){
if(!soundContext){
soundContext =
new (window.AudioContext ||
window.webkitAudioContext)();
}
if(soundContext.state === "suspended"){
soundContext.resume();
}
}
function tone(freq,duration,type="sine",volume=.04){
if(!soundContext)return;
const osc=soundContext.createOscillator();
const gain=soundContext.createGain();
osc.type=type;
osc.frequency.value=freq;
gain.gain.setValueAtTime(volume,soundContext.currentTime);
gain.gain.exponentialRampToValueAtTime(
.001,
soundContext.currentTime+duration
);
osc.connect(gain);
gain.connect(soundContext.destination);
osc.start();
osc.stop(soundContext.currentTime+duration);
}
function doorSound(){
tone(90,.12,"square",.07);
setTimeout(()=>{
tone(55,.16,"square",.04);
},80);
}
function lightSound(){
tone(300,.06,"triangle",.025);
}
function cameraSound(){
tone(700,.05,"square",.025);
setTimeout(()=>{
tone(450,.06,"square",.018);
},60);
}
function movementSound(){
tone(80,.08,"sawtooth",.025);
setTimeout(()=>{
tone(62,.1,"sawtooth",.018);
},100);
}
function scareSound(){
if(!soundContext)return;
const osc=soundContext.createOscillator();
const gain=soundContext.createGain();
osc.type="sawtooth";
osc.frequency.setValueAtTime(
80,
soundContext.currentTime
);
osc.frequency.exponentialRampToValueAtTime(
900,
soundContext.currentTime+.5
);
gain.gain.setValueAtTime(
.001,
soundContext.currentTime
);
gain.gain.linearRampToValueAtTime(
.28,
soundContext.currentTime+.08
);
gain.gain.exponentialRampToValueAtTime(
.001,
soundContext.currentTime+.9
);
osc.connect(gain);
gain.connect(soundContext.destination);
osc.start();
osc.stop(soundContext.currentTime+1);
}
/* =========================================================
START NIGHT
========================================================= */
function startNight(n){
initAudio();
if(n > highestNight){
return;
}
currentNight=n;
localStorage.setItem(
"nightCurrent",
String(n)
);
running=true;
menu.style.display="none";
nightComplete.style.display="none";
jumpscare.classList.remove("show");
game.classList.add("active");
resetGameState();
nightLabel.textContent =
"NIGHT " + String(n).padStart(2,"0");
statusText.textContent =
"SYSTEM READY / LISTEN FOR MOVEMENT";
cameraOpen=false;
cameraScreen.classList.remove("open");
startTimers();
}
/* =========================================================
RESET STATE
========================================================= */
function resetGameState(){
power=100;
hour=0;
minute=0;
gameSeconds=0;
currentCam=1;
leftDoorClosed=false;
rightDoorClosed=false;
leftLight=false;
rightLight=false;
characters.forEach((c,index)=>{
c.position=0;
c.aggression =
currentNight +
index*.15;
});
updateOffice();
updateClock();
updatePower();
updateCamera();
}
/* =========================================================
TIMERS
========================================================= */
function startTimers(){
stopTimers();
/*
One game hour = 38 seconds.
Full night = 3 minutes 48 seconds.
Long enough to actually watch cameras.
*/
nightTimer=setInterval(()=>{
if(!running)return;
gameSeconds++;
if(gameSeconds % 38 === 0){
hour++;
if(hour >= 6){
winNight();
return;
}
}
updateClock();
},1000);
aiTimer=setInterval(()=>{
if(running){
moveCharacters();
}
},7000);
powerTimer=setInterval(()=>{
if(running){
consumePower();
}
},1000);
}
/* =========================================================
STOP
========================================================= */
function stopTimers(){
clearInterval(nightTimer);
clearInterval(aiTimer);
clearInterval(powerTimer);
}
/* =========================================================
CLOCK
========================================================= */
function updateClock(){
let displayHour;
if(hour===0){
displayHour=12;
}else{
displayHour=hour;
}
clock.textContent =
displayHour + " AM";
}
/* =========================================================
POWER
========================================================= */
function consumePower(){
let drain=.035;
if(leftDoorClosed) drain+=.035;
if(rightDoorClosed) drain+=.035;
if(leftLight) drain+=.025;
if(rightLight) drain+=.025;
if(cameraOpen) drain+=.025;
drain += currentNight*.004;
power-=drain;
if(power<0)power=0;
updatePower();
if(power<=0){
blackout();
}
}
function updatePower(){
powerText.textContent =
Math.max(0,Math.floor(power));
powerFill.style.width =
Math.max(0,power)+"%";
if(power<20){
powerFill.style.background="#b64d4d";
}else if(power<40){
powerFill.style.background="#b6a15c";
}else{
powerFill.style.background="#a8c8b8";
}
}
/* =========================================================
DOORS
========================================================= */
function toggleDoor(side){
if(!running)return;
if(power<=0)return;
initAudio();
if(side==="left"){
leftDoorClosed=!leftDoorClosed;
}else{
rightDoorClosed=!rightDoorClosed;
}
doorSound();
updateOffice();
}
function toggleLight(side){
if(!running)return;
if(power<=0)return;
initAudio();
if(side==="left"){
leftLight=!leftLight;
}else{
rightLight=!rightLight;
}
lightSound();
updateOffice();
}
leftDoorBtn.onclick=()=>toggleDoor("left");
rightDoorBtn.onclick=()=>toggleDoor("right");
leftLightBtn.onclick=()=>toggleLight("left");
rightLightBtn.onclick=()=>toggleLight("right");
/* =========================================================
OFFICE UPDATE
========================================================= */
function updateOffice(){
leftDoor.classList.toggle(
"closed",
leftDoorClosed
);
rightDoor.classList.toggle(
"closed",
rightDoorClosed
);
leftDoor.classList.toggle(
"lightOn",
leftLight
);
rightDoor.classList.toggle(
"lightOn",
rightLight
);
leftDoorBtn.textContent =
leftDoorClosed
? "OPEN DOOR"
: "CLOSE DOOR";
rightDoorBtn.textContent =
rightDoorClosed
? "OPEN DOOR"
: "CLOSE DOOR";
leftDoorBtn.classList.toggle(
"active",
leftDoorClosed
);
rightDoorBtn.classList.toggle(
"active",
rightDoorClosed
);
leftLightBtn.textContent =
leftLight
? "LIGHT ON"
: "LIGHT OFF";
rightLightBtn.textContent =
rightLight
? "LIGHT ON"
: "LIGHT OFF";
leftLightBtn.classList.toggle(
"lightActive",
leftLight
);
rightLightBtn.classList.toggle(
"lightActive",
rightLight
);
/*
Only show a creature at the door
when that character has reached it.
*/
const leftDanger =
characters.some(c=>
c.side==="left" &&
c.route[c.position]==="LEFT_DOOR"
);
const rightDanger =
characters.some(c=>
c.side==="right" &&
c.route[c.position]==="RIGHT_DOOR"
);
leftCreature.style.opacity =
leftLight && leftDanger ? "1" : "0";
rightCreature.style.opacity =
rightLight && rightDanger ? "1" : "0";
}
/* =========================================================
CHARACTER AI
========================================================= */
function moveCharacters(){
if(power<=0)return;
characters.forEach((c,index)=>{
const nightFactor =
currentNight * .12;
/*
Night 1 is deliberately slow.
Night 5 is much harder.
*/
const chance =
.22 +
nightFactor +
c.aggression*.04;
if(Math.random() > chance){
return;
}
/*
A character doesn't move every time.
Each has an independent route.
*/
if(c.position < c.route.length-1){
c.position++;
movementSound();
statusText.textContent =
c.name +
" MOVEMENT DETECTED";
showAlert(
c.name +
" MOVED"
);
}else{
/*
Character is at the door.
Closed door sends it back.
Open door causes jumpscare.
*/
const isLeft =
c.route[c.position]==="LEFT_DOOR";
const isRight =
c.route[c.position]==="RIGHT_DOOR";
if(isLeft){
if(leftDoorClosed){
c.position =
Math.max(0,c.position-1);
movementSound();
}else{
triggerJumpscare(c);
}
}
if(isRight){
if(rightDoorClosed){
c.position =
Math.max(0,c.position-1);
movementSound();
}else{
triggerJumpscare(c);
}
}
}
});
updateOffice();
updateCamera();
}
/* =========================================================
CAMERA
========================================================= */
function openCameras(){
if(!running)return;
initAudio();
cameraOpen=true;
cameraScreen.classList.add("open");
cameraSound();
updateCamera();
}
function closeCameras(){
cameraOpen=false;
cameraScreen.classList.remove("open");
cameraSound();
}
document.querySelector(".cameraBar").onclick=
e=>{
/*
Camera bar itself opens system,
but not arrow buttons.
*/
if(
e.target!==document.getElementById("prevCam") &&
e.target!==document.getElementById("nextCam")
){
openCameras();
}
};
closeCams.onclick=closeCameras;
document.getElementById("prevCam").onclick=()=>{
if(!running)return;
openCameras();
currentCam--;
if(currentCam<1){
currentCam=9;
}
cameraSound();
updateCamera();
};
document.getElementById("nextCam").onclick=()=>{
if(!running)return;
openCameras();
currentCam++;
if(currentCam>9){
currentCam=1;
}
cameraSound();
updateCamera();
};
document.querySelectorAll(".map button").forEach(btn=>{
btn.onclick=()=>{
currentCam=
Number(btn.dataset.cam);
cameraSound();
updateCamera();
};
});
/* =========================================================
CAMERA UPDATE
========================================================= */
function updateCamera(){
const cam =
CAMERAS.find(c=>c.id===currentCam);
if(!cam)return;
cameraName.textContent=cam.name;
camLabel.textContent=cam.name;
/*
Determine if any character is
currently in this camera.
*/
const occupants =
characters.filter(c=>
c.route[c.position]===currentCam
);
if(occupants.length){
camMonster.classList.remove("hidden");
const c=occupants[0];
const visual=
document.getElementById(
"cameraMonsterVisual"
);
visual.className=
"monster " +
c.color;
/*
Different character proportions.
*/
if(c.id==="MILO"){
visual.classList.add("round");
}
if(c.id==="RAVEN"){
visual.classList.add("long");
}
if(c.id==="NOVA"){
visual.classList.add("tall");
}
}else{
camMonster.classList.add("hidden");
}
document.querySelectorAll(
".map button"
).forEach(btn=>{
const n=Number(btn.dataset.cam);
btn.classList.toggle(
"current",
n===currentCam
);
const danger =
characters.some(c=>
c.route[c.position]===n
);
btn.classList.toggle(
"danger",
danger
);
});
}
/* =========================================================
ALERT
========================================================= */
let alertTimeout;
function showAlert(text){
alertBox.textContent=text;
alertBox.classList.add("show");
clearTimeout(alertTimeout);
alertTimeout=setTimeout(()=>{
alertBox.classList.remove("show");
},1300);
}
/* =========================================================
JUMPSCARE
========================================================= */
function triggerJumpscare(character){
if(!running)return;
running=false;
stopTimers();
closeCameras();
initAudio();
scareSound();
jumpscare.classList.add("show");
setTimeout(()=>{
jumpscare.classList.remove("show");
menu.style.display="flex";
game.classList.remove("active");
/*
Continue stays on the night
where the player died.
*/
currentNight=currentNight;
localStorage.setItem(
"nightCurrent",
String(currentNight)
);
selectedNight=currentNight;
refreshMenu();
},2200);
}
/* =========================================================
BLACKOUT
========================================================= */
function blackout(){
if(!running)return;
power=0;
updatePower();
leftDoorClosed=false;
rightDoorClosed=false;
leftLight=false;
rightLight=false;
updateOffice();
statusText.textContent=
"TOTAL POWER FAILURE";
showAlert("POWER OUT");
setTimeout(()=>{
triggerJumpscare({
name:"UNKNOWN",
side:"right"
});
},1500);
}
/* =========================================================
WIN NIGHT
========================================================= */
function winNight(){
if(!running)return;
running=false;
stopTimers();
closeCameras();
/*
Unlock next night.
*/
if(currentNight<5){
highestNight =
Math.max(
highestNight,
currentNight+1
);
localStorage.setItem(
"nightUnlocked",
String(highestNight)
);
currentNight++;
localStorage.setItem(
"nightCurrent",
String(currentNight)
);
completeText.textContent =
"NIGHT " +
String(currentNight-1).padStart(2,"0") +
" COMPLETE — NIGHT " +
String(currentNight).padStart(2,"0") +
" UNLOCKED";
nextNightBtn.textContent =
"START NIGHT " +
String(currentNight).padStart(2,"0");
}else{
/*
Completed all five.
Continue remains at Night 5.
*/
highestNight=5;
currentNight=5;
localStorage.setItem(
"nightUnlocked",
"5"
);
localStorage.setItem(
"nightCurrent",
"5"
);
completeText.textContent =
"ALL FIVE NIGHTS SURVIVED";
nextNightBtn.textContent =
"PLAY NIGHT 05 AGAIN";
}
nightComplete.style.display="flex";
}
/* =========================================================
COMPLETE BUTTONS
========================================================= */
nextNightBtn.onclick=()=>{
nightComplete.style.display="none";
startNight(currentNight);
};
document.getElementById("menuAfterNight").onclick=()=>{
nightComplete.style.display="none";
game.classList.remove("active");
menu.style.display="flex";
selectedNight=
Math.min(5,currentNight);
refreshMenu();
};
/* =========================================================
KEYBOARD
========================================================= */
document.addEventListener("keydown",e=>{
if(!running)return;
if(e.key.toLowerCase()==="q"){
toggleDoor("left");
}
if(e.key.toLowerCase()==="a"){
toggleLight("left");
}
if(e.key.toLowerCase()==="e"){
toggleDoor("right");
}
if(e.key.toLowerCase()==="d"){
toggleLight("right");
}
if(e.key==="ArrowLeft"){
currentCam--;
if(currentCam<1)currentCam=9;
openCameras();
updateCamera();
}
if(e.key==="ArrowRight"){
currentCam++;
if(currentCam>9)currentCam=1;
openCameras();
updateCamera();
}
if(e.key==="Escape"){
closeCameras();
}
});
/* =========================================================
INITIALIZE
========================================================= */
if(highestNight<1){
highestNight=1;
}
if(currentNight<1){
currentNight=1;
}
if(currentNight>highestNight){
currentNight=highestNight;
}
selectedNight=currentNight;
refreshMenu();
</script>
</body>
</html>Game Source: NIGHT SHIFT — Security Network
Creator: LunarPanda23
Libraries: none
Complexity: complex (2833 lines, 49.0 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-security-network-lunarpanda23" to link back to the original. Then publish at arcadelab.ai/publish.