Meta Description" name="description" />
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JARVIS 3D Holographic Armor Interface</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Three.js and OrbitControls -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<!-- FontAwesome Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;800;900&family=Rajdhani:wght@500;600;700&display=swap" rel="stylesheet">
<style>
* {
user-select: none;
-webkit-user-select: none;
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #020617;
font-family: 'Rajdhani', sans-serif;
color: #38bdf8;
}
.font-orbitron {
font-family: 'Orbitron', sans-serif;
}
/* Glassmorphism HUD Style */
.hud-glass {
background: rgba(8, 20, 42, 0.55);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(56, 189, 248, 0.25);
box-shadow: 0 0 20px rgba(14, 165, 233, 0.15), inset 0 0 15px rgba(56, 189, 248, 0.08);
}
.hud-glass-gold {
background: rgba(30, 20, 5, 0.55);
backdrop-filter: blur(12px);
border: 1px solid rgba(245, 158, 11, 0.35);
box-shadow: 0 0 20px rgba(245, 158, 11, 0.2), inset 0 0 15px rgba(245, 158, 11, 0.1);
}
.hud-border-glow {
box-shadow: 0 0 10px rgba(56, 189, 248, 0.4);
}
/* Scanline Overlay */
.scanline-bg {
background: linear-gradient(
to bottom,
rgba(255,255,255,0),
rgba(255,255,255,0) 50%,
rgba(0, 200, 255, 0.04) 50%,
rgba(0, 200, 255, 0.04)
);
background-size: 100% 4px;
}
/* Hologram Grid Overlay */
#canvas-container {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
/* UI Overlays */
.hud-overlay {
position: absolute;
z-index: 10;
transition: opacity 0.5s ease, transform 0.5s ease;
pointer-events: auto;
}
.hud-hidden {
opacity: 0 !important;
pointer-events: none !important;
transform: scale(0.98);
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 4px;
}
::-webkit-scrollbar-track {
background: rgba(2, 6, 23, 0.6);
}
::-webkit-scrollbar-thumb {
background: rgba(56, 189, 248, 0.4);
border-radius: 2px;
}
/* Pulse Animations */
@keyframes arcPulse {
0% { box-shadow: 0 0 5px #06b6d4, inset 0 0 5px #06b6d4; }
50% { box-shadow: 0 0 22px #38bdf8, inset 0 0 15px #38bdf8; }
100% { box-shadow: 0 0 5px #06b6d4, inset 0 0 5px #06b6d4; }
}
.arc-reactor-glow {
animation: arcPulse 2.5s infinite ease-in-out;
}
/* Holographic Part Tooltip Pin */
.part-label {
position: absolute;
z-index: 20;
transform: translate(-50%, -100%);
pointer-events: none;
transition: opacity 0.3s ease;
}
.range-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: #38bdf8;
cursor: pointer;
box-shadow: 0 0 8px #38bdf8;
}
</style>
</head>
<body class="scanline-bg select-none overflow-hidden">
<!-- 3D WebGL Canvas Container -->
<div id="canvas-container"></div>
<!-- FOCUS MODE RESTORE HINT (Visible only when UI hidden) -->
<div id="focus-restore-hint" class="fixed top-6 left-1/2 -translate-x-1/2 z-30 pointer-events-none opacity-0 transition-opacity duration-500 text-center">
<div class="hud-glass px-6 py-2 rounded-full border border-cyan-400/40 text-cyan-300 font-orbitron text-xs tracking-widest flex items-center gap-3">
<span class="w-2 h-2 rounded-full bg-cyan-400 animate-ping"></span>
HOLOGRAM FOCUS MODE ACTIVE • CLICK ANYWHERE OR SAY "SHOW HUD"
</div>
</div>
<!-- TOP HEADER / JARVIS STATUS BAR -->
<header id="hud-header" class="hud-overlay top-0 left-0 right-0 p-4 flex justify-between items-center border-b border-cyan-500/20 bg-slate-950/40">
<div class="flex items-center gap-4">
<div class="w-10 h-10 rounded-full border-2 border-cyan-400/60 flex items-center justify-center arc-reactor-glow bg-cyan-950/40">
<i class="fa-solid fa-atom text-cyan-300 text-lg animate-spin" style="animation-duration: 8s;"></i>
</div>
<div>
<h1 class="font-orbitron text-lg font-bold tracking-widest text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 via-sky-200 to-amber-300">
STARK INDUSTRIES // MARK 85
</h1>
<p class="text-xs text-cyan-400/70 tracking-wider flex items-center gap-2">
<span id="jarvis-status-dot" class="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></span>
<span id="jarvis-status-text">JARVIS ONLINE • VOICE SYSTEM ACTIVE</span>
</p>
</div>
</div>
<!-- Central Quick Actions -->
<div class="hidden md:flex items-center gap-3">
<button onclick="triggerAssemble()" class="hud-glass hover:bg-cyan-500/20 text-cyan-300 px-3 py-1.5 rounded text-xs font-orbitron tracking-wider flex items-center gap-2 border border-cyan-500/40 transition">
<i class="fa-solid fa-cubes-stacked text-cyan-400"></i> ASSEMBLE SUIT
</button>
<button onclick="triggerExplode()" class="hud-glass hover:bg-amber-500/20 text-amber-300 px-3 py-1.5 rounded text-xs font-orbitron tracking-wider flex items-center gap-2 border border-amber-500/40 transition">
<i class="fa-solid fa-explosion text-amber-400"></i> EXPLODE PARTS
</button>
<button onclick="toggleFocusMode(true)" class="hud-glass hover:bg-sky-500/20 text-sky-200 px-3 py-1.5 rounded text-xs font-orbitron tracking-wider flex items-center gap-2 border border-sky-400/40 transition">
<i class="fa-solid fa-expand text-sky-400"></i> FOCUS HOLOGRAM
</button>
</div>
<!-- Voice & Diagnostic Indicator -->
<div class="flex items-center gap-3">
<button id="mic-btn" onclick="toggleVoiceRecognition()" class="relative p-2.5 rounded-full hud-glass border-cyan-400/50 text-cyan-300 hover:text-cyan-100 transition">
<i id="mic-icon" class="fa-solid fa-microphone text-base"></i>
<span id="mic-pulse" class="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full hidden animate-ping"></span>
</button>
<div class="hidden lg:block text-right">
<div class="text-xs font-orbitron text-amber-300" id="power-output">PWR: 100% ARC CORE</div>
<div class="text-[10px] text-cyan-400/60 font-mono" id="fps-display">FRAME RATE: 60 FPS</div>
</div>
</div>
</header>
<!-- LEFT SIDEBAR: PARTS ISOLATION & EXPLODED VIEW CONTROLS -->
<aside id="hud-left-panel" class="hud-overlay top-20 left-4 bottom-6 w-80 hud-glass rounded-xl p-4 flex flex-col justify-between overflow-y-auto">
<div>
<!-- Section Header -->
<div class="flex justify-between items-center border-b border-cyan-500/30 pb-2 mb-4">
<span class="font-orbitron text-xs font-bold tracking-widest text-cyan-300 flex items-center gap-2">
<i class="fa-solid fa-layer-group text-cyan-400"></i> SUIT MODULE ISOLATION
</span>
<span class="text-[10px] bg-cyan-900/60 text-cyan-300 px-2 py-0.5 rounded font-mono border border-cyan-500/30">MK-85</span>
</div>
<!-- Exploded View Slider Controls -->
<div class="mb-5 hud-glass p-3 rounded-lg border border-cyan-500/20">
<div class="flex justify-between text-xs font-orbitron text-cyan-200 mb-1">
<span>EXPLODED VIEW DISTANCE</span>
<span id="explode-val-text" class="text-amber-400 font-mono">0%</span>
</div>
<input id="explode-slider" type="range" min="0" max="100" value="0" oninput="updateExplodeFromSlider(this.value)" class="w-full h-1.5 bg-slate-800 rounded-lg appearance-none cursor-pointer range-slider">
<div class="flex justify-between text-[10px] text-cyan-400/50 mt-1 font-mono">
<span>ASSEMBLED</span>
<span>EXPANDED</span>
</div>
</div>
<!-- Quick Part Selection List -->
<p class="text-[11px] font-orbitron text-cyan-400/80 mb-2 uppercase tracking-wider">Modular Components</p>
<div class="space-y-1.5 text-xs">
<button onclick="isolatePart('Helmet')" class="part-btn w-full text-left px-3 py-2 rounded hud-glass hover:border-cyan-400/80 transition flex items-center justify-between group">
<span class="group-hover:translate-x-1 transition-transform flex items-center gap-2">
<i class="fa-solid fa-helmet-safety text-cyan-400 text-xs"></i> Helmet & HUD Unit
</span>
<i class="fa-solid fa-chevron-right text-[10px] text-cyan-500"></i>
</button>
<button onclick="isolatePart('Chest')" class="part-btn w-full text-left px-3 py-2 rounded hud-glass hover:border-cyan-400/80 transition flex items-center justify-between group">
<span class="group-hover:translate-x-1 transition-transform flex items-center gap-2">
<i class="fa-solid fa-[#00ffff] fa-circle-radiation text-amber-400 text-xs"></i> Chestplate & Arc Core
</span>
<i class="fa-solid fa-chevron-right text-[10px] text-cyan-500"></i>
</button>
<button onclick="isolatePart('RightArm')" class="part-btn w-full text-left px-3 py-2 rounded hud-glass hover:border-cyan-400/80 transition flex items-center justify-between group">
<span class="group-hover:translate-x-1 transition-transform flex items-center gap-2">
<i class="fa-solid fa-hand-back-fist text-cyan-400 text-xs"></i> Right Arm & Repulsor
</span>
<i class="fa-solid fa-chevron-right text-[10px] text-cyan-500"></i>
</button>
<button onclick="isolatePart('LeftArm')" class="part-btn w-full text-left px-3 py-2 rounded hud-glass hover:border-cyan-400/80 transition flex items-center justify-between group">
<span class="group-hover:translate-x-1 transition-transform flex items-center gap-2">
<i class="fa-solid fa-hand-back-fist text-cyan-400 text-xs"></i> Left Arm & Repulsor
</span>
<i class="fa-solid fa-chevron-right text-[10px] text-cyan-500"></i>
</button>
<button onclick="isolatePart('Legs')" class="part-btn w-full text-left px-3 py-2 rounded hud-glass hover:border-cyan-400/80 transition flex items-center justify-between group">
<span class="group-hover:translate-x-1 transition-transform flex items-center gap-2">
<i class="fa-solid fa-socks text-cyan-400 text-xs"></i> Lower Armor & Thrusters
</span>
<i class="fa-solid fa-chevron-right text-[10px] text-cyan-500"></i>
</button>
<button onclick="resetIsolation()" class="w-full text-center px-3 py-2 mt-2 rounded border border-cyan-400/40 bg-cyan-500/10 hover:bg-cyan-500/30 text-cyan-200 font-orbitron text-xs tracking-wider transition">
<i class="fa-solid fa-arrows-rotate mr-1"></i> RESET FULL ASSEMBLY
</button>
</div>
</div>
<!-- Drag & Move Selected Part Details Box -->
<div id="selected-part-box" class="hud-glass-gold p-3 rounded-lg border border-amber-500/30 mt-4">
<div class="flex justify-between items-center border-b border-amber-500/20 pb-1 mb-2">
<span id="selected-part-name" class="font-orbitron text-xs text-amber-300 font-bold">CLICK PART TO MOVE</span>
<i class="fa-solid fa-arrows-up-down-left-right text-amber-400 text-xs"></i>
</div>
<p id="selected-part-desc" class="text-[11px] text-amber-200/70 mb-2">Select any armor piece with left click to adjust offset positions.</p>
<div class="space-y-1.5 text-[11px]">
<div class="flex items-center gap-2">
<span class="w-4 font-mono text-cyan-400">X:</span>
<input id="part-pos-x" type="range" min="-3" max="3" step="0.05" value="0" disabled oninput="manualPartOffset('x', this.value)" class="w-full h-1 bg-slate-800 rounded range-slider">
</div>
<div class="flex items-center gap-2">
<span class="w-4 font-mono text-cyan-400">Y:</span>
<input id="part-pos-y" type="range" min="-3" max="3" step="0.05" value="0" disabled oninput="manualPartOffset('y', this.value)" class="w-full h-1 bg-slate-800 rounded range-slider">
</div>
<div class="flex items-center gap-2">
<span class="w-4 font-mono text-cyan-400">Z:</span>
<input id="part-pos-z" type="range" min="-3" max="3" step="0.05" value="0" disabled oninput="manualPartOffset('z', this.value)" class="w-full h-1 bg-slate-800 rounded range-slider">
</div>
</div>
</div>
</aside>
<!-- RIGHT SIDEBAR: SYSTEM COMMANDS & DIAGNOSTICS -->
<aside id="hud-right-panel" class="hud-overlay top-20 right-4 bottom-6 w-80 hud-glass rounded-xl p-4 flex flex-col justify-between overflow-y-auto">
<div>
<!-- Section Header -->
<div class="flex justify-between items-center border-b border-cyan-500/30 pb-2 mb-4">
<span class="font-orbitron text-xs font-bold tracking-widest text-cyan-300 flex items-center gap-2">
<i class="fa-solid fa-microchip text-cyan-400"></i> JARVIS PROTOCOLS
</span>
<span class="text-[10px] bg-amber-500/20 text-amber-300 px-2 py-0.5 rounded font-mono border border-amber-500/30">ONLINE</span>
</div>
<!-- Preset Commands Grid -->
<div class="grid grid-cols-2 gap-2 mb-4">
<button onclick="triggerOverclock()" class="hud-glass p-2.5 rounded text-left hover:border-amber-400/70 border border-cyan-500/20 transition">
<i class="fa-solid fa-bolt text-amber-400 text-sm mb-1"></i>
<div class="font-orbitron text-[11px] text-cyan-200">OVERCLOCK</div>
<div class="text-[9px] text-cyan-400/60">Arc Matrix Boost</div>
</button>
<button onclick="triggerScanner()" class="hud-glass p-2.5 rounded text-left hover:border-cyan-400/70 border border-cyan-500/20 transition">
<i class="fa-solid fa-radar text-cyan-400 text-sm mb-1"></i>
<div class="font-orbitron text-[11px] text-cyan-200">SCAN SUIT</div>
<div class="text-[9px] text-cyan-400/60">Biometric Audit</div>
</button>
<button onclick="toggleWireframe()" class="hud-glass p-2.5 rounded text-left hover:border-cyan-400/70 border border-cyan-500/20 transition">
<i class="fa-solid fa-border-all text-cyan-400 text-sm mb-1"></i>
<div class="font-orbitron text-[11px] text-cyan-200">WIREFRAME</div>
<div class="text-[9px] text-cyan-400/60">Holo Mesh Mode</div>
</button>
<button onclick="toggleFocusMode(true)" class="hud-glass p-2.5 rounded text-left hover:border-sky-400/70 border border-cyan-500/20 transition">
<i class="fa-solid fa-eye text-sky-400 text-sm mb-1"></i>
<div class="font-orbitron text-[11px] text-cyan-200">FOCUS MODE</div>
<div class="text-[9px] text-cyan-400/60">Hide All HUDs</div>
</button>
</div>
<!-- Voice Command Cheatsheet -->
<div class="hud-glass p-3 rounded-lg border border-cyan-500/20 mb-4">
<div class="font-orbitron text-xs text-cyan-300 font-bold mb-2 flex items-center gap-1.5">
<i class="fa-solid fa-headset text-cyan-400"></i> VOICE COMMAND GUIDE
</div>
<div class="space-y-1 text-[11px] font-mono text-cyan-200/80">
<div class="flex justify-between border-b border-cyan-500/10 pb-0.5">
<span class="text-amber-300">"Jarvis Explode"</span>
<span class="text-cyan-400/60">Explodes View</span>
</div>
<div class="flex justify-between border-b border-cyan-500/10 pb-0.5">
<span class="text-amber-300">"Assemble Suit"</span>
<span class="text-cyan-400/60">Reset Armor</span>
</div>
<div class="flex justify-between border-b border-cyan-500/10 pb-0.5">
<span class="text-amber-300">"Isolate Helmet"</span>
<span class="text-cyan-400/60">Focus Head</span>
</div>
<div class="flex justify-between border-b border-cyan-500/10 pb-0.5">
<span class="text-amber-300">"Isolate Chest"</span>
<span class="text-cyan-400/60">Focus Core</span>
</div>
<div class="flex justify-between border-b border-cyan-500/10 pb-0.5">
<span class="text-amber-300">"Focus Hologram"</span>
<span class="text-cyan-400/60">Clean Mode</span>
</div>
<div class="flex justify-between">
<span class="text-amber-300">"Overclock"</span>
<span class="text-cyan-400/60">Energy Burst</span>
</div>
</div>
</div>
<!-- Diagnostic Live Speech Terminal Log -->
<div class="hud-glass p-3 rounded-lg border border-cyan-500/20">
<div class="flex justify-between items-center mb-1.5">
<span class="font-orbitron text-[10px] text-cyan-400 tracking-wider">JARVIS LOG TERMINAL</span>
<span class="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-ping"></span>
</div>
<div id="voice-log" class="h-24 overflow-y-auto font-mono text-[10px] text-cyan-300/80 space-y-1 pr-1">
<p class="text-emerald-400">> JARVIS v4.2 initialized successfully.</p>
<p class="text-cyan-400/60">> Waiting for user interaction or voice query...</p>
</div>
1 </div>
</div>
1
<!-- Audio Sound SFX Toggle Bar -->
<div class="mt-4 pt-3 border-t border-cyan-500/20 flex justify-between items-center text-xs">
<span class="font-orbitron text-cyan-400 text-[11px]">AUDIO FX SYNTH</span>
<button id="sfx-btn" onclick="toggleAudioSFX()" class="px-2.5 py-1 rounded bg-cyan-500/20 text-cyan-300 border border-cyan-400/40 text-[10px] font-orbitron">
AUDIO: ON
</button>
</div>
</aside>
<!-- FLOATING HOLOGRAM PART LABEL (Follows 3D Coordinates) -->
<div id="holo-part-label" class="part-label opacity-0 pointer-events-none">
<div class="hud-glass-gold px-3 py-1.5 rounded border border-amber-400/60 text-amber-200 text-xs font-orbitron shadow-lg flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-amber-400 animate-pulse"></span>
<span id="holo-label-text">HELMET MODULE</span>
</div>
</div>
<!-- MAIN JAVASCRIPT LOGIC -->
<script>
/* ----------------------------------------------------
* GLOBAL STATE & VARIABLES
* ---------------------------------------------------- */
let scene, camera, renderer, controls;
let suitGroup, gridFloor, scanBeam, particleSystem;
let suitParts = {}; // Map of individual suit meshes/groups
let originalPositions = {}; // Stores default assembled coordinates
let selectedPart = null;
let isExploded = false;
let explodeFactor = 0; // 0 (assembled) to 1 (full explosion)
let isWireframe = false;
let isOverclocked = false;
let isFocusMode = false;
// Audio & Speech
let audioCtx = null;
let isAudioEnabled = true;
let hologramHumNode = null;
let speechRecognition = null;
let isListening = false;
/* ----------------------------------------------------
* INITIALIZATION & THREE.JS SETUP
* ---------------------------------------------------- */
window.addEventListener('load', () => {
initThreeJS();
buildIronManSuit();
initParticleSystem();
initAudioEngine();
initSpeechRecognition();
animate();
// Resize Handler
window.addEventListener('resize', onWindowResize);
// Raycasting for individual part click and drag
const container = document.getElementById('canvas-container');
container.addEventListener('click', onCanvasClick);
// Focus Mode Dismiss Click
window.addEventListener('click', (e) => {
if (isFocusMode && e.target.tagName !== 'BUTTON' && e.target.tagName !== 'INPUT') {
toggleFocusMode(false);
}
});
logTerminal("System ready. Say 'JARVIS' or click suit components.");
});
function initThreeJS() {
const container = document.getElementById('canvas-container');
// Scene Setup
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x020617, 0.035);
// Camera Setup
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 1.2, 5.5);
// Renderer Setup
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
container.appendChild(renderer.domElement);
// Orbit Controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.maxPolarAngle = Math.PI / 2 + 0.1;
controls.minDistance = 2;
controls.maxDistance = 12;
controls.target.set(0, 0, 0);
// Lighting Setup
const ambientLight = new THREE.AmbientLight(0x082f49, 1.5);
scene.add(ambientLight);
const cyanLight = new THREE.PointLight(0x38bdf8, 3, 10);
cyanLight.position.set(2, 3, 2);
scene.add(cyanLight);
const goldLight = new THREE.PointLight(0xf59e0b, 2, 8);
goldLight.position.set(-2, 1, 2);
scene.add(goldLight);
const arcLight = new THREE.PointLight(0x06b6d4, 4, 5);
arcLight.position.set(0, 0.4, 0.5);
scene.add(arcLight);
// Holographic Grid Floor
const gridHelper = new THREE.GridHelper(20, 40, 0x38bdf8, 0x0284c7);
gridHelper.position.y = -2;
gridHelper.material.opacity = 0.35;
gridHelper.material.transparent = true;
scene.add(gridHelper);
// Holographic Scanning Beam Ring
const beamGeo = new THREE.RingGeometry(0.1, 2.5, 32);
const beamMat = new THREE.MeshBasicMaterial({
color: 0x38bdf8,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.25,
wireframe: true
});
scanBeam = new THREE.Mesh(beamGeo, beamMat);
scanBeam.rotation.x = Math.PI / 2;
scanBeam.position.y = -1.95;
scene.add(scanBeam);
}
/* ----------------------------------------------------
* PROCEDURAL IRON MAN MARK 85 ARMOR GEOMETRY ENGINE
* ---------------------------------------------------- */
function buildIronManSuit() {
suitGroup = new THREE.Group();
scene.add(suitGroup);
// Hologram Material Generators
const cyanHoloMat = new THREE.MeshStandardMaterial({
color: 0x0284c7,
emissive: 0x0369a1,
roughness: 0.2,
metalness: 0.8,
wireframe: false,
transparent: true,
opacity: 0.85
});
const goldHoloMat = new THREE.MeshStandardMaterial({
color: 0xd97706,
emissive: 0xb45309,
roughness: 0.3,
metalness: 0.9,
transparent: true,
opacity: 0.85
});
const arcCoreMat = new THREE.MeshBasicMaterial({
color: 0x67e8f9,
wireframe: true
});
// 1. HELMET / FACEPLATE MODULE
const helmetGroup = new THREE.Group();
const headGeo = new THREE.SphereGeometry(0.26, 16, 16);
headGeo.scale(1, 1.15, 1);
const headMesh = new THREE.Mesh(headGeo, cyanHoloMat);
helmetGroup.add(headMesh);
// Faceplate Shield
const faceGeo = new THREE.BoxGeometry(0.24, 0.28, 0.15);
const faceMesh = new THREE.Mesh(faceGeo, goldHoloMat);
faceMesh.position.set(0, 0.02, 0.16);
faceMesh.rotation.x = -0.1;
helmetGroup.add(faceMesh);
// Glowing Eyes
const eyeGeo = new THREE.BoxGeometry(0.08, 0.02, 0.02);
const eyeLeft = new THREE.Mesh(eyeGeo, arcCoreMat);
const eyeRight = new THREE.Mesh(eyeGeo, arcCoreMat);
eyeLeft.position.set(-0.07, 0.04, 0.23);
eyeRight.position.set(0.07, 0.04, 0.23);
helmetGroup.add(eyeLeft, eyeRight);
helmetGroup.position.set(0, 1.2, 0);
suitGroup.add(helmetGroup);
registerPart('Helmet', helmetGroup, new THREE.Vector3(0, 1.6, 0));
// 2. CHESTPLATE & ARC REACTOR CORE MODULE
const chestGroup = new THREE.Group();
const chestGeo = new THREE.CylinderGeometry(0.42, 0.3, 0.65, 8);
const chestMesh = new THREE.Mesh(chestGeo, cyanHoloMat);
chestGroup.add(chestMesh);
// Gold Chest Trim Plates
const trimGeo = new THREE.BoxGeometry(0.22, 0.35, 0.1);
const trimL = new THREE.Mesh(trimGeo, goldHoloMat);
const trimR = new THREE.Mesh(trimGeo, goldHoloMat);
trimL.position.set(-0.16, 0.08, 0.18);
trimL.rotation.z = -0.2;
trimR.position.set(0.16, 0.08, 0.18);
trimR.rotation.z = 0.2;
chestGroup.add(trimL, trimR);
// Arc Reactor Core (Triangular / Circular Housing)
const arcGeo = new THREE.TorusGeometry(0.1, 0.025, 8, 24);
const arcMesh = new THREE.Mesh(arcGeo, arcCoreMat);
arcMesh.position.set(0, 0.1, 0.24);
chestGroup.add(arcMesh);
const arcInnerGeo = new THREE.OctahedronGeometry(0.06);
const arcInnerMesh = new THREE.Mesh(arcInnerGeo, new THREE.MeshBasicMaterial({ color: 0xffffff }));
arcInnerMesh.position.set(0, 0.1, 0.24);
chestGroup.add(arcInnerMesh);
chestGroup.position.set(0, 0.5, 0);
suitGroup.add(chestGroup);
registerPart('Chest', chestGroup, new THREE.Vector3(0, 0.8, 0));
// 3. RIGHT ARM & GAUNTLET MODULE
const rightArmGroup = new THREE.Group();
const rShoulderGeo = new THREE.SphereGeometry(0.2, 12, 12);
const rShoulder = new THREE.Mesh(rShoulderGeo, goldHoloMat);
rShoulder.position.set(0, 0, 0);
const rBicepGeo = new THREE.CylinderGeometry(0.11, 0.09, 0.35, 8);
const rBicep = new THREE.Mesh(rBicepGeo, cyanHoloMat);
rBicep.position.set(0, -0.22, 0);
const rGauntletGeo = new THREE.CylinderGeometry(0.1, 0.08, 0.35, 8);
const rGauntlet = new THREE.Mesh(rGauntletGeo, goldHoloMat);
rGauntlet.position.set(0, -0.52, 0);
// Repulsor Palm
const rRepulsor = new THREE.Mesh(new THREE.CircleGeometry(0.04, 12), arcCoreMat);
rRepulsor.position.set(0, -0.7, 0.05);
rRepulsor.rotation.x = Math.PI / 2;
rightArmGroup.add(rShoulder, rBicep, rGauntlet, rRepulsor);
rightArmGroup.position.set(-0.52, 0.65, 0);
suitGroup.add(rightArmGroup);
registerPart('RightArm', rightArmGroup, new THREE.Vector3(-1.2, 0.5, 0));
// 4. LEFT ARM & GAUNTLET MODULE
const leftArmGroup = new THREE.Group();
const lShoulder = new THREE.Mesh(rShoulderGeo, goldHoloMat);
const lBicep = new THREE.Mesh(rBicepGeo, cyanHoloMat);
lBicep.position.set(0, -0.22, 0);
const lGauntlet = new THREE.Mesh(rGauntletGeo, goldHoloMat);
lGauntlet.position.set(0, -0.52, 0);
const lRepulsor = new THREE.Mesh(new THREE.CircleGeometry(0.04, 12), arcCoreMat);
lRepulsor.position.set(0, -0.7, 0.05);
lRepulsor.rotation.x = Math.PI / 2;
leftArmGroup.add(lShoulder, lBicep, lGauntlet, lRepulsor);
leftArmGroup.position.set(0.52, 0.65, 0);
suitGroup.add(leftArmGroup);
registerPart('LeftArm', leftArmGroup, new THREE.Vector3(1.2, 0.5, 0));
// 5. WAIST & LEGS MODULE
const legsGroup = new THREE.Group();
const waistGeo = new THREE.CylinderGeometry(0.3, 0.25, 0.25, 8);
const waist = new THREE.Mesh(waistGeo, goldHoloMat);
waist.position.set(0, 0, 0);
// Thighs
const thighGeo = new THREE.CylinderGeometry(0.13, 0.1, 0.5, 8);
const thighR = new THREE.Mesh(thighGeo, cyanHoloMat);
const thighL = new THREE.Mesh(thighGeo, cyanHoloMat);
thighR.position.set(-0.18, -0.32, 0);
thighL.position.set(0.18, -0.32, 0);
// Lower Legs / Boots
const bootGeo = new THREE.CylinderGeometry(0.1, 0.08, 0.55, 8);
const bootR = new THREE.Mesh(bootGeo, goldHoloMat);
const bootL = new THREE.Mesh(bootGeo, goldHoloMat);
bootR.position.set(-0.18, -0.82, 0);
bootL.position.set(0.18, -0.82, 0);
// Thruster Soles
const soleR = new THREE.Mesh(new THREE.CircleGeometry(0.06, 12), arcCoreMat);
const soleL = new THREE.Mesh(new THREE.CircleGeometry(0.06, 12), arcCoreMat);
soleR.position.set(-0.18, -1.1, 0);
soleR.rotation.x = Math.PI / 2;
soleL.position.set(0.18, -1.1, 0);
soleL.rotation.x = Math.PI / 2;
legsGroup.add(waist, thighR, thighL, bootR, bootL, soleR, soleL);
legsGroup.position.set(0, 0.05, 0);
suitGroup.add(legsGroup);
registerPart('Legs', legsGroup, new THREE.Vector3(0, -0.8, 0));
}
// Helper to register modular part metadata
function registerPart(name, groupObject, explodedOffset) {
suitParts[name] = groupObject;
groupObject.userData = {
name: name,
basePos: groupObject.position.clone(),
explodedOffset: explodedOffset,
manualOffset: new THREE.Vector3(0, 0, 0)
};
originalPositions[name] = groupObject.position.clone();
}
/* ----------------------------------------------------
* ADVANCED PARTICLE SYSTEM (Arc Core Energy Streams)
* ---------------------------------------------------- */
function initParticleSystem() {
const particleCount = 200;
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
const radius = 0.3 + Math.random() * 1.5;
const theta = Math.random() * Math.PI * 2;
const y = (Math.random() - 0.5) * 3;
positions[i * 3] = radius * Math.cos(theta);
positions[i * 3 + 1] = y;
positions[i * 3 + 2] = radius * Math.sin(theta);
colors[i * 3] = 0.2;
colors[i * 3 + 1] = 0.7;
colors[i * 3 + 2] = 1.0;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({
size: 0.03,
vertexColors: true,
transparent: true,
opacity: 0.6,
blending: THREE.AdditiveBlending
});
particleSystem = new THREE.Points(geometry, material);
scene.add(particleSystem);
}
/* ----------------------------------------------------
* INTERACTIVE CLICK, SELECTION & RAYCASTING
* ---------------------------------------------------- */
function onCanvasClick(event) {
// Ignore click if in focus mode transition
if (isFocusMode) return;
const mouse = new THREE.Vector2(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1
);
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
// Collect all interactive sub-meshes
const interactiveMeshes = [];
Object.values(suitParts).forEach(group => {
group.traverse(child => {
if (child.isMesh) {
child.userData.parentPart = group.userData.name;
interactiveMeshes.push(child);
}
});
});
const intersects = raycaster.intersectObjects(interactiveMeshes);
if (intersects.length > 0) {
const hitMesh = intersects[0].object;
const partName = hitMesh.userData.parentPart;
if (partName) {
selectPart(partName);
playLaserSFX();
}
}
}
function selectPart(partName) {
selectedPart = suitParts[partName];
if (!selectedPart) return;
// Update UI Sidebar
document.getElementById('selected-part-name').innerText = partName.toUpperCase() + ' MODULE';
document.getElementById('selected-part-desc').innerText = `Active sub-assembly selected. Use sliders or click & drag to adjust 3D offsets.`;
// Enable position sliders
['x', 'y', 'z'].forEach(axis => {
const input = document.getElementById(`part-pos-${axis}`);
input.disabled = false;
input.value = selectedPart.userData.manualOffset[axis];
});
// Update 3D Floating Tag
updatePartLabelPosition();
speakJARVIS(`Inspecting ${partName} module, Boss.`);
logTerminal(`Selected module: ${partName}`);
}
function manualPartOffset(axis, val) {
if (!selectedPart) return;
val = parseFloat(val);
selectedPart.userData.manualOffset[axis] = val;
updatePartTransforms();
}
function updatePartTransforms() {
Object.values(suitParts).forEach(part => {
const base = part.userData.basePos;
const explode = part.userData.explodedOffset;
const manual = part.userData.manualOffset;
// Interpolate target position based on explodedFactor
part.position.x = THREE.MathUtils.lerp(base.x, explode.x, explodeFactor) + manual.x;
part.position.y = THREE.MathUtils.lerp(base.y, explode.y, explodeFactor) + manual.y;
part.position.z = THREE.MathUtils.lerp(base.z, explode.z, explodeFactor) + manual.z;
});
}
/* ----------------------------------------------------
* EXPLOEDED VIEW ENGINE & ISOLATION COMMANDS
* ---------------------------------------------------- */
function updateExplodeFromSlider(val) {
explodeFactor = val / 100;
document.getElementById('explode-val-text').innerText = `${val}%`;
updatePartTransforms();
}
function triggerExplode() {
let start = explodeFactor;
let target = 1.0;
animateValue(start, target, 800, (v) => {
explodeFactor = v;
document.getElementById('explode-slider').value = v * 100;
document.getElementById('explode-val-text').innerText = `${Math.round(v * 100)}%`;
updatePartTransforms();
});
speakJARVIS("Armor matrix disassembly initialized. Displaying exploded view.");
logTerminal("Command executed: EXPLODE SUIT PARTS");
playScanSFX();
}
function triggerAssemble() {
let start = explodeFactor;
let target = 0.0;
// Reset manual offsets as well
Object.values(suitParts).forEach(p => p.userData.manualOffset.set(0, 0, 0));
if (selectedPart) {
['x', 'y', 'z'].forEach(a => document.getElementById(`part-pos-${a}`).value = 0);
}
animateValue(start, target, 800, (v) => {
explodeFactor = v;
document.getElementById('explode-slider').value = v * 100;
document.getElementById('explode-val-text').innerText = `${Math.round(v * 100)}%`;
updatePartTransforms();
});
speakJARVIS("Reassembling Mark 85 components into combat stance.");
logTerminal("Command executed: ASSEMBLE SUIT");
playScanSFX();
}
function isolatePart(partName) {
triggerExplode();
setTimeout(() => {
selectPart(partName);
}, 400);
}
function resetIsolation() {
triggerAssemble();
selectedPart = null;
document.getElementById('selected-part-name').innerText = 'CLICK PART TO MOVE';
document.getElementById('selected-part-desc').innerText = 'Select any armor piece with left click to adjust offset positions.';
['x', 'y', 'z'].forEach(axis => {
const input = document.getElementById(`part-pos-${axis}`);
input.disabled = true;
input.value = 0;
});
document.getElementById('holo-part-label').classList.add('opacity-0');
}
/* ----------------------------------------------------
* FOCUS HOLOGRAM MODE (UI CLEAN VIEW)
* ---------------------------------------------------- */
function toggleFocusMode(enable) {
isFocusMode = enable;
const overlays = document.querySelectorAll('.hud-overlay');
const hint = document.getElementById('focus-restore-hint');
if (isFocusMode) {
overlays.forEach(el => el.classList.add('hud-hidden'));
hint.classList.remove('opacity-0');
speakJARVIS("Hologram Focus Mode engaged, Boss.");
logTerminal("Focus mode ENABLED.");
} else {
overlays.forEach(el => el.classList.remove('hud-hidden'));
hint.classList.add('opacity-0');
speakJARVIS("Restoring HUD primary interface display.");
logTerminal("Focus mode DISABLED.");
}
}
/* ----------------------------------------------------
* SYSTEM PROTOCOLS (OVERCLOCK, SCANNER, WIREFRAME)
* ---------------------------------------------------- */
function triggerOverclock() {
isOverclocked = !isOverclocked;
playPulseSFX();
Object.values(suitParts).forEach(group => {
group.traverse(child => {
if (child.isMesh && child.material) {
if (isOverclocked) {
child.material.emissiveIntensity = 2.5;
} else {
child.material.emissiveIntensity = 1.0;
}
}
});
});
if (isOverclocked) {
speakJARVIS("Warning! Arc reactor overclocked to 150% output!");
logTerminal("OVERCLOCK ACTIVATED!");
document.getElementById('power-output').innerText = "PWR: 150% OVERCLOCK";
document.getElementById('power-output').classList.add('animate-pulse');
} else {
speakJARVIS("Power output normalized.");
logTerminal("OVERCLOCK DEACTIVATED.");
document.getElementById('power-output').innerText = "PWR: 100% ARC CORE";
document.getElementById('power-output').classList.remove('animate-pulse');
}
}
function triggerScanner() {
playScanSFX();
speakJARVIS("Initiating full suit biometric and structural audit.");
logTerminal("Scanner active: All systems 100% operational.");
// Animate scanning ring upward
scanBeam.position.y = -2;
let scanAnim = setInterval(() => {
scanBeam.position.y += 0.08;
if (scanBeam.position.y > 2) {
scanBeam.position.y = -1.95;
clearInterval(scanAnim);
}
}, 30);
}
function toggleWireframe() {
isWireframe = !isWireframe;
Object.values(suitParts).forEach(group => {
group.traverse(child => {
if (child.isMesh && child.material) {
child.material.wireframe = isWireframe;
}
});
});
speakJARVIS(isWireframe ? "Wireframe holographic mesh enabled." : "Solid armor rendering restored.");
logTerminal(`Wireframe mode: ${isWireframe ? 'ON' : 'OFF'}`);
}
/* ----------------------------------------------------
* WEB AUDIO API SYNTHESIS (NO EXTERNAL ASSETS)
* ---------------------------------------------------- */
function initAudioEngine() {
try {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
audioCtx = new AudioContext();
} catch (e) {
console.warn("Web Audio API not supported");
}
}
function toggleAudioSFX() {
isAudioEnabled = !isAudioEnabled;
document.getElementById('sfx-btn').innerText = isAudioEnabled ? "AUDIO: ON" : "AUDIO: OFF";
logTerminal(`Audio SFX system: ${isAudioEnabled ? 'ENABLED' : 'MUTED'}`);
}
function playLaserSFX() {
if (!isAudioEnabled || !audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(800, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(150, audioCtx.currentTime + 0.15);
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.15);
}
function playScanSFX() {
if (!isAudioEnabled || !audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(200, audioCtx.currentTime);
osc.frequency.linearRampToValueAtTime(1200, audioCtx.currentTime + 0.4);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.4);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.4);
}
function playPulseSFX() {
if (!isAudioEnabled || !audioCtx) return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(100, audioCtx.currentTime);
osc.frequency.linearRampToValueAtTime(500, audioCtx.currentTime + 0.3);
gain.gain.setValueAtTime(0.4, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.3);
}
/* ----------------------------------------------------
* WEB SPEECH RECOGNITION & VOICE SYNTHESIS
* ---------------------------------------------------- */
function initSpeechRecognition() {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
logTerminal("Speech Recognition not supported in this browser.", true);
return;
}
speechRecognition = new SpeechRecognition();
speechRecognition.continuous = true;
speechRecognition.interimResults = false;
speechRecognition.lang = 'en-US';
speechRecognition.onresult = (event) => {
const transcript = event.results[event.results.length - 1][0].transcript.trim().toLowerCase();
logTerminal(`Voice input: "${transcript}"`);
parseVoiceCommand(transcript);
};
speechRecognition.onerror = (e) => {
console.warn("Speech recognition error:", e.error);
};
speechRecognition.onend = () => {
if (isListening) speechRecognition.start(); // Keep listening active
};
}
function toggleVoiceRecognition() {
if (!speechRecognition) {
alert("Web Speech API is not supported in this browser. Please use Chrome/Edge.");
return;
}
const btn = document.getElementById('mic-btn');
const pulse = document.getElementById('mic-pulse');
if (!isListening) {
try {
speechRecognition.start();
isListening = true;
btn.classList.add('border-red-500', 'text-red-400');
pulse.classList.remove('hidden');
speakJARVIS("Voice recognition activated. Listening for commands.");
logTerminal("JARVIS listening for voice commands...");
} catch (err) {
console.error(err);
}
} else {
speechRecognition.stop();
isListening = false;
btn.classList.remove('border-red-500', 'text-red-400');
pulse.classList.add('hidden');
speakJARVIS("Voice system offline.");
logTerminal("JARVIS voice recognition stopped.");
}
}
function parseVoiceCommand(cmd) {
if (cmd.includes('explode') || cmd.includes('disassemble')) {
triggerExplode();
} else if (cmd.includes('assemble') || cmd.includes('suit up')) {
triggerAssemble();
} else if (cmd.includes('helmet') || cmd.includes('head')) {
isolatePart('Helmet');
} else if (cmd.includes('chest') || cmd.includes('arc') || cmd.includes('core')) {
isolatePart('Chest');
} else if (cmd.includes('arm') || cmd.includes('hand')) {
isolatePart('RightArm');
} else if (cmd.includes('leg') || cmd.includes('boot')) {
isolatePart('Legs');
} else if (cmd.includes('focus') || cmd.includes('hologram only')) {
toggleFocusMode(true);
} else if (cmd.includes('hud') || cmd.includes('show ui')) {
toggleFocusMode(false);
} else if (cmd.includes('overclock') || cmd.includes('power')) {
triggerOverclock();
} else if (cmd.includes('scan')) {
triggerScanner();
} else if (cmd.includes('jarvis')) {
speakJARVIS("At your service, Boss.");
}
}
function speakJARVIS(text) {
if ('speechSynthesis' in window) {
window.speechSynthesis.cancel(); // Stop current speech
const utterance = new SpeechSynthesisUtterance(text);
// Find a sleek male voice if available
const voices = window.speechSynthesis.getVoices();
const jarvisVoice = voices.find(v => v.name.includes('Natural') || v.name.includes('David') || v.name.includes('Google UK English Male'));
if (jarvisVoice) utterance.voice = jarvisVoice;
utterance.pitch = 0.9;
utterance.rate = 1.0;
window.speechSynthesis.speak(utterance);
}
}
/* ----------------------------------------------------
* UTILITIES & RENDER LOOP
* ---------------------------------------------------- */
function updatePartLabelPosition() {
const label = document.getElementById('holo-part-label');
if (!selectedPart) {
label.classList.add('opacity-0');
return;
}
const worldPos = new THREE.Vector3();
selectedPart.getWorldPosition(worldPos);
// Project 3D vector to 2D screen coordinates
const projected = worldPos.clone().project(camera);
const x = (projected.x * 0.5 + 0.5) * window.innerWidth;
const y = (-(projected.y * 0.5) + 0.5) * window.innerHeight;
label.style.left = `${x}px`;
label.style.top = `${y}px`;
document.getElementById('holo-label-text').innerText = `${selectedPart.userData.name.toUpperCase()} MODULE`;
label.classList.remove('opacity-0');
}
function animate() {
requestAnimationFrame(animate);
// Rotate main hologram gently
if (suitGroup) {
suitGroup.rotation.y += 0.003;
}
// Animate Particles
if (particleSystem) {
particleSystem.rotation.y += 0.002;
}
// Pulse Scan Beam
if (scanBeam) {
scanBeam.rotation.z += 0.01;
}
// Update Label Tracker
if (selectedPart) {
updatePartLabelPosition();
}
controls.update();
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animateValue(start, end, duration, callback) {
let startTimestamp = null;
const step = (timestamp) => {
if (!startTimestamp) startTimestamp = timestamp;
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
const currentVal = start + progress * (end - start);
callback(currentVal);
if (progress < 1) {
window.requestAnimationFrame(step);
}
};
window.requestAnimationFrame(step);
}
function logTerminal(msg, isError = false) {
const logBox = document.getElementById('voice-log');
if (!logBox) return;
const p = document.createElement('p');
p.className = isError ? "text-red-400 font-bold" : "text-cyan-300/80";
p.innerText = `> ${msg}`;
logBox.appendChild(p);
logBox.scrollTop = logBox.scrollHeight;
}
</script>
</body>
</html><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HOLO SUIT - FROM IMAGE</title>
<style>
body{margin:0;background:#000;overflow:hidden;font-family:monospace;color:#00f0ff}
#video{position:fixed;top:0;left:0;width:100vw;height:100vh;object-fit:cover;transform:scaleX(-1);z-index:1}
#canvas{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:2}
.hud{position:fixed;z-index:5;width:100vw;height:100vh;padding:10px;box-sizing:border-box;border:2px solid #00f0ff;pointer-events:none}
.top{display:flex;justify-content:space-between;font-size:11px;border-bottom:1px solid #00f0ff;padding-bottom:5px;background:rgba(0,0,0,0.6)}
.suit-container{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:3;display:flex;align-items:center;justify-content:center;pointer-events:none}
#h10
6
499KB
1347KB
535.0ms
816.0ms
713.0ms