🎮ArcadeLab

RISK STRIKE V3

by EpicNinja33
5375 lines70.9 KB🛠️ Three.js (3D graphics)
▶ Play
<!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.