RISK STRIKE V3
by EpicNinja335375 lines70.9 KB🛠️ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="mn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover">
<title>RISK STRIKE V3</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;overscroll-behavior:none}
body{
overflow:hidden;background:#02060b;color:#fff;
font-family:Arial,Helvetica,sans-serif;
-webkit-user-select:none;user-select:none;
-webkit-touch-callout:none;touch-action:none
}
canvas{display:block;touch-action:none}
#ui{position:fixed;inset:0;z-index:10;pointer-events:none}
#topbar{
position:absolute;top:22px;left:25px;right:25px;
display:flex;justify-content:space-between;align-items:flex-start
}
.hud-title{
font-size:12px;letter-spacing:3px;color:#00c8ff;margin-bottom:7px
}
.hud-value{font-size:17px;font-weight:bold}
#timerPanel{text-align:center}
#missionTimer{
font-size:42px;color:#ffd000;font-weight:bold;
text-shadow:0 0 20px rgba(255,208,0,.7)
}
#riskCounter{
color:#aaa;margin-top:7px;font-size:12px;letter-spacing:2px
}
#healthPanel{
position:absolute;left:30px;bottom:35px;width:340px
}
.barHeader{
display:flex;justify-content:space-between;
font-size:12px;letter-spacing:2px;margin-bottom:10px
}
#healthBar{
height:20px;background:#101820;border:1px solid #34495e;overflow:hidden
}
#healthFill{
height:100%;width:100%;background:#00d977;
transition:width .8s ease,background .5s;
box-shadow:0 0 15px #00d977
}
#scorePanel{
position:absolute;right:30px;bottom:35px;text-align:right
}
#score{
font-size:52px;color:#ffd000;font-weight:bold
}
#crosshair{
position:absolute;left:50%;top:50%;width:42px;height:42px;
transform:translate(-50%,-50%);transition:.12s
}
#crosshair:before,#crosshair:after{
content:"";position:absolute;background:#00ff99;
box-shadow:0 0 10px #00ff99
}
#crosshair:before{
width:2px;height:42px;left:20px
}
#crosshair:after{
height:2px;width:42px;top:20px
}
.recoil{
transform:translate(-50%,-50%) scale(.7)!important
}
#controls{
position:absolute;bottom:25px;left:50%;
transform:translateX(-50%);color:#718096;font-size:11px;
letter-spacing:2px;text-align:center
}
.screen{
position:fixed;inset:0;z-index:100;
display:flex;align-items:center;justify-content:center;
background:radial-gradient(circle,rgba(0,50,80,.45),rgba(0,0,0,.96))
}
.hidden{display:none!important}
.panel{
width:min(850px,90vw);padding:50px;text-align:center;
background:rgba(5,12,20,.95);border:1px solid #00c8ff;
box-shadow:0 0 50px rgba(0,200,255,.25)
}
.logo{
font-size:clamp(55px,9vw,110px);letter-spacing:10px;
font-weight:900;color:#00c8ff;text-shadow:0 0 25px #00c8ff
}
.subtitle{
margin:15px 0 35px;color:#8494a7;letter-spacing:4px
}
.text{
color:#c4ced8;line-height:1.9;margin-bottom:30px
}
#playerNameInput{
width:min(420px,90%);padding:16px 18px;margin:0 auto 22px;
display:block;background:#0b1621;color:#fff;border:1px solid #00a8cc;
outline:none;font-size:16px;text-align:center;letter-spacing:1px
}
#playerNameInput:focus{
border-color:#00ff99;
box-shadow:0 0 20px rgba(0,255,153,.18)
}
#playerNameInput::placeholder{color:#718096}
button{
padding:17px 45px;background:transparent;color:#00ff99;
border:2px solid #00ff99;cursor:pointer;font-weight:bold;
letter-spacing:2px;transition:.2s
}
button:hover{
background:#00ff99;color:#000;box-shadow:0 0 30px #00ff99
}
#detected,#encourage{
position:fixed;
top:50%;
left:50%;
transform:translate(-50%,-50%);
text-align:center;
pointer-events:none;
display:none;
}
#detected{
z-index:140;
}
#encourage{
z-index:145;
}
#detected small{
color:#ff5566;
letter-spacing:5px
}
#detected h1{
margin-top:12px;
font-size:42px;
color:#fff;
text-shadow:0 0 20px #ff3344
}
#encourage h1{
font-size:38px;
color:#00ff99;
text-shadow:
0 0 8px #00ff99,
0 0 20px #00ff99,
0 0 35px rgba(0,255,153,.65);
letter-spacing:3px;
white-space:nowrap;
}
#encourage.walking h1{
color:#00ff99;
text-shadow:
0 0 8px #00ff99,
0 0 20px #00ff99;
}
#encourage.seated h1{
color:#ffd000;
text-shadow:
0 0 8px #ffd000,
0 0 20px #ffd000;
}
#encourage.ceo h1{
color:#ffd000;
font-size:44px;
font-weight:900;
text-shadow:
0 0 8px #ffd000,
0 0 22px #ffd000,
0 0 40px rgba(255,208,0,.75);
}
#riskModal{
position:fixed;inset:0;z-index:200;display:none;
align-items:center;justify-content:center;background:rgba(0,0,0,.86)
}
.modalBox{
width:min(950px,92vw);background:#07111c;border:1px solid #00a8cc;
padding:40px;box-shadow:0 0 50px rgba(0,180,255,.2)
}
#riskTitle{
color:#00c8ff;letter-spacing:3px;margin-bottom:15px
}
#scenario{
color:#cbd5e1;line-height:1.8;margin-bottom:20px;font-size:17px
}
#decisionTimerBox{
margin-bottom:25px;padding:12px;border:1px solid #263849;
display:flex;justify-content:space-between;color:#9fb3c8
}
#decisionTimer{
color:#ffd000;font-weight:bold;font-size:20px
}
#options{
display:grid;
grid-template-columns:repeat(3,1fr);
gap:18px
}
.option{
pointer-events:auto;
min-height:150px;
padding:25px;
background:#0d1925;
border:1px solid #2a4155;
cursor:pointer;
transition:.2s;
color:#dbe7f1
}
.option:hover{
transform:translateY(-7px);
border-color:#00c8ff;
box-shadow:0 0 25px rgba(0,200,255,.15)
}
.option.optionLocked{
pointer-events:none!important;
opacity:.45;
cursor:default
}
.option.optionSelected{
border-color:#00ff99;
box-shadow:0 0 25px rgba(0,255,153,.2);
opacity:1
}
.optionKey{
color:#00c8ff;
font-size:12px;
letter-spacing:2px;
margin-bottom:15px
}
#analyzing{
display:none;
text-align:center;
padding:30px;
color:#00c8ff;
letter-spacing:3px;
animation:pulse 1s infinite
}
@keyframes pulse{
50%{opacity:.35}
}
#result{
display:none;
text-align:center;
padding:30px;
margin-top:25px;
border:1px solid #2a4155
}
#resultTitle{
font-size:30px;
margin-bottom:15px
}
#resultStats{
color:#aab8c5;
line-height:1.8
}
#flash{
position:fixed;
inset:0;
z-index:150;
pointer-events:none;
opacity:0;
transition:.15s
}
.flash-red{background:rgba(255,20,40,.35)}
.flash-yellow{background:rgba(255,190,0,.25)}
.flash-green{background:rgba(0,255,120,.22)}
#scanOverlay{
position:fixed;
inset:0;
z-index:20;
pointer-events:none;
opacity:0;
background:radial-gradient(
circle,
rgba(0,255,180,.18),
transparent 55%
)
}
.scanActive{
animation:scan .35s
}
@keyframes scan{
0%{opacity:0}
50%{opacity:1}
100%{opacity:0}
}
#finalScore{
font-size:80px;
color:#ffd000;
margin:15px
}
#managerType{
font-size:40px;
margin:20px
}
.shake{
animation:shake .35s
}
@keyframes shake{
0%,100%{transform:translate(0)}
20%{transform:translate(-10px,5px)}
40%{transform:translate(10px,-5px)}
60%{transform:translate(-7px,5px)}
80%{transform:translate(7px,-5px)}
}
#mobileControls{
display:none;
position:fixed;
inset:0;
z-index:30;
pointer-events:none;
touch-action:none
}
.mobilePad{
position:absolute;
bottom:22px;
display:grid;
grid-template-columns:repeat(3,54px);
grid-template-rows:repeat(2,54px);
gap:7px;
pointer-events:auto
}
#movePad{left:18px}
#actionPad{
right:18px;
display:flex;
align-items:flex-end;
gap:10px
}
.mobileBtn{
width:54px;
height:54px;
border:1px solid rgba(0,200,255,.65);
background:rgba(5,15,24,.78);
color:#00dfff;
display:flex;
align-items:center;
justify-content:center;
font-size:22px;
font-weight:bold;
user-select:none;
-webkit-user-select:none;
-webkit-tap-highlight-color:transparent;
border-radius:12px;
pointer-events:auto
}
.mobileBtn:active{
background:rgba(0,200,255,.28);
border-color:#00ff99;
color:#fff
}
#mobileScan{
width:72px;
height:72px;
border-radius:50%;
font-size:13px;
letter-spacing:1px
}
#touchHint{
position:absolute;
left:50%;
bottom:18px;
transform:translateX(-50%);
color:#718096;
font-size:10px;
letter-spacing:1px;
text-align:center;
white-space:nowrap
}
#engineLoading{
position:fixed;
inset:0;
z-index:999999;
display:flex;
align-items:center;
justify-content:center;
background:#02060b;
color:#00c8ff;
font-family:Arial,Helvetica,sans-serif;
text-align:center;
}
#engineLoadingBox{
padding:40px;
}
#engineLoadingTitle{
font-size:42px;
font-weight:900;
letter-spacing:5px;
text-shadow:0 0 25px #00c8ff;
}
#engineLoadingText{
margin-top:18px;
color:#718096;
font-size:13px;
letter-spacing:2px;
}
#engineError{
display:none;
position:fixed;
inset:0;
z-index:1000000;
align-items:center;
justify-content:center;
background:#02060b;
color:#fff;
font-family:Arial,Helvetica,sans-serif;
text-align:center;
padding:30px;
}
@media(max-width:800px){
#mobileControls{display:block}
#controls{display:none}
#healthPanel{
left:15px;
bottom:104px;
width:180px
}
#scorePanel{
right:15px;
bottom:104px
}
#score{font-size:38px}
#missionTimer{font-size:30px}
#topbar{
top:12px;
left:15px;
right:15px
}
.hud-title{
font-size:9px;
letter-spacing:1.5px
}
.hud-value{
font-size:13px;
max-width:110px;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap
}
#riskCounter{
font-size:9px;
letter-spacing:1px
}
.modalBox{
width:94vw;
max-height:90vh;
overflow-y:auto;
padding:22px 16px
}
#scenario{
font-size:14px;
line-height:1.55
}
#options{
grid-template-columns:1fr;
gap:10px
}
.option{
min-height:92px;
padding:16px
}
.option:hover{
transform:none
}
.panel{
width:94vw;
max-height:92vh;
overflow-y:auto;
padding:25px 18px
}
.logo{
font-size:clamp(42px,14vw,70px);
letter-spacing:5px
}
.subtitle{
font-size:11px;
letter-spacing:2px
}
.text{
font-size:14px;
line-height:1.6
}
#playerNameInput{
width:100%
}
button{
min-height:52px;
padding:14px 25px
}
#encourage h1{
font-size:30px
}
#encourage.ceo h1{
font-size:34px
}
}
</style>
</head>
<body>
<!-- Early error trap: must load BEFORE the game script so a crash
mid-script is actually caught and shown, instead of silently
leaving window.__RISK_START_GAME undefined. -->
<script>
window.__riskShowError = function(msg){
try{
var box = document.getElementById("engineError");
var detail = document.getElementById("engineErrorDetail");
if(detail) detail.textContent = "Алдаа: " + msg;
if(box) box.style.display = "flex";
var loading = document.getElementById("engineLoading");
if(loading) loading.style.display = "none";
}catch(e){}
};
window.addEventListener("error", function(e){
console.error("RISK STRIKE runtime error:", e.error || e.message);
window.__riskShowError((e.error && (e.error.message + " (line " + e.lineno + ")")) || e.message || "Unknown error");
});
window.addEventListener("unhandledrejection", function(e){
console.error("RISK STRIKE promise error:", e.reason);
window.__riskShowError((e.reason && e.reason.message) || String(e.reason));
});
</script>
<div id="engineLoading">
<div id="engineLoadingBox">
<div id="engineLoadingTitle">RISK STRIKE</div>
<div id="engineLoadingText">3D ENGINE АЧААЛЖ БАЙНА...</div>
</div>
</div>
<div id="engineError">
<div>
<div style="
font-size:42px;
font-weight:900;
letter-spacing:5px;
color:#00c8ff;
text-shadow:0 0 25px #00c8ff;
margin-bottom:20px;
">RISK STRIKE</div>
<div style="
color:#ff5566;
font-size:18px;
margin-bottom:15px;
">3D ENGINE АЧААЛЛАЖ ЧАДСАНГҮЙ</div>
<div style="
color:#718096;
font-size:13px;
line-height:1.8;
max-width:650px;
margin:auto;
">
Three.js ачаалагдсангүй.
<br>
ArcadeLab external CDN access-ийг хаасан байж болзошгүй.
</div>
<div id="engineErrorDetail" style="
color:#ffd000;
font-size:12px;
line-height:1.6;
max-width:650px;
margin:15px auto 0;
word-break:break-word;
"></div>
<button
onclick="location.reload()"
style="margin-top:25px"
>
ДАХИН АЧААЛАХ
</button>
</div>
</div>
<div id="ui">
<div id="topbar">
<div>
<div class="hud-title">ТОГЛОГЧ</div>
<div class="hud-value" id="playerDisplay">ЭРСДЭЛИЙН МЕНЕЖЕР</div>
</div>
<div id="timerPanel">
<div class="hud-title">ҮЛДСЭН ХУГАЦАА</div>
<div id="missionTimer">04:00</div>
<div id="riskCounter">ИЛРҮҮЛСЭН ЭРСДЭЛ: 0 / 10</div>
</div>
<div style="width:180px"></div>
</div>
<div id="crosshair"></div>
<div id="healthPanel">
<div class="barHeader">
<span>КОМПАНИЙН ЭРҮҮЛ МЭНД</span>
<span id="healthText">100%</span>
</div>
<div id="healthBar">
<div id="healthFill"></div>
</div>
</div>
<div id="controls">
W A S D — ХӨДӨЛГӨӨН • ХУЛГАНА — ХАРАА • ЗҮҮН ТОВЧ — СКАН
</div>
<div id="mobileControls">
<div id="movePad" class="mobilePad">
<div></div>
<div class="mobileBtn" data-move="forward">▲</div>
<div></div>
<div class="mobileBtn" data-move="left">◀</div>
<div class="mobileBtn" data-move="backward">▼</div>
<div class="mobileBtn" data-move="right">▶</div>
</div>
<div id="actionPad">
<div id="mobileScan" class="mobileBtn">SCAN</div>
</div>
<div id="touchHint">
ДЭЛГЭЦ ДЭЭР ЧИРЖ ХАРААГАА ЭРГҮҮЛНЭ
</div>
</div>
<div id="scorePanel">
<div class="hud-title">ЭРСДЭЛИЙН ОНОО</div>
<div id="score">0</div>
</div>
</div>
<div id="scanOverlay"></div>
<div id="flash"></div>
<div id="detected">
<h1 id="detectedName"></h1>
</div>
<div id="encourage">
<h1 id="encourageText"></h1>
</div>
<div id="startScreen" class="screen">
<div class="panel">
<div class="logo">RISK STRIKE</div>
<div class="subtitle">
БИЗНЕСЭЭ ХАМГААЛ • ЭРСДЭЛИЙГ ИЛРҮҮЛ
</div>
<div class="text">
Та бол <b>ЭРСДЭЛИЙН МЕНЕЖЕР</b>.
<br><br>
<b>КОМПАНИ ТАНААС ХАМААРНА.</b>
</div>
<input
id="playerNameInput"
type="text"
maxlength="30"
placeholder="НЭРЭЭ ОРУУЛНА УУ"
autocomplete="off"
>
<button id="startButton" type="button">
ТОГЛООМ ЭХЛҮҮЛЭХ
</button>
</div>
</div>
<div id="riskModal">
<div class="modalBox">
<h2 id="riskTitle">ЭРСДЭЛ ИЛЭРЛЭЭ</h2>
<div id="scenario"></div>
<div id="decisionTimerBox">
<span>ШИЙДВЭР ГАРГАХ ХУГАЦАА</span>
<span id="decisionTimer">15</span>
</div>
<div id="options"></div>
<div id="analyzing">
ЭРСДЭЛИЙН ШИЙДЛИЙГ ШИНЖИЛЖ БАЙНА...
</div>
<div id="result">
<div id="resultTitle"></div>
<div id="resultStats"></div>
</div>
</div>
</div>
<div id="endScreen" class="screen hidden">
<div class="panel">
<div class="subtitle">ДААЛГАВАР ДУУСЛАА</div>
<div id="finalScore">0</div>
<h2 id="managerType">ЭРСДЭЛИЙН МАСТЕР</h2>
<div id="managerText" class="text"></div>
<div class="text">
<b>ТА ЭРСДЭЛИЙН УДИРДЛАГА ХИЙЛЭЭ</b>
<br><br>
<b>
"Эрсдэлийн удирдлага гэдэг нь бизнесийг удаашруулах биш.
<br>
Бизнесийг аюулгүй урагшлуулах явдал юм."
</b>
</div>
<button onclick="location.reload()">
ДАХИН ТОГЛОХ
</button>
</div>
</div>
<!-- ARCADELAB PUBLISH FIX: independent Start-button bridge -->
<script>
(function(){
function installRiskStartBridge(){
const btn = document.getElementById("startButton");
if(!btn || btn.__riskBridgeInstalled) return;
btn.__riskBridgeInstalled = true;
btn.addEventListener("click", function(ev){
try { ev.preventDefault(); ev.stopPropagation(); } catch(e) {}
window.__RISK_START_REQUESTED = true;
if(typeof window.__RISK_START_GAME === "function"){
window.__RISK_START_GAME();
}
}, false);
}
if(document.readyState === "loading"){
document.addEventListener("DOMContentLoaded", installRiskStartBridge, {once:true});
}else{
installRiskStartBridge();
}
})();
</script>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<script id="RISK_STRIKE_GAME_SOURCE">
/* ARCADELAB FIX: Three.js is already loaded before this script. */
if (typeof window.THREE === "undefined") {
const e = document.getElementById("engineLoading");
if (e) e.innerHTML = '<div id="engineLoadingBox"><div id="engineLoadingTitle">RISK STRIKE</div><div id="engineLoadingText" style="color:#ff5566">THREE.JS АЧААЛЛАЖ ЧАДСАНГҮЙ</div></div>';
throw new Error("Three.js failed to load.");
}
const loadingScreen = document.getElementById("engineLoading");
if (loadingScreen) loadingScreen.style.display = "none";
const scene=new THREE.Scene();
scene.background=new THREE.Color(0x11100e);
scene.fog=new THREE.Fog(
0x11100e,
45,
170
);
const camera=new THREE.PerspectiveCamera(
75,
window.innerWidth/window.innerHeight,
.1,
1000
);
const renderer=new THREE.WebGLRenderer({
antialias:true
});
renderer.setSize(
window.innerWidth,
window.innerHeight
);
renderer.setPixelRatio(
Math.min(window.devicePixelRatio,2)
);
renderer.shadowMap.enabled=true;
renderer.shadowMap.type=THREE.PCFSoftShadowMap;
document.body.appendChild(
renderer.domElement
);
scene.add(
new THREE.AmbientLight(
0x91a9bb,
2.8
)
);
const directional=new THREE.DirectionalLight(
0xc8ecff,
3
);
directional.position.set(
10,20,10
);
directional.castShadow=true;
scene.add(directional);
const floor=new THREE.Mesh(
new THREE.PlaneGeometry(160,160),
new THREE.MeshStandardMaterial({
color:0x101820,
roughness:.75,
metalness:.25
})
);
floor.rotation.x=-Math.PI/2;
floor.receiveShadow=true;
scene.add(floor);
const grid=new THREE.GridHelper(
160,
160,
0x006688,
0x142535
);
grid.position.y=.01;
scene.add(grid);
const officeLaneMaterial=
new THREE.MeshStandardMaterial({
color:0x2b2520,
roughness:.92,
metalness:.02
});
for(const z of [-40,-20,0,20,40]){
const lane=new THREE.Mesh(
new THREE.BoxGeometry(142,.025,5.5),
officeLaneMaterial
);
lane.position.set(
0,.018,z
);
scene.add(lane);
}
for(const x of [-55,-35,-15,5,25,45,65]){
const lane=new THREE.Mesh(
new THREE.BoxGeometry(2.2,.026,142),
officeLaneMaterial
);
lane.position.set(
x,.019,0
);
scene.add(lane);
}
const ceiling=new THREE.Mesh(
new THREE.BoxGeometry(150,.22,150),
new THREE.MeshStandardMaterial({
color:0x0b141c,
roughness:.82,
metalness:.12,
side:THREE.DoubleSide
})
);
ceiling.position.y=8.4;
scene.add(ceiling);
for(let x=-65;x<=65;x+=13){
for(let z=-64;z<=64;z+=16){
const panel=new THREE.Mesh(
new THREE.BoxGeometry(7,.08,.55),
new THREE.MeshStandardMaterial({
color:0xe8f7ff,
emissive:0xb9eaff,
emissiveIntensity:1.15
})
);
panel.position.set(
x,8.22,z
);
scene.add(panel);
const light=new THREE.PointLight(
0xb9eaff,
1.05,
18,
2
);
light.position.set(
x,7.85,z
);
scene.add(light);
}
}
const obstacles=[];
function createWall(x,z,w,d,h=8){
const wall=new THREE.Mesh(
new THREE.BoxGeometry(w,h,d),
new THREE.MeshStandardMaterial({
color:0x4a3d32,
metalness:.15,
roughness:.72
})
);
wall.position.set(
x,
h/2,
z
);
wall.castShadow=true;
wall.receiveShadow=true;
scene.add(wall);
obstacles.push({
x,z,w,d
});
return wall;
}
[
[0,-75,150,1],
[0,75,150,1],
[-75,0,1,150],
[75,0,1,150],
[-34,-18,24,.6],
[-47,-4,.6,24],
[34,-18,24,.6],
[47,-4,.6,24],
[0,10,32,.6]
].forEach(p=>createWall(...p));
function createClassicPartition(
x,z,w,d,h=4.2
){
const mat=
new THREE.MeshStandardMaterial({
color:0x5b4a3a,
roughness:.78,
metalness:.05
});
const panel=new THREE.Mesh(
new THREE.BoxGeometry(w,h,d),
mat
);
panel.position.set(
x,
h/2,
z
);
scene.add(panel);
obstacles.push({
x,z,w,d
});
const cap=new THREE.Mesh(
new THREE.BoxGeometry(
w+.08,
.12,
d+.08
),
new THREE.MeshStandardMaterial({
color:0x8d765d,
roughness:.55,
metalness:.1
})
);
cap.position.set(
x,
h+.06,
z
);
scene.add(cap);
const glass=new THREE.Mesh(
new THREE.BoxGeometry(
w*.72,
.75,
Math.max(d,.08)
),
new THREE.MeshStandardMaterial({
color:0x9bb5bd,
transparent:true,
opacity:.16,
roughness:.12,
metalness:.05
})
);
glass.position.set(
x,
h-.48,
z
);
scene.add(glass);
}
[
[-56,-25,13,.22,4.5],
[56,-25,13,.22,4.5],
[-56,25,13,.22,4.5],
[56,25,13,.22,4.5],
[-55,0,.22,20,4.5],
[55,0,.22,20,4.5],
[-12,47,18,.22,4.2],
[12,47,18,.22,4.2],
[-12,-47,18,.22,4.2],
[12,-47,18,.22,4.2]
].forEach(p=>createClassicPartition(...p));
const officeWood=
new THREE.MeshStandardMaterial({
color:0x3d3027,
roughness:.65,
metalness:.15
});
const darkWood=
new THREE.MeshStandardMaterial({
color:0x251d18,
roughness:.7,
metalness:.1
});
const glassMaterial=
new THREE.MeshStandardMaterial({
color:0x3ba9c8,
transparent:true,
opacity:.20,
roughness:.1,
metalness:.15
});
function createCompanySign(){
const c=document.createElement("canvas");
c.width=1400;
c.height=260;
const ctx=c.getContext("2d");
ctx.fillStyle="#0b141c";
ctx.fillRect(
0,0,c.width,c.height
);
ctx.font="bold 118px Arial";
ctx.textAlign="center";
ctx.textBaseline="middle";
ctx.fillStyle="#e8f7ff";
ctx.fillText(
"SIMPLE",
c.width/2,
c.height/2
);
const mesh=new THREE.Mesh(
new THREE.PlaneGeometry(29,5.4),
new THREE.MeshBasicMaterial({
map:new THREE.CanvasTexture(c),
transparent:true
})
);
mesh.position.set(
0,5.6,-57.42
);
scene.add(mesh);
}
createCompanySign();
function createDepartmentSign(
text,x,y,z,rotationY=0
){
const c=document.createElement("canvas");
c.width=1100;
c.height=180;
const ctx=c.getContext("2d");
ctx.fillStyle="#101d27";
ctx.fillRect(
0,0,c.width,c.height
);
ctx.strokeStyle="#00a8cc";
ctx.lineWidth=4;
ctx.strokeRect(
5,5,
c.width-10,
c.height-10
);
ctx.font="bold 48px Arial";
ctx.textAlign="center";
ctx.textBaseline="middle";
ctx.fillStyle="#dce8ef";
ctx.fillText(
text,
c.width/2,
c.height/2
);
const sign=new THREE.Mesh(
new THREE.PlaneGeometry(8,1.3),
new THREE.MeshBasicMaterial({
map:new THREE.CanvasTexture(c)
})
);
sign.position.set(
x,y,z
);
sign.rotation.y=rotationY;
scene.add(sign);
}
createDepartmentSign(
"ЭРСДЭЛИЙН УДИРДЛАГЫН ХЭЛТЭС",
-57.35,5.15,-29,
Math.PI/2
);
createDepartmentSign(
"САЛБАРЫН УДИРДЛАГЫН ХЭЛТЭС",
-34,5.15,-17.65,
0
);
createDepartmentSign(
"ЗЭЭЛ ТӨЛҮҮЛЭЛТИЙН ХЭЛТЭС",
34,5.15,-17.65,
0
);
createDepartmentSign(
"МЭДЭЭЛЛИЙН ТЕХНОЛОГИЙН ХЭЛТЭС",
57.35,5.15,-4,
-Math.PI/2
);
createDepartmentSign(
"ХУУЛЬ, КОМПЛАЕНСИЙН ХЭЛТЭС",
0,5.15,10.35,
0
);
function createMonitor(x,z){
const g=new THREE.Group();
const screen=new THREE.Mesh(
new THREE.BoxGeometry(1.65,1.05,.12),
new THREE.MeshStandardMaterial({
color:0x071018,
emissive:0x006688,
emissiveIntensity:.55,
metalness:.5,
roughness:.3
})
);
screen.position.y=2.05;
g.add(screen);
const stand=new THREE.Mesh(
new THREE.BoxGeometry(.12,.65,.12),
new THREE.MeshStandardMaterial({
color:0x596772,
metalness:.6,
roughness:.3
})
);
stand.position.y=1.55;
g.add(stand);
const base=new THREE.Mesh(
new THREE.BoxGeometry(.7,.08,.35),
new THREE.MeshStandardMaterial({
color:0x596772,
metalness:.6
})
);
base.position.y=1.25;
g.add(base);
g.position.set(
x,0,z
);
scene.add(g);
}
function createOfficeChair(x,z){
const g=new THREE.Group();
const seat=new THREE.Mesh(
new THREE.BoxGeometry(1.25,.18,1.2),
darkWood
);
seat.position.y=.85;
g.add(seat);
const back=new THREE.Mesh(
new THREE.BoxGeometry(1.25,1.4,.16),
darkWood
);
back.position.set(
0,1.55,.48
);
g.add(back);
const pole=new THREE.Mesh(
new THREE.CylinderGeometry(.08,.08,.8,10),
new THREE.MeshStandardMaterial({
color:0x64727d,
metalness:.7,
roughness:.3
})
);
pole.position.y=.4;
g.add(pole);
const base=new THREE.Mesh(
new THREE.CylinderGeometry(.55,.55,.08,16),
new THREE.MeshStandardMaterial({
color:0x202a32,
metalness:.5
})
);
base.position.y=.05;
g.add(base);
g.position.set(
x,0,z
);
scene.add(g);
obstacles.push({
x,z,w:1.35,d:1.25
});
return g;
}
function createOfficeDesk(x,z){
const desk=new THREE.Group();
const top=new THREE.Mesh(
new THREE.BoxGeometry(4.6,.18,2.1),
officeWood
);
top.position.y=1.25;
desk.add(top);
const lg=new THREE.BoxGeometry(
.16,1.25,.16
);
[
[-2,0,-.8],
[2,0,-.8],
[-2,0,.8],
[2,0,.8]
].forEach(p=>{
const leg=new THREE.Mesh(
lg,
darkWood
);
leg.position.set(
p[0],
.62,
p[2]
);
desk.add(leg);
});
desk.position.set(
x,0,z
);
scene.add(desk);
obstacles.push({
x,z,w:4.6,d:2.1
});
createOfficeChair(
x,
z+1.9
);
createMonitor(
x,
z-.15
);
}
function workerMaterial(color){
return new THREE.MeshStandardMaterial({
color,
roughness:.55,
metalness:.08
});
}
/* ARCADELAB FIX: some hosts inject an older Three.js build that has
no THREE.CapsuleGeometry (added in r142+). Build the same rounded
capsule shape from a cylinder + two hemisphere caps instead, so
this works on any Three.js version. */
function createCapsuleMesh(radius,length,radialSegments,material){
const group=new THREE.Group();
const body=new THREE.Mesh(
new THREE.CylinderGeometry(radius,radius,length,radialSegments),
material
);
group.add(body);
const capSegments=Math.max(4,Math.floor(radialSegments/2));
const topCap=new THREE.Mesh(
new THREE.SphereGeometry(radius,radialSegments,capSegments,0,Math.PI*2,0,Math.PI/2),
material
);
topCap.position.y=length/2;
group.add(topCap);
const bottomCap=new THREE.Mesh(
new THREE.SphereGeometry(radius,radialSegments,capSegments,0,Math.PI*2,Math.PI/2,Math.PI/2),
material
);
bottomCap.position.y=-length/2;
group.add(bottomCap);
return group;
}
function createWorkerNameLabel(
name,
scaleX=2.9,
scaleY=.50,
border="#00c8ff",
fontSize=42
){
const c=document.createElement("canvas");
c.width=700;
c.height=120;
const ctx=c.getContext("2d");
ctx.clearRect(
0,0,c.width,c.height
);
ctx.fillStyle="rgba(5,14,22,.90)";
ctx.fillRect(
8,8,
c.width-16,
c.height-16
);
ctx.strokeStyle=border;
ctx.lineWidth=5;
ctx.strokeRect(
8,8,
c.width-16,
c.height-16
);
ctx.font=`bold ${fontSize}px Arial`;
ctx.textAlign="center";
ctx.textBaseline="middle";
ctx.fillStyle="#ffffff";
ctx.fillText(
name,
c.width/2,
c.height/2
);
const texture=
new THREE.CanvasTexture(c);
texture.colorSpace=
THREE.SRGBColorSpace;
const sprite=
new THREE.Sprite(
new THREE.SpriteMaterial({
map:texture,
transparent:true,
depthTest:true,
depthWrite:false
})
);
sprite.scale.set(
scaleX,
scaleY,
1
);
return sprite;
}
function createSeatedEmployee(
name,x,z,color=0x2d6f9d
){
const g=new THREE.Group();
const body=createCapsuleMesh(.30,.72,8,workerMaterial(color));
body.position.set(
0,1.22,0
);
body.rotation.x=.22;
g.add(body);
const head=new THREE.Mesh(
new THREE.SphereGeometry(
.27,14,12
),
new THREE.MeshStandardMaterial({
color:0xd7a57c,
roughness:.85
})
);
head.position.set(
0,1.92,-.03
);
g.add(head);
const hair=new THREE.Mesh(
new THREE.SphereGeometry(
.28,14,8,
0,Math.PI*2,
0,Math.PI*.55
),
new THREE.MeshStandardMaterial({
color:0x20242a,
roughness:.9
})
);
hair.position.set(
0,2.04,-.03
);
g.add(hair);
const armMat=workerMaterial(color);
for(const s of [-1,1]){
const arm=new THREE.Mesh(
new THREE.BoxGeometry(
.12,.55,.13
),
armMat
);
arm.position.set(
s*.36,
1.22,
-.02
);
arm.rotation.x=-.72;
g.add(arm);
}
const ew=
new THREE.MeshStandardMaterial({
color:0xffffff,
roughness:.35
});
const eb=
new THREE.MeshStandardMaterial({
color:0x050505,
roughness:.25
});
for(const s of [-1,1]){
const eye=new THREE.Mesh(
new THREE.SphereGeometry(
.05,10,8
),
ew
);
eye.position.set(
s*.105,
1.96,
-.285
);
g.add(eye);
const pupil=new THREE.Mesh(
new THREE.SphereGeometry(
.026,8,6
),
eb
);
pupil.position.set(
s*.105,
1.96,
-.325
);
g.add(pupil);
}
const label=createWorkerNameLabel(
name
);
label.position.set(
0,2.55,-.03
);
g.add(label);
g.position.set(
x,
0,
z+1.63
);
scene.add(g);
seatedScanTargets.push(g);
return g;
}
const DEPARTMENT_DESKS=[
[-38,18,"ТӨРБОЛД"],
[-26,18,"БАТ-ЭРДЭНЭ"],
[-38,28,"ЭНХБОЛД"],
[-26,28,"АНАР"],
[-38,38,"БУЯНЗАЯА"],
[-26,38,"АНУЖИН"],
[-10,18,"МӨНХБАТ"],
[2,18,"БОЛОР"],
[-10,28,"ЭНХБИЛГҮҮН"],
[2,28,"ДӨЛГӨӨН"],
[-10,38,"МИШЭЭЛ"],
[2,38,"ЭНХБААТАР"],
[22,18,"ТӨГӨЛДӨР"],
[34,18,"ТЭМҮҮЖИН"],
[22,28,"БИЛГҮҮН"],
[34,28,"МӨНХБАЯСГАЛАН"],
[22,38,"ЭРДЭНЭТУНГАЛАГ"],
[34,38,"ЭНХТҮВШИН"]
];
DEPARTMENT_DESKS.forEach(
d=>createOfficeDesk(
d[0],
d[1]
)
);
const officeWorkers=[];
const seatedScanTargets=[];
const walkingScanTargets=[];
let ceoScanTarget=null;
[
["ОЮУНГЭРЭЛ",-38,18,0x2d6f9d],
["БОЛОР",2,28,0x3c8d67],
["БАТТУЛГА",34,18,0x8c6b2f],
["ЗОЛБОО",-10,38,0x7a4c9e],
["МАРАЛГУА",22,38,0x3c6b85]
].forEach(p=>createSeatedEmployee(...p));
function createConferenceTable(){
const table=new THREE.Group();
const top=new THREE.Mesh(
new THREE.BoxGeometry(
12,.25,4
),
darkWood
);
top.position.y=1.05;
table.add(top);
const lg=new THREE.BoxGeometry(
.25,1,.25
);
[
[-5,0,-1.5],
[5,0,-1.5],
[-5,0,1.5],
[5,0,1.5]
].forEach(p=>{
const leg=new THREE.Mesh(
lg,
officeWood
);
leg.position.set(
p[0],
.5,
p[2]
);
table.add(leg);
});
table.position.set(
0,0,-32
);
scene.add(table);
obstacles.push({
x:0,
z:-32,
w:12,
d:4
});
for(let i=-4;i<=4;i+=2){
createOfficeChair(
i,
-35
);
createOfficeChair(
i,
-29
);
}
}
createConferenceTable();
function createReception(){
const reception=new THREE.Mesh(
new THREE.BoxGeometry(
7,1.4,2
),
officeWood
);
reception.position.set(
0,.7,48
);
scene.add(reception);
const front=new THREE.Mesh(
new THREE.BoxGeometry(
6.6,.9,.08
),
new THREE.MeshStandardMaterial({
color:0x0b2632,
emissive:0x003d50,
emissiveIntensity:.5
})
);
front.position.set(
0,.7,46.96
);
scene.add(front);
const sign=new THREE.Mesh(
new THREE.BoxGeometry(
3.5,.55,.08
),
new THREE.MeshStandardMaterial({
color:0x00a8cc,
emissive:0x00a8cc,
emissiveIntensity:.8
})
);
sign.position.set(
0,1.3,46.91
);
scene.add(sign);
obstacles.push({
x:0,
z:48,
w:7,
d:2
});
}
createReception();
function createCabinet(x,z){
const cabinet=new THREE.Mesh(
new THREE.BoxGeometry(
2.8,3.5,.8
),
darkWood
);
cabinet.position.set(
x,1.75,z
);
scene.add(cabinet);
for(let y=.65;y<=2.8;y+=.7){
const line=new THREE.Mesh(
new THREE.BoxGeometry(
2.5,.04,.03
),
new THREE.MeshStandardMaterial({
color:0x53636f
})
);
line.position.set(
x,y,z-.42
);
scene.add(line);
}
obstacles.push({
x,z,w:2.8,d:.8
});
}
[
[-60,-30],
[60,-30],
[-60,30],
[60,30]
].forEach(
p=>createCabinet(...p)
);
function createPlant(x,z){
const pot=new THREE.Mesh(
new THREE.CylinderGeometry(
.45,.55,.7,16
),
new THREE.MeshStandardMaterial({
color:0x24333b,
roughness:.8
})
);
pot.position.set(
x,.35,z
);
scene.add(pot);
const leaves=new THREE.Group();
for(let i=0;i<8;i++){
const leaf=new THREE.Mesh(
new THREE.ConeGeometry(
.18,1.6,8
),
new THREE.MeshStandardMaterial({
color:0x16805a,
roughness:.8
})
);
leaf.position.set(
Math.cos(i)*.35,
1.35,
Math.sin(i)*.35
);
leaf.rotation.z=
(Math.random()-.5)*.6;
leaves.add(leaf);
}
leaves.position.set(
x,0,z
);
scene.add(leaves);
}
[
[-60,55],
[60,55],
[-60,-55],
[60,-55],
[-65,15],
[65,15],
[-65,-10],
[65,-10],
[-15,60],
[15,60],
[-15,-60],
[15,-60]
].forEach(
p=>createPlant(...p)
);
[
[-52,42],
[-40,50],
[-25,50],
[20,50],
[35,50],
[50,42],
[-52,-45],
[-38,-50],
[25,-50],
[45,-45]
].forEach(
p=>createOfficeDesk(...p)
);
function createGlassPartition(
x,z,w,d
){
const glass=new THREE.Mesh(
new THREE.BoxGeometry(
w,6.5,d
),
glassMaterial
);
glass.position.set(
x,3.25,z
);
scene.add(glass);
obstacles.push({
x,z,w,d
});
const frameMaterial=
new THREE.MeshStandardMaterial({
color:0x71818d,
metalness:.75,
roughness:.25
});
if(w>d){
for(
let px=x-w/2;
px<=x+w/2;
px+=2
){
const frame=new THREE.Mesh(
new THREE.BoxGeometry(
.06,6.5,.08
),
frameMaterial
);
frame.position.set(
px,3.25,z
);
scene.add(frame);
}
}else{
for(
let pz=z-d/2;
pz<=z+d/2;
pz+=2
){
const frame=new THREE.Mesh(
new THREE.BoxGeometry(
.08,6.5,.06
),
frameMaterial
);
frame.position.set(
x,3.25,pz
);
scene.add(frame);
}
}
}
createGlassPartition(
-30,-5,25,.12
);
createGlassPartition(
30,-5,25,.12
);
function createCeilingLight(x,z){
const fixture=new THREE.Mesh(
new THREE.BoxGeometry(
5,.08,.35
),
new THREE.MeshStandardMaterial({
color:0xdce8ef,
emissive:0x9edfff,
emissiveIntensity:.9
})
);
fixture.position.set(
x,8.05,z
);
scene.add(fixture);
const light=new THREE.PointLight(
0xb9eaff,
1.8,
20
);
light.position.set(
x,7.55,z
);
scene.add(light);
}
for(let x=-45;x<=45;x+=15){
[-40,-20,0,20,40].forEach(
z=>createCeilingLight(x,z)
);
}
function neon(x,z,color){
const light=new THREE.PointLight(
color,
4,
14
);
light.position.set(
x,7.2,z
);
scene.add(light);
}
[
[-40,-40,0x00c8ff],
[40,-40,0xff2255],
[0,40,0x00ff88],
[0,0,0x7755ff],
[-60,15,0x00c8ff],
[60,15,0xffd000],
[-60,-12,0x00ff88],
[60,-12,0x9d7cff]
].forEach(
p=>neon(...p)
);
function createWorker(
name,x,z,color=0x2c78a8
){
const g=new THREE.Group();
const body=createCapsuleMesh(.32,.9,8,workerMaterial(color));
body.position.y=1.15;
g.add(body);
const head=new THREE.Mesh(
new THREE.SphereGeometry(
.28,14,12
),
new THREE.MeshStandardMaterial({
color:0xd7a57c,
roughness:.85
})
);
head.position.y=1.95;
g.add(head);
const hair=new THREE.Mesh(
new THREE.SphereGeometry(
.29,14,10,
0,Math.PI*2,
0,Math.PI*.55
),
new THREE.MeshStandardMaterial({
color:0x20242a,
roughness:.9
})
);
hair.position.set(
0,2.05,0
);
g.add(hair);
const legMat=
workerMaterial(0x202b35);
const leftLeg=new THREE.Mesh(
new THREE.BoxGeometry(
.13,.72,.16
),
legMat
);
const rightLeg=new THREE.Mesh(
new THREE.BoxGeometry(
.13,.72,.16
),
legMat
);
leftLeg.position.set(
-.14,.48,0
);
rightLeg.position.set(
.14,.48,0
);
g.add(
leftLeg,
rightLeg
);
const armMat=
workerMaterial(color);
const leftArm=new THREE.Mesh(
new THREE.BoxGeometry(
.12,.62,.13
),
armMat
);
const rightArm=new THREE.Mesh(
new THREE.BoxGeometry(
.12,.62,.13
),
armMat
);
leftArm.position.set(
-.42,1.22,0
);
rightArm.position.set(
.42,1.22,0
);
g.add(
leftArm,
rightArm
);
const eyeWhiteMat=
new THREE.MeshStandardMaterial({
color:0xffffff,
roughness:.35,
metalness:.02
});
const eyeBlackMat=
new THREE.MeshStandardMaterial({
color:0x050505,
roughness:.25,
metalness:.05
});
for(const s of [-1,1]){
const eye=new THREE.Mesh(
new THREE.SphereGeometry(
.052,10,8
),
eyeWhiteMat
);
eye.position.set(
s*.105,
1.99,
.255
);
g.add(eye);
const pupil=new THREE.Mesh(
new THREE.SphereGeometry(
.026,8,6
),
eyeBlackMat
);
pupil.position.set(
s*.105,
1.99,
.300
);
g.add(pupil);
}
const nameLabel=
createWorkerNameLabel(name);
nameLabel.position.set(
0,2.85,0
);
g.add(nameLabel);
g.position.set(
x,0,z
);
scene.add(g);
return {
group:g,
leftLeg,
rightLeg,
leftArm,
rightArm,
nameLabel,
phase:Math.random()*Math.PI*2
};
}
const workerRoutes=[
[
new THREE.Vector3(-60,0,15),
new THREE.Vector3(60,0,15)
],
[
new THREE.Vector3(60,0,-20),
new THREE.Vector3(-60,0,-20)
],
[
new THREE.Vector3(-60,0,-22),
new THREE.Vector3(-30,0,-22),
new THREE.Vector3(30,0,-22),
new THREE.Vector3(60,0,-22)
],
[
new THREE.Vector3(-60,0,57),
new THREE.Vector3(60,0,57)
]
];
const workerNames=[
"БАТ-ЭРДЭНЭ",
"ЭНХМАНДАХ",
"ЭНХБОЛД",
"ТӨРБОЛД",
"МАРАЛГУА",
"БИЛГҮҮН"
];
for(let i=0;i<workerNames.length;i++){
const route=
workerRoutes[
i%workerRoutes.length
];
const start=route[0];
const w=createWorker(
workerNames[i],
start.x,
start.z,
[
0x2d6f9d,
0x3c8d67,
0x8c6b2f,
0x7a4c9e,
0x3c6b85,
0x8b4a4a
][i]
);
w.route=route;
w.targetIndex=1;
w.speed=.9+(i%3)*.12;
w.direction=
i%2===0?1:-1;
officeWorkers.push(w);
walkingScanTargets.push(
w.group
);
}
function createCEO(
name,x,z
){
const g=new THREE.Group();
const suitMat=
new THREE.MeshStandardMaterial({
color:0x183c56,
roughness:.48,
metalness:.12
});
const body=createCapsuleMesh(.46,1.15,10,suitMat);
body.position.y=1.35;
g.add(body);
const head=new THREE.Mesh(
new THREE.SphereGeometry(
.36,18,14
),
new THREE.MeshStandardMaterial({
color:0xd7a57c,
roughness:.78
})
);
head.position.y=2.45;
g.add(head);
const hair=new THREE.Mesh(
new THREE.SphereGeometry(
.37,18,10,
0,Math.PI*2,
0,Math.PI*.55
),
new THREE.MeshStandardMaterial({
color:0x20242a,
roughness:.88
})
);
hair.position.y=2.58;
g.add(hair);
const pants=
new THREE.MeshStandardMaterial({
color:0x17232d,
roughness:.65
});
const leftLeg=new THREE.Mesh(
new THREE.BoxGeometry(
.18,.82,.22
),
pants
);
const rightLeg=new THREE.Mesh(
new THREE.BoxGeometry(
.18,.82,.22
),
pants
);
leftLeg.position.set(
-.20,.48,0
);
rightLeg.position.set(
.20,.48,0
);
g.add(
leftLeg,
rightLeg
);
const leftArm=new THREE.Mesh(
new THREE.BoxGeometry(
.17,.72,.18
),
suitMat
);
const rightArm=new THREE.Mesh(
new THREE.BoxGeometry(
.17,.72,.18
),
suitMat
);
leftArm.position.set(
-.56,1.42,0
);
rightArm.position.set(
.56,1.42,0
);
g.add(
leftArm,
rightArm
);
const white=
new THREE.MeshStandardMaterial({
color:0xffffff,
roughness:.35
});
const black=
new THREE.MeshStandardMaterial({
color:0x050505,
roughness:.25
});
for(const s of [-1,1]){
const eye=new THREE.Mesh(
new THREE.SphereGeometry(
.06,10,8
),
white
);
eye.position.set(
s*.13,
2.48,
.335
);
g.add(eye);
const pupil=new THREE.Mesh(
new THREE.SphereGeometry(
.029,8,6
),
black
);
pupil.position.set(
s*.13,
2.48,
.385
);
g.add(pupil);
}
const label=
createWorkerNameLabel(
name,
3.65,
.63,
"#ffd000",
50
);
label.position.set(
0,3.35,0
);
g.add(label);
g.position.set(
x,0,z
);
scene.add(g);
return {
group:g,
leftLeg,
rightLeg,
leftArm,
rightArm,
phase:Math.random()*Math.PI*2
};
}
const ceo=
createCEO(
"CEO БАТЖАРГАЛ",
0,
32
);
ceoScanTarget=ceo.group;
let ceoTime=0;
function animateCEO(){
if(!ceo)return;
ceoTime+=.035;
const step=
Math.sin(ceoTime)*.18;
ceo.leftLeg.rotation.x=step;
ceo.rightLeg.rotation.x=-step;
ceo.leftArm.rotation.x=
-step*.45;
ceo.rightArm.rotation.x=
step*.45;
}
const workerClock=
new THREE.Clock();
function animateOfficeWorkers(){
const dt=
Math.min(
workerClock.getDelta(),
.05
);
const now=
performance.now()*.006;
officeWorkers.forEach(w=>{
if(!w.route||w.route.length<2)return;
const target=
w.route[w.targetIndex];
const pos=
w.group.position;
const dx=
target.x-pos.x;
const dz=
target.z-pos.z;
const dist=
Math.hypot(dx,dz);
if(dist<.35){
w.targetIndex+=w.direction;
if(
w.targetIndex>=
w.route.length
){
w.direction=-1;
w.targetIndex=
w.route.length-2;
}
if(w.targetIndex<0){
w.direction=1;
w.targetIndex=1;
}
return;
}
const vx=dx/dist;
const vz=dz/dist;
const nextWorkerX=
pos.x+
vx*w.speed*dt;
const nextWorkerZ=
pos.z+
vz*w.speed*dt;
if(
canMove(
nextWorkerX,
nextWorkerZ
)
){
pos.x=nextWorkerX;
pos.z=nextWorkerZ;
}else{
w.targetIndex+=
w.direction;
if(
w.targetIndex>=
w.route.length
){
w.direction=-1;
w.targetIndex=
w.route.length-2;
}
if(w.targetIndex<0){
w.direction=1;
w.targetIndex=1;
}
}
w.group.rotation.y=
Math.atan2(vx,vz);
const step=
Math.sin(
now*w.speed+w.phase
);
w.leftLeg.rotation.x=
step*.55;
w.rightLeg.rotation.x=
-step*.55;
w.leftArm.rotation.x=
-step*.35;
w.rightArm.rotation.x=
step*.35;
});
}
const player={
position:new THREE.Vector3(
0,0,58
)
};
let yaw=0;
let pitch=-.12;
let forward=false;
let backward=false;
let left=false;
let right=false;
let gameRunning=false;
let gamePaused=false;
const playerModel=
new THREE.Group();
function createPlayerModel(){
const body=createCapsuleMesh(.38,.9,10,new THREE.MeshStandardMaterial({
color:0x126a86,
roughness:.55,
metalness:.1
}));
body.position.y=1.25;
playerModel.add(body);
const head=new THREE.Mesh(
new THREE.SphereGeometry(
.32,18,14
),
new THREE.MeshStandardMaterial({
color:0xd7a57c,
roughness:.8
})
);
head.position.y=2.15;
playerModel.add(head);
const hair=new THREE.Mesh(
new THREE.SphereGeometry(
.33,18,10,
0,Math.PI*2,
0,Math.PI*.55
),
new THREE.MeshStandardMaterial({
color:0x20242a,
roughness:.9
})
);
hair.position.y=2.27;
playerModel.add(hair);
const pantsMat=
new THREE.MeshStandardMaterial({
color:0x202b35,
roughness:.7
});
const leftLeg=new THREE.Mesh(
new THREE.BoxGeometry(
.16,.8,.2
),
pantsMat
);
const rightLeg=new THREE.Mesh(
new THREE.BoxGeometry(
.16,.8,.2
),
pantsMat
);
leftLeg.position.set(
-.18,.45,0
);
rightLeg.position.set(
.18,.45,0
);
playerModel.add(
leftLeg,
rightLeg
);
const armMat=
new THREE.MeshStandardMaterial({
color:0x126a86,
roughness:.55
});
const leftArm=new THREE.Mesh(
new THREE.BoxGeometry(
.15,.65,.16
),
armMat
);
const rightArm=new THREE.Mesh(
new THREE.BoxGeometry(
.15,.65,.16
),
armMat
);
leftArm.position.set(
-.48,1.28,0
);
rightArm.position.set(
.48,1.28,0
);
playerModel.add(
leftArm,
rightArm
);
playerModel.userData={
leftLeg,
rightLeg,
leftArm,
rightArm
};
scene.add(playerModel);
}
createPlayerModel();
document.addEventListener(
"keydown",
e=>{
if(!gameRunning)return;
if(e.code==="KeyW")forward=true;
if(e.code==="KeyS")backward=true;
if(e.code==="KeyA")left=true;
if(e.code==="KeyD")right=true;
}
);
document.addEventListener(
"keyup",
e=>{
if(e.code==="KeyW")forward=false;
if(e.code==="KeyS")backward=false;
if(e.code==="KeyA")left=false;
if(e.code==="KeyD")right=false;
}
);
const isMobileUA=
window.matchMedia(
"(max-width:800px)"
).matches||
"ontouchstart" in window||
navigator.maxTouchPoints>0;
/* ARCADELAB FIX: some hosts sandbox the page and refuse Pointer
Lock entirely (SecurityError). Never call requestPointerLock
without a try/catch, and fall back to click-and-drag look so
the game still works without pointer lock. */
function safeRequestPointerLock(){
try{
renderer.domElement.requestPointerLock();
}catch(e){
console.warn("Pointer lock unavailable:",e);
}
}
function safeExitPointerLock(){
try{
document.exitPointerLock();
}catch(e){}
}
let mouseDragging=false;
let dragLastX=0;
let dragLastY=0;
if(!isMobileUA){
renderer.domElement.addEventListener(
"mousedown",
e=>{
if(!gameRunning||gamePaused)return;
mouseDragging=true;
dragLastX=e.clientX;
dragLastY=e.clientY;
}
);
document.addEventListener(
"mouseup",
()=>{
mouseDragging=false;
}
);
document.addEventListener(
"mouseleave",
()=>{
mouseDragging=false;
}
);
}
document.addEventListener(
"mousemove",
e=>{
if(gamePaused)return;
if(
document.pointerLockElement===
renderer.domElement
){
yaw-=e.movementX*.002;
pitch-=e.movementY*.002;
pitch=Math.max(
-.75,
Math.min(.65,pitch)
);
return;
}
if(mouseDragging&&gameRunning){
const dx=e.clientX-dragLastX;
const dy=e.clientY-dragLastY;
dragLastX=e.clientX;
dragLastY=e.clientY;
yaw-=dx*.0035;
pitch-=dy*.0035;
pitch=Math.max(
-.75,
Math.min(.65,pitch)
);
}
}
);
const isMobile=isMobileUA;
if(isMobile){
const moveButtons=
document.querySelectorAll(
".mobileBtn[data-move]"
);
moveButtons.forEach(btn=>{
const key=
btn.dataset.move;
const setMove=value=>{
if(key==="forward")
forward=value;
if(key==="backward")
backward=value;
if(key==="left")
left=value;
if(key==="right")
right=value;
};
btn.addEventListener(
"touchstart",
e=>{
e.preventDefault();
setMove(true);
},
{passive:false}
);
btn.addEventListener(
"touchend",
e=>{
e.preventDefault();
setMove(false);
},
{passive:false}
);
btn.addEventListener(
"touchcancel",
e=>{
e.preventDefault();
setMove(false);
},
{passive:false}
);
});
let lookTouchId=null;
let lastTouchX=0;
let lastTouchY=0;
renderer.domElement.addEventListener(
"touchstart",
e=>{
if(!gameRunning||gamePaused)return;
const touch=
Array.from(
e.changedTouches
).find(
t=>t.clientX>
window.innerWidth*.28
);
if(!touch)return;
lookTouchId=
touch.identifier;
lastTouchX=
touch.clientX;
lastTouchY=
touch.clientY;
},
{passive:false}
);
renderer.domElement.addEventListener(
"touchmove",
e=>{
if(
lookTouchId===null||
gamePaused
)return;
const touch=
Array.from(
e.touches
).find(
t=>t.identifier===
lookTouchId
);
if(!touch)return;
const dx=
touch.clientX-lastTouchX;
const dy=
touch.clientY-lastTouchY;
lastTouchX=
touch.clientX;
lastTouchY=
touch.clientY;
yaw-=dx*.004;
pitch-=dy*.004;
pitch=Math.max(
-.75,
Math.min(.65,pitch)
);
e.preventDefault();
},
{passive:false}
);
renderer.domElement.addEventListener(
"touchend",
e=>{
if(
Array.from(
e.changedTouches
).some(
t=>t.identifier===
lookTouchId
)
){
lookTouchId=null;
}
},
{passive:false}
);
document.getElementById(
"mobileScan"
).addEventListener(
"touchstart",
e=>{
e.preventDefault();
if(
!gameRunning||
gamePaused
)return;
shoot();
},
{passive:false}
);
}
const risks=[
{
name:"ЗЭЭЛИЙН ЭРСДЭЛ",
icon:"💳",
color:0xff8800,
scenario:
"Харилцагч зээл хүсэж байна. Түүний өрийн хэмжээ богино хугацаанд огцом өсөж, эргэн төлөлтийн чадвар буурч байна. Ямар арга хэмжээ авах вэ?",
options:[
{
text:"Зээлийг батлаад олголтын дараа эргэн төлөлтийг хянах.",
result:"ЗЭЭЛИЙН АЛДАГДАЛ ҮҮСЛЭЭ",
impact:-25,
points:0,
quality:"weak"
},
{
text:"Нэмэлт мэдээлэл шалгуулж, гар аргаар дахин үнэлгээ хийх.",
result:"ЗЭЭЛИЙН ЭРСДЭЛ БУУРЛАА",
impact:-10,
points:50,
quality:"medium"
},
{
text:"Төлөх чадварын шалгуур хэрэглэж, эрсдэлтэй зээлийн хэмжээг автоматаар хязгаарлах.",
result:"ЗЭЭЛИЙН ЭРСДЭЛ СААРМАГЖЛАА",
impact:0,
points:100,
quality:"strong"
}
]
},
{
name:"ЛУЙВРЫН ЭРСДЭЛ",
icon:"🥷",
color:0xff1744,
scenario:
"Хэд хэдэн зээлийн хүсэлтэд ижил төстэй мэдээлэл болон хэвийн бус зан төлөвийн хэв шинж илэрлээ. Та ямар арга хэмжээ авах вэ?",
options:[
{
text:"Луйвар батлагдаагүй тул хүсэлтүүдийг хэвийн үргэлжлүүлэн боловсруулах.",
result:"ЛУЙВРЫН ЭРСДЭЛ БИЕЛЭВ",
impact:-25,
points:0,
quality:"weak"
},
{
text:"Сэжигтэй хүсэлтүүдийг шалгагч ажилтанд шилжүүлж шалгуулах.",
result:"ЛУЙВРЫН ЭРСДЭЛ БУУРЛАА",
impact:-10,
points:50,
quality:"medium"
},
{
text:"Луйвар илрүүлэх хяналтыг ажиллуулж, сэжигтэй хүсэлтүүдийг блоклох.",
result:"ЛУЙВРЫН ЭРСДЭЛ СААРМАГЖЛАА",
impact:0,
points:100,
quality:"strong"
}
]
},
{
name:"ҮЙЛ АЖИЛЛАГААНЫ ЭРСДЭЛ",
icon:"⚙",
color:0xffcc00,
scenario:
"Нэг ажилтан зээлийн дүнг боловсруулах системд буруу орууллаа.",
options:[
{
text:"Асуудал үүсвэл дараа нь засварлах.",
result:"ҮЙЛ АЖИЛЛАГААНЫ ДОГОЛДОЛ ҮҮСЛЭЭ",
impact:-25,
points:0,
quality:"weak"
},
{
text:"Нэмэлт гарын үсэг болон баталгаажуулалтын алхам оруулах.",
result:"ҮЙЛ АЖИЛЛАГААНЫ ЭРСДЭЛ БУУРЛАА",
impact:-10,
points:50,
quality:"medium"
},
{
text:"Гүйлгээ дуусахаас өмнө дүнг автоматаар шалгах хяналт нэвтрүүлэх.",
result:"ҮЙЛ АЖИЛЛАГААНЫ ЭРСДЭЛ СААРМАГЖЛАА",
impact:0,
points:100,
quality:"strong"
}
]
},
{
name:"КИБЕР ЭРСДЭЛ",
icon:"◉",
color:0x9933ff,
scenario:
"Танихгүй байршлуудаас олон тооны хэвийн бус нэвтрэх оролдлого илэрлээ.",
options:[
{
text:"Нэмэлт мэдээлэл гарах хүртэл ажиглаад хүлээх.",
result:"АЮУЛГҮЙ БАЙДЛЫН ЗӨРЧИЛ ҮҮСЛЭЭ",
impact:-25,
points:0,
quality:"weak"
},
{
text:"Нөлөөлөлд өртсөн хэрэглэгчийн нэвтрэх эрхийн мэдээллийг шинэчлэх.",
result:"КИБЕР ЭРСДЭЛ БУУРЛАА",
impact:-10,
points:50,
quality:"medium"
},
{
text:"Сэжигтэй хандалтыг блоклож, зөрчилд хариу арга хэмжээ авах хяналтыг ажиллуулах.",
result:"КИБЕР ЭРСДЭЛ СААРМАГЖЛАА",
impact:0,
points:100,
quality:"strong"
}
]
},
{
name:"СКОРИНГИЙН ЗАГВАРЫН ЭРСДЭЛ",
icon:"▲",
color:0x00aaff,
scenario:
"Зээлийн скорингийн загварын гүйцэтгэл буурч эхэллээ.",
options:[
{
text:"Өмнө нь сайн ажиллаж байсан тул загварыг хэвийн ашиглах.",
result:"СКОРИНГИЙН ЗАГВАРЫН АЛДАА ҮҮСЛЭЭ",
impact:-25,
points:0,
quality:"weak"
},
{
text:"Алдаатай байж болзошгүй зээлийн шийдвэрүүдийг гараар шалгах",
result:"СКОРИНГИЙН ЗАГВАРЫН ЭРСДЭЛ БУУРЛАА",
impact:-10,
points:50,
quality:"medium"
},
{
text:"Скорингийн загварыг дахин шалгаж, шаардлагатай бол тохируулах",
result:"СКОРИНГИЙН ЗАГВАРЫН ЭРСДЭЛ СААРМАГЖЛАА",
impact:0,
points:100,
quality:"strong"
}
]
},
{
name:"НИЙЦЛИЙН ЭРСДЭЛ",
icon:"⚖",
color:0xffffff,
scenario:
"Шинэ зохицуулалтын шаардлага гарч, харилцагчийн мэдээллийг боловсруулах арга өөрчлөгдөхөөр боллоо.",
options:[
{
text:"Асуудал гарч иртэл одоогийн үйл ажиллагааг хэвээр үргэлжлүүлэх.",
result:"ЗОХИЦУУЛАЛТЫН ЗӨРЧИЛ ҮҮСЛЭЭ",
impact:-25,
points:0,
quality:"weak"
},
{
text:"Үйл ажиллагааны журмыг шинэчилж, өөрчлөлтийг ажилтнуудад мэдээлэх.",
result:"НИЙЦЛИЙН ЭРСДЭЛ БУУРЛАА",
impact:-10,
points:50,
quality:"medium"
},
{
text:"Хяналтын арга хэмжээ нэвтрүүлж, нийцлийг тогтмол хянах.",
result:"НИЙЦЛИЙН ЭРСДЭЛ СААРМАГЖЛАА",
impact:0,
points:100,
quality:"strong"
}
]
},
{
name:"ХҮНИЙ АЛДААНААС ҮҮСЭХ ЭРСДЭЛ",
icon:"!",
color:0xff33aa,
scenario:
"Харилцагчийн нууц мэдээллийг буруу хүлээн авагч руу илгээхээс хэрхэн сэргийлэх вэ?",
options:[
{
text:"Асуудал илэрвэл дараа нь засах.",
result:"МАБ ЗӨРЧИЛ ҮҮСЛЭЭ",
impact:-25,
points:0,
quality:"weak"
},
{
text:"Мэдээллийн ангилал болон хүлээн авагчийг шалгах",
result:"ХҮНИЙ АЛДААНААС ҮҮСЭХ ЭРСДЭЛ БУУРЛАА",
impact:-10,
points:50,
quality:"medium"
},
{
text:"Гадагш чиглэсэн мэдээлэл дамжуулалтад хяналттай болох",
result:"ТУХАЙН ЭРСДЭЛЭЭС СЭРГИЙЛЭГДЛЭЭ",
impact:0,
points:100,
quality:"strong"
}
]
},
{
name:"ПРОЦЕССЫН ЭРСДЭЛ",
icon:"⌘",
color:0xff5500,
scenario:
"Процессын өөрчлөлтийн улмаас заавал хийх ёстой баталгаажуулалтын алхам алгасагджээ.",
options:[
{
text:"Үйл явцыг үргэлжлүүлээд асуудлыг дараа нь шийдэх.",
result:"ПРОЦЕССЫН АЛДАА ҮҮСЛЭЭ",
impact:-25,
points:0,
quality:"weak"
},
{
text:"Нөлөөлөлд өртсөн тохиолдлуудыг менежерт дахин хянуулах.",
result:"ПРОЦЕССЫН ЭРСДЭЛ БУУРЛАА",
impact:-10,
points:50,
quality:"medium"
},
{
text:"Заавал хийх процессын алхмуудыг автоматаар шалгах хяналт нэвтрүүлэх.",
result:"ПРОЦЕССЫН ЭРСДЭЛ СААРМАГЖЛАА",
impact:0,
points:100,
quality:"strong"
}
]
},
{
name:"СИСТЕМИЙН ЭРСДЭЛ",
icon:"▣",
color:0x00ffaa,
scenario:
"Гүйлгээний оргил ачааллын үеэр бизнесийн чухал систем тогтворгүй болж эхэллээ.",
options:[
{
text:"Системийн ачаалал буурахыг хүлээгээд хэвийн ажиллагааг үргэлжлүүлэх",
result:"СИСТЕМИЙН СААТАЛ ҮҮСЛЭЭ",
impact:-25,
points:0,
quality:"weak"
},
{
text:"Системийн ачааллыг тогтмол шалгах",
result:"СИСТЕМИЙН ЭРСДЭЛ БУУРЛАА",
impact:-10,
points:50,
quality:"medium"
},
{
text:"Нөөц системийг ажиллуулах, автоматаар хянах",
result:"СИСТЕМИЙН ЭРСДЭЛ СААРМАГЖЛАА",
impact:0,
points:100,
quality:"strong"
}
]
},
{
name:"НЭР ХҮНДИЙН ЭРСДЭЛ",
icon:"◎",
color:0xff3366,
scenario:
"Харилцагчийн гомдол сошиал орчинд маш хурдан тархаж эхэллээ.",
options:[
{
text:"Нөхцөл байдал тодорхой болтол хариу өгөхгүй байх.",
result:"НЭР ХҮНДИЙН ХОХИРОЛ ҮҮСЛЭЭ",
impact:-25,
points:0,
quality:"weak"
},
{
text:"Олон нийтэд өгөх хариу мэдэгдлийг бэлтгэх.",
result:"НЭР ХҮНДИЙН ЭРСДЭЛ БУУРЛАА",
impact:-10,
points:50,
quality:"medium"
},
{
text:"Нөхцөл байдалд шуурхай арга хэмжээ авч, асуудлыг шийдвэрлэх.",
result:"НЭР ХҮНДИЙН ЭРСДЭЛ СААРМАГЖЛАА",
impact:0,
points:100,
quality:"strong"
}
]
}
];
let currentRiskIndex=0;
let health=100;
let score=0;
let correctCount=0;
let mediumCount=0;
let weakCount=0;
let currentEnemy=null;
const raycaster=
new THREE.Raycaster();
const center=
new THREE.Vector2(0,0);
let missionTime=240;
let missionInterval=null;
function formatTime(seconds){
const m=
Math.floor(seconds/60);
const s=
seconds%60;
return String(m).padStart(2,"0")
+
":"
+
String(s).padStart(2,"0");
}
function startMissionTimer(){
missionInterval=
setInterval(()=>{
if(
!gameRunning||
gamePaused
)return;
missionTime--;
document.getElementById(
"missionTimer"
).innerText=
formatTime(missionTime);
if(missionTime<=0){
clearInterval(
missionInterval
);
endGame(
"ХУГАЦАА ДУУССАН"
);
}
},1000);
}
let decisionTime=15;
let decisionInterval=null;
function startDecisionTimer(){
decisionTime=15;
document.getElementById(
"decisionTimer"
).innerText=
decisionTime;
decisionInterval=
setInterval(()=>{
decisionTime--;
document.getElementById(
"decisionTimer"
).innerText=
decisionTime;
if(decisionTime<=0){
clearInterval(
decisionInterval
);
timeoutDecision();
}
},1000);
}
function timeoutDecision(){
health-=15;
updateHUD();
flashEffect(
"flash-red"
);
document.getElementById(
"result"
).style.display=
"block";
document.getElementById(
"resultTitle"
).style.color=
"#ff3344";
document.getElementById(
"resultTitle"
).innerText=
"ШИЙДВЭР ХОЦОРЛОО";
document.getElementById(
"resultStats"
).innerHTML=
"Эрсдэлийн өртөлт нэмэгдлээ.<br><br>Компанийн эрүүл мэнд: -15%";
document.getElementById(
"options"
).style.pointerEvents=
"none";
setTimeout(
continueGame,
2200
);
}
const spawnPoints=[
[-45,-45],
[43.8,-48.3],
[-33.5,30],
[37,31.4],
[-15,-35],
[15,40],
[-47.5,10.2],
[48,15],
[-12,0],
[15,0]
];
function shuffle(array){
return [
...array
].sort(
()=>Math.random()-.5
);
}
const randomizedSpawn=
shuffle(spawnPoints);
function spawnEnemy(){
if(
currentRiskIndex>=
risks.length
){
endGame();
return;
}
const risk=
risks[currentRiskIndex];
const group=
new THREE.Group();
const body=createCapsuleMesh(1.1,2.2,16,new THREE.MeshStandardMaterial({
color:risk.color,
emissive:risk.color,
emissiveIntensity:.6,
metalness:.5,
roughness:.25
}));
body.position.y=2.2;
group.add(body);
const head=new THREE.Mesh(
new THREE.SphereGeometry(
.85,24,24
),
new THREE.MeshStandardMaterial({
color:0x111111,
emissive:risk.color,
emissiveIntensity:.3
})
);
head.position.y=4.3;
group.add(head);
const core=new THREE.Mesh(
new THREE.SphereGeometry(
.35,16,16
),
new THREE.MeshBasicMaterial({
color:0xffffff
})
);
core.position.set(
0,2.7,1.05
);
group.add(core);
const ring=new THREE.Mesh(
new THREE.TorusGeometry(
1.6,.05,8,32
),
new THREE.MeshBasicMaterial({
color:risk.color
})
);
ring.rotation.x=
Math.PI/2;
ring.position.y=2.5;
group.add(ring);
const point=
randomizedSpawn[
currentRiskIndex
];
group.position.set(
point[0],
0,
point[1]
);
group.userData={
risk,
ring,
baseY:0
};
scene.add(group);
currentEnemy=group;
document.getElementById(
"riskCounter"
).innerText=
`ИЛРҮҮЛСЭН ЭРСДЭЛ: ${currentRiskIndex} / 10`;
}
const RISK_INTERACTION_DISTANCE=9.5;
const WALKING_MESSAGES=[
"Good luck!",
"Орой яахын!",
"Simple day-р уунаа!",
"Өлсөөд байна!",
"Хөөрхөн шүү!"
];
const SEATED_MESSAGE="Busy!";
const CEO_MESSAGE="Win! Win! Win!";
function showScanMessage(
message,
type
){
const el=
document.getElementById(
"encourage"
);
const text=
document.getElementById(
"encourageText"
);
text.innerText=message;
el.classList.remove(
"walking",
"seated",
"ceo"
);
if(type){
el.classList.add(type);
}
el.style.display="block";
clearTimeout(
window.__encourageMessage
);
window.__encourageMessage=
setTimeout(()=>{
el.style.display="none";
},900);
}
function getFirstHit(targets){
if(
!targets||
targets.length===0
){
return null;
}
const hits=
raycaster.intersectObjects(
targets,
true
);
if(
hits.length===0
){
return null;
}
return hits[0];
}
function belongsToTarget(
object,
targets
){
let obj=object;
while(obj){
for(
const target of targets
){
if(obj===target){
return target;
}
}
obj=obj.parent;
}
return null;
}
function shoot(){
if(
!gameRunning||
gamePaused
)return;
raycaster.setFromCamera(
center,
camera
);
if(currentEnemy){
const distanceToRisk=
player.position.distanceTo(
currentEnemy.position
);
if(
distanceToRisk>
RISK_INTERACTION_DISTANCE
){
if(ceoScanTarget){
const ceoHit=
getFirstHit([
ceoScanTarget
]);
if(ceoHit){
showScanMessage(
CEO_MESSAGE,
"ceo"
);
shootAnimation();
return;
}
}
const walkingHit=
getFirstHit(
walkingScanTargets
);
if(walkingHit){
showScanMessage(
WALKING_MESSAGES[
Math.floor(
Math.random()*
WALKING_MESSAGES.length
)
],
"walking"
);
shootAnimation();
return;
}
const seatedHit=
getFirstHit(
seatedScanTargets
);
if(seatedHit){
showScanMessage(
SEATED_MESSAGE,
"seated"
);
shootAnimation();
return;
}
const detected=
document.getElementById(
"detected"
);
document.getElementById(
"detectedName"
).innerText=
"ОЙРТОЖ БАЙЖ СКАН ХИЙНЭ ҮҮ";
detected.style.display=
"block";
clearTimeout(
window.__riskApproachMessage
);
window.__riskApproachMessage=
setTimeout(()=>{
detected.style.display=
"none";
},700);
return;
}
}
shootAnimation();
if(currentEnemy){
const hits=
raycaster.intersectObject(
currentEnemy,
true
);
if(hits.length>0){
identifyRisk();
return;
}
}
if(ceoScanTarget){
const ceoHit=
getFirstHit([
ceoScanTarget
]);
if(ceoHit){
showScanMessage(
CEO_MESSAGE,
"ceo"
);
return;
}
}
const walkingHit=
getFirstHit(
walkingScanTargets
);
if(walkingHit){
showScanMessage(
WALKING_MESSAGES[
Math.floor(
Math.random()*
WALKING_MESSAGES.length
)
],
"walking"
);
return;
}
const seatedHit=
getFirstHit(
seatedScanTargets
);
if(seatedHit){
showScanMessage(
SEATED_MESSAGE,
"seated"
);
return;
}
}
function shootAnimation(){
const crosshair=
document.getElementById(
"crosshair"
);
crosshair.classList.add(
"recoil"
);
setTimeout(()=>{
crosshair.classList.remove(
"recoil"
);
},100);
const overlay=
document.getElementById(
"scanOverlay"
);
overlay.classList.add(
"scanActive"
);
setTimeout(()=>{
overlay.classList.remove(
"scanActive"
);
},350);
const dir=
new THREE.Vector3();
camera.getWorldDirection(
dir
);
const geometry=
new THREE.BufferGeometry()
.setFromPoints([
camera.position.clone(),
camera.position.clone()
.add(
dir.multiplyScalar(50)
)
]);
const material=
new THREE.LineBasicMaterial({
color:0x00ff99
});
const beam=
new THREE.Line(
geometry,
material
);
scene.add(beam);
setTimeout(()=>{
scene.remove(beam);
},100);
}
function identifyRisk(){
gamePaused=true;
const risk=
currentEnemy.userData.risk;
scene.remove(
currentEnemy
);
currentEnemy=null;
const detected=
document.getElementById(
"detected"
);
document.getElementById(
"detectedName"
).innerText=
risk.name;
detected.style.display=
"block";
setTimeout(()=>{
detected.style.display=
"none";
openRiskModal(
risk
);
},1100);
}
function openRiskModal(risk){
safeExitPointerLock();
const modal=
document.getElementById(
"riskModal"
);
modal.style.display="flex";
document.getElementById(
"riskTitle"
).innerText=
risk.name+
" ИЛЭРЛЭЭ";
document.getElementById(
"scenario"
).innerText=
risk.scenario;
const options=
document.getElementById(
"options"
);
options.innerHTML="";
options.style.pointerEvents=
"auto";
document.getElementById(
"result"
).style.display=
"none";
document.getElementById(
"analyzing"
).style.display=
"none";
options.dataset.locked=
"false";
const randomizedOptions=
shuffle(
risk.options
);
const letters=[
"A","B","C"
];
randomizedOptions.forEach(
(option,index)=>{
const div=
document.createElement(
"div"
);
div.className=
"option";
div.innerHTML=`
<div class="optionKey">
${letters[index]} ХУВИЛБАР
</div>
${option.text}
`;
div.onclick=()=>{
if(
options.dataset.locked===
"true"
)return;
chooseOption(
option,
div
);
};
options.appendChild(div);
}
);
startDecisionTimer();
}
function chooseOption(
option,
selectedElement
){
const options=
document.getElementById(
"options"
);
if(
options.dataset.locked===
"true"
)return;
options.dataset.locked=
"true";
clearInterval(
decisionInterval
);
const allOptions=
options.querySelectorAll(
".option"
);
allOptions.forEach(el=>{
el.classList.add(
"optionLocked"
);
});
selectedElement.classList.remove(
"optionLocked"
);
selectedElement.classList.add(
"optionSelected"
);
options.style.pointerEvents=
"none";
document.getElementById(
"analyzing"
).style.display=
"block";
setTimeout(()=>{
document.getElementById(
"analyzing"
).style.display=
"none";
applyDecision(option);
},900);
}
function applyDecision(option){
health+=option.impact;
let speedBonus=
Math.max(
0,
decisionTime*5
);
let totalPoints=
option.points+
speedBonus;
if(option.quality==="weak"){
totalPoints=0;
weakCount++;
}
if(
option.quality==="medium"
){
mediumCount++;
}
if(
option.quality==="strong"
){
correctCount++;
}
score+=totalPoints;
if(health<0)
health=0;
updateHUD();
const result=
document.getElementById(
"result"
);
const title=
document.getElementById(
"resultTitle"
);
const stats=
document.getElementById(
"resultStats"
);
result.style.display=
"block";
const healthChange=
option.impact;
if(
option.quality===
"weak"
){
title.style.color=
"#ff3344";
flashEffect(
"flash-red"
);
title.innerText=
option.result;
stats.innerHTML=`
КОМПАНИЙН ЭРҮҮЛ МЭНД:
${healthChange}%
<br>
ХУРДНЫ НЭМЭЛТ: 0
<br>
ЭРСДЭЛИЙН ОНОО: +0
`;
document.body.classList.add(
"shake"
);
setTimeout(()=>{
document.body.classList.remove(
"shake"
);
},350);
}
if(
option.quality===
"medium"
){
title.style.color=
"#ffd000";
flashEffect(
"flash-yellow"
);
title.innerText=
option.result;
stats.innerHTML=`
КОМПАНИЙН ЭРҮҮЛ МЭНД:
${healthChange}%
<br>
ХУРДНЫ НЭМЭЛТ:
+${speedBonus}
<br>
ЭРСДЭЛИЙН ОНОО:
+${totalPoints}
`;
}
if(
option.quality===
"strong"
){
title.style.color=
"#00ff99";
flashEffect(
"flash-green"
);
title.innerText=
option.result;
stats.innerHTML=`
КОМПАНИЙН ЭРҮҮЛ МЭНД:
ХОХИРОЛГҮЙ
<br>
ХУРДНЫ НЭМЭЛТ:
+${speedBonus}
<br>
ЭРСДЭЛИЙН ОНОО:
+${totalPoints}
`;
}
setTimeout(
continueGame,
2400
);
}
function continueGame(){
document.getElementById(
"riskModal"
).style.display=
"none";
document.getElementById(
"options"
).style.pointerEvents=
"auto";
document.getElementById(
"options"
).dataset.locked=
"false";
currentRiskIndex++;
gamePaused=false;
if(health<=0){
endGame(
"КОМПАНИЙН ЭРҮҮЛ МЭНД НОЦТОЙ БУУРЛАА"
);
return;
}
spawnEnemy();
setTimeout(()=>{
if(
gameRunning&&
!isMobile
){
safeRequestPointerLock();
}
},200);
}
function flashEffect(className){
const flash=
document.getElementById(
"flash"
);
flash.className=
className;
flash.style.opacity=1;
setTimeout(()=>{
flash.style.opacity=0;
},450);
}
function updateHUD(){
document.getElementById(
"healthText"
).innerText=
health+"%";
document.getElementById(
"healthFill"
).style.width=
health+"%";
document.getElementById(
"score"
).innerText=
score;
let color=
"#00d977";
if(health<=70)
color="#ffd000";
if(health<=40)
color="#ff3344";
document.getElementById(
"healthFill"
).style.background=
color;
}
function endGame(
reason="ДААЛГАВАР ДУУСЛАА"
){
gameRunning=false;
clearInterval(
missionInterval
);
clearInterval(
decisionInterval
);
safeExitPointerLock();
document.getElementById(
"finalScore"
).innerText=
score;
const type=
document.getElementById(
"managerType"
);
const text=
document.getElementById(
"managerText"
);
const accuracy=
Math.round(
(correctCount/
Math.max(
1,
currentRiskIndex
))*100
);
if(score<400){
type.innerText=
"🔥 ЭРСДЭЛИЙН ГАЛ УНТРААГЧ";
type.style.color=
"#ff3344";
text.innerHTML=`
<b>${reason}</b>
<br><br>
Та эрсдэлийг асуудал болсны дараа
хариу арга хэмжээ авч байна.
<br><br>
Хүчтэй шийдвэр:
${correctCount}
<br>
Эрсдэлийн хохирол:
${weakCount}
`;
}else if(score<850){
type.innerText=
"🛡 ЭРСДЭЛИЙН ХЯНАГЧ";
type.style.color=
"#ffd000";
text.innerHTML=`
<b>${reason}</b>
<br><br>
Та эрсдэлийг сайн илрүүлж,
хянаж чаддаг байна.
Гэхдээ урьдчилан сэргийлэх
шийдвэр гаргах боломж бий.
<br><br>
Хүчтэй шийдвэр:
${correctCount}
<br>
Нарийвчлал:
${accuracy}%
`;
}else{
type.innerText=
"👑 ЭРСДЭЛИЙН МАСТЕР";
type.style.color=
"#00ff99";
text.innerHTML=`
<b>${reason}</b>
<br><br>
Та эрсдэлийг эрт илрүүлж,
урьдчилан сэргийлэх хүчтэй
хяналтын арга хэмжээг тогтмол сонголоо.
<br><br>
Хүчтэй шийдвэр:
${correctCount}
<br>
Нарийвчлал:
${accuracy}%
`;
}
}
function canMove(x,z){
if(
x<-73.2||
x>73.2||
z<-73.2||
z>73.2
){
return false;
}
for(
const o of obstacles
){
if(
x>
o.x-o.w/2-1 &&
x<
o.x+o.w/2+1 &&
z>
o.z-o.d/2-1 &&
z<
o.z+o.d/2+1
){
return false;
}
}
return true;
}
const clock=
new THREE.Clock();
function updatePlayer(){
const delta=
clock.getDelta();
const speed=11;
let dx=0;
let dz=0;
if(forward)dz-=1;
if(backward)dz+=1;
if(left)dx-=1;
if(right)dx+=1;
if(
dx===0&&
dz===0
){
animatePlayer(false);
return;
}
const length=
Math.sqrt(
dx*dx+
dz*dz
);
dx/=length;
dz/=length;
const forwardVector=
new THREE.Vector3(
0,0,-1
);
forwardVector.applyAxisAngle(
new THREE.Vector3(0,1,0),
yaw
);
const rightVector=
new THREE.Vector3(
1,0,0
);
rightVector.applyAxisAngle(
new THREE.Vector3(0,1,0),
yaw
);
const moveX=(
forwardVector.x*-dz+
rightVector.x*dx
)*
speed*
delta;
const moveZ=(
forwardVector.z*-dz+
rightVector.z*dx
)*
speed*
delta;
const nextX=
player.position.x+
moveX;
if(
canMove(
nextX,
player.position.z
)
){
player.position.x=
nextX;
}
const nextZ=
player.position.z+
moveZ;
if(
canMove(
player.position.x,
nextZ
)
){
player.position.z=
nextZ;
}
animatePlayer(true);
const movementAngle=
Math.atan2(
moveX,
moveZ
);
playerModel.rotation.y=
movementAngle;
}
let playerWalkTime=0;
function animatePlayer(
isMoving
){
const p=
playerModel.userData;
if(!p)return;
if(isMoving){
playerWalkTime+=.18;
const step=
Math.sin(
playerWalkTime
);
p.leftLeg.rotation.x=
step*.65;
p.rightLeg.rotation.x=
-step*.65;
p.leftArm.rotation.x=
-step*.35;
p.rightArm.rotation.x=
step*.35;
}else{
p.leftLeg.rotation.x*=.82;
p.rightLeg.rotation.x*=.82;
p.leftArm.rotation.x*=.82;
p.rightArm.rotation.x*=.82;
}
}
function updateThirdPersonCamera(){
const target=
new THREE.Vector3(
player.position.x,
1.65,
player.position.z
);
const distance=7.2;
const height=2.7;
const cameraOffset=
new THREE.Vector3(
Math.sin(yaw)*distance,
height,
Math.cos(yaw)*distance
);
const desired=
target.clone()
.add(cameraOffset);
camera.position.lerp(
desired,
.16
);
const lookTarget=
new THREE.Vector3(
player.position.x,
1.8,
player.position.z
);
camera.lookAt(
lookTarget
);
}
let enemyAnimation=0;
function animateEnemy(){
if(!currentEnemy)return;
enemyAnimation+=.03;
currentEnemy.position.y=
Math.sin(
enemyAnimation
)*.35;
currentEnemy.rotation.y+=.015;
if(
currentEnemy.userData.ring
){
currentEnemy.userData.ring.rotation.z+=.04;
}
}
renderer.domElement.addEventListener(
"click",
()=>{
if(
!gameRunning||
gamePaused
)return;
if(isMobile)
return;
if(
document.pointerLockElement!==
renderer.domElement
){
safeRequestPointerLock();
}
shoot();
}
);
window.__RISK_START_GAME = function(){
const input=
document.getElementById(
"playerNameInput"
);
const name=
input.value.trim();
document.getElementById(
"playerDisplay"
).innerText=
name||
"ЭРСДЭЛИЙН МЕНЕЖЕР";
document.getElementById(
"startScreen"
).classList.add(
"hidden"
);
gameRunning=true;
player.position.set(
0,0,58
);
playerModel.position.copy(
player.position
);
if(!isMobile){
try{
renderer.domElement.requestPointerLock();
}catch(e){
console.warn("Pointer lock unavailable:",e);
}
}
spawnEnemy();
startMissionTimer();
};
if(window.__RISK_START_REQUESTED){
window.__RISK_START_GAME();
}
function animate(){
requestAnimationFrame(
animate
);
if(
gameRunning&&
!gamePaused
){
updatePlayer();
playerModel.position.copy(
player.position
);
animateEnemy();
animateCEO();
updateThirdPersonCamera();
}else{
playerModel.position.copy(
player.position
);
}
animateOfficeWorkers();
renderer.render(
scene,
camera
);
}
animate();
window.addEventListener(
"resize",
()=>{
camera.aspect=
window.innerWidth/
window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(
window.innerWidth,
window.innerHeight
);
}
);
</script>
<script>
window.addEventListener("error", function(e){
console.error("RISK STRIKE runtime error:", e.error || e.message);
});
window.addEventListener("unhandledrejection", function(e){
console.error("RISK STRIKE promise error:", e.reason);
});
</script>
</script>
</body>
</html>
Game Source: RISK STRIKE V3
Creator: EpicNinja33
Libraries: three
Complexity: complex (5375 lines, 70.9 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: risk-strike-v3-epicninja33" to link back to the original. Then publish at arcadelab.ai/publish.