Files
game_development_workshop/js/avatar.js
T
2026-09-25 18:18:21 +05:30

337 lines
16 KiB
JavaScript

/**
* PPE SAFETY CHALLENGE - DYNAMIC WORKER AVATAR SYSTEM
* Generates an SVG industrial worker avatar with modular layered PPE gear.
*/
class WorkerAvatar {
constructor(containerId) {
this.container = document.getElementById(containerId);
this.equippedItems = new Set();
this.render();
}
setEquipped(itemIds) {
this.equippedItems = new Set(itemIds);
this.updateLayers();
}
equip(itemId) {
this.equippedItems.add(itemId);
this.updateLayers();
}
unequip(itemId) {
this.equippedItems.delete(itemId);
this.updateLayers();
}
has(itemId) {
return this.equippedItems.has(itemId);
}
getAvatarCenterCoordinates() {
if (!this.container) return { x: 150, y: 200, eyeX: 140, eyeY: 100, handX: 190, handY: 220 };
const rect = this.container.getBoundingClientRect();
const parentRect = this.container.parentElement.getBoundingClientRect();
const relX = rect.left - parentRect.left + (rect.width * 0.5);
const relY = rect.top - parentRect.top + (rect.height * 0.5);
return {
x: relX,
y: relY,
eyeX: relX - 10,
eyeY: relY - 110,
handX: relX + 45,
handY: relY + 15
};
}
render() {
if (!this.container) return;
this.container.innerHTML = `
<svg viewBox="0 0 300 450" class="worker-svg" id="worker-avatar-svg" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="skinGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#f6d5b8"/>
<stop offset="100%" stop-color="#e2b792"/>
</linearGradient>
<linearGradient id="hairGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#3b291d"/>
<stop offset="100%" stop-color="#24180f"/>
</linearGradient>
<linearGradient id="baseSuitGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#2c3e50"/>
<stop offset="100%" stop-color="#1a252f"/>
</linearGradient>
<linearGradient id="leatherGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#a0522d"/>
<stop offset="100%" stop-color="#723616"/>
</linearGradient>
<linearGradient id="nitrileGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#0284c7"/>
<stop offset="100%" stop-color="#0369a1"/>
</linearGradient>
<linearGradient id="visorGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="rgba(14, 165, 233, 0.4)"/>
<stop offset="100%" stop-color="rgba(2, 132, 199, 0.6)"/>
</linearGradient>
<linearGradient id="chemicalApronGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#059669"/>
<stop offset="100%" stop-color="#047857"/>
</linearGradient>
<filter id="gearGlow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#facc15" flood-opacity="0.35"/>
</filter>
</defs>
<!-- Shadow on floor -->
<ellipse cx="150" cy="425" rx="75" ry="12" fill="rgba(0,0,0,0.4)"/>
<!-- LAYER: BASE WORKER BODY -->
<g id="layer-base-body">
<!-- Legs / Basic Work Trousers -->
<path d="M 120 230 L 110 375 L 138 375 L 146 250 L 154 250 L 162 375 L 190 375 L 180 230 Z" fill="#1e293b"/>
<!-- Feet (Base work shoes) -->
<g id="layer-feet-base">
<path d="M 105 375 L 140 375 L 142 395 L 98 395 Z" fill="#334155"/>
<path d="M 160 375 L 195 375 L 202 395 L 158 395 Z" fill="#334155"/>
</g>
<!-- Torso (Base blue industrial shirt) -->
<path d="M 110 140 C 110 130 190 130 190 140 L 196 235 C 196 240 104 240 104 235 Z" fill="url(#baseSuitGrad)"/>
<!-- Arms (Base) -->
<path d="M 110 140 L 80 215 L 98 222 L 122 160 Z" fill="url(#baseSuitGrad)"/>
<path d="M 190 140 L 220 215 L 202 222 L 178 160 Z" fill="url(#baseSuitGrad)"/>
<!-- Forearms (Skin) -->
<path d="M 80 215 L 70 260 L 84 262 L 98 222 Z" fill="url(#skinGrad)"/>
<path d="M 220 215 L 230 260 L 216 262 L 202 222 Z" fill="url(#skinGrad)"/>
<!-- Hands (Skin - Bare) -->
<g id="layer-hands-bare">
<ellipse cx="74" cy="272" rx="10" ry="14" fill="url(#skinGrad)"/>
<ellipse cx="226" cy="272" rx="10" ry="14" fill="url(#skinGrad)"/>
</g>
<!-- Neck -->
<rect x="138" y="115" width="24" height="25" rx="4" fill="url(#skinGrad)"/>
<!-- Head -->
<ellipse cx="150" cy="95" rx="28" ry="34" fill="url(#skinGrad)"/>
<!-- Hair Base -->
<path d="M 122 90 C 122 65 178 65 178 90 C 178 72 165 62 150 62 C 135 62 122 72 122 90 Z" fill="url(#hairGrad)"/>
<!-- Eyes & Eyebrows -->
<ellipse cx="140" cy="94" rx="3.5" ry="3" fill="#1e293b"/>
<ellipse cx="160" cy="94" rx="3.5" ry="3" fill="#1e293b"/>
<path d="M 134 88 Q 140 85 146 88" stroke="#3b291d" stroke-width="2" fill="none"/>
<path d="M 154 88 Q 160 85 166 88" stroke="#3b291d" stroke-width="2" fill="none"/>
<!-- Nose & Mouth -->
<path d="M 150 96 L 147 106 L 153 106" stroke="#c08c65" stroke-width="1.5" fill="none"/>
<path d="M 143 116 Q 150 120 157 116" stroke="#b45309" stroke-width="2" fill="none"/>
<!-- Ears -->
<ellipse cx="121" cy="96" rx="4" ry="7" fill="url(#skinGrad)"/>
<ellipse cx="179" cy="96" rx="4" ry="7" fill="url(#skinGrad)"/>
</g>
<!-- ====================================================
EQUIPMENT LAYERS (Toggled by class or visibility)
==================================================== -->
<!-- 1. HEAD GEAR -->
<!-- Hard Hat (Grinding / General) -->
<g id="layer-gear-hard_hat_cap" style="display: none;">
<path d="M 115 82 C 115 48 185 48 185 82 L 195 86 C 195 90 105 90 105 86 Z" fill="#facc15" stroke="#ca8a04" stroke-width="2"/>
<path d="M 144 48 L 156 48 L 154 85 L 146 85 Z" fill="#eab308"/>
</g>
<!-- Hard Hat (Chemical) -->
<g id="layer-gear-hard_hat_chemical" style="display: none;">
<path d="M 115 82 C 115 48 185 48 185 82 L 195 86 C 195 90 105 90 105 86 Z" fill="#f8fafc" stroke="#94a3b8" stroke-width="2"/>
<path d="M 144 48 L 156 48 L 154 85 L 146 85 Z" fill="#cbd5e1"/>
</g>
<!-- 2. HEARING / RESPIRATORY GEAR -->
<!-- Heavy Ear Muffs -->
<g id="layer-gear-earmuffs_heavy" style="display: none;">
<path d="M 116 80 C 116 45 184 45 184 80" stroke="#dc2626" stroke-width="5" fill="none"/>
<rect x="114" y="86" width="10" height="22" rx="4" fill="#ef4444" stroke="#991b1b" stroke-width="1.5"/>
<rect x="176" y="86" width="10" height="22" rx="4" fill="#ef4444" stroke="#991b1b" stroke-width="1.5"/>
</g>
<!-- Consumer Earbuds (Distractor) -->
<g id="layer-gear-wireless_earbuds" style="display: none;">
<ellipse cx="122" cy="98" rx="2" ry="3" fill="#ffffff"/>
<ellipse cx="178" cy="98" rx="2" ry="3" fill="#ffffff"/>
<line x1="122" y1="98" x2="122" y2="108" stroke="#ffffff" stroke-width="1.5"/>
<line x1="178" y1="98" x2="178" y2="108" stroke="#ffffff" stroke-width="1.5"/>
</g>
<!-- Organic Vapor Half-Mask Respirator -->
<g id="layer-gear-organic_vapor_respirator" style="display: none;">
<path d="M 136 102 L 164 102 L 160 126 L 140 126 Z" fill="#475569" stroke="#1e293b" stroke-width="1.5"/>
<!-- Cartridge canisters on sides -->
<ellipse cx="132" cy="115" rx="7" ry="9" fill="#0284c7" stroke="#0369a1" stroke-width="1.5"/>
<ellipse cx="168" cy="115" rx="7" ry="9" fill="#0284c7" stroke="#0369a1" stroke-width="1.5"/>
<!-- Straps -->
<line x1="136" y1="104" x2="122" y2="92" stroke="#334155" stroke-width="2"/>
<line x1="164" y1="104" x2="178" y2="92" stroke="#334155" stroke-width="2"/>
</g>
<!-- Cloth Dust Mask (Distractor) -->
<g id="layer-gear-cloth_dust_mask" style="display: none;">
<path d="M 134 102 C 134 102 150 100 166 102 L 160 125 C 150 128 150 128 140 125 Z" fill="#93c5fd" stroke="#60a5fa" stroke-width="1"/>
</g>
<!-- 3. EYE & FACE GEAR -->
<!-- Face Shield + Safety Glasses (Grinding) -->
<g id="layer-gear-faceshield_glasses" style="display: none;">
<!-- Dual Glasses underneath -->
<rect x="130" y="88" width="18" height="12" rx="3" fill="rgba(6, 182, 212, 0.3)" stroke="#0891b2" stroke-width="2"/>
<rect x="152" y="88" width="18" height="12" rx="3" fill="rgba(6, 182, 212, 0.3)" stroke="#0891b2" stroke-width="2"/>
<line x1="148" y1="92" x2="152" y2="92" stroke="#0891b2" stroke-width="2"/>
<!-- Full Clear Curved Visor Overlay -->
<path d="M 112 78 L 188 78 L 182 135 C 182 142 118 142 118 135 Z" fill="url(#visorGrad)" stroke="#38bdf8" stroke-width="2" opacity="0.85"/>
<!-- Brow guard -->
<path d="M 112 76 L 188 76 L 186 82 L 114 82 Z" fill="#0369a1"/>
</g>
<!-- Chemical Splash Goggles -->
<g id="layer-gear-chemical_splash_goggles" style="display: none;">
<path d="M 124 86 C 124 86 150 84 176 86 L 174 104 C 150 106 150 106 126 104 Z" fill="rgba(16, 185, 129, 0.35)" stroke="#059669" stroke-width="2.5"/>
<ellipse cx="138" cy="94" rx="10" ry="7" fill="none" stroke="#10b981" stroke-width="2"/>
<ellipse cx="162" cy="94" rx="10" ry="7" fill="none" stroke="#10b981" stroke-width="2"/>
<line x1="124" y1="94" x2="120" y2="94" stroke="#065f46" stroke-width="3"/>
<line x1="176" y1="94" x2="180" y2="94" stroke="#065f46" stroke-width="3"/>
</g>
<!-- Standard Sunglasses (Distractor) -->
<g id="layer-gear-regular_sunglasses" style="display: none;">
<rect x="130" y="90" width="18" height="10" rx="2" fill="#111827" stroke="#000" stroke-width="2"/>
<rect x="152" y="90" width="18" height="10" rx="2" fill="#111827" stroke="#000" stroke-width="2"/>
<line x1="148" y1="93" x2="152" y2="93" stroke="#000" stroke-width="2"/>
</g>
<!-- Open Safety Glasses (Distractor in Chemical) -->
<g id="layer-gear-standard_safety_glasses" style="display: none;">
<rect x="130" y="90" width="18" height="10" rx="2" fill="rgba(255,255,255,0.4)" stroke="#64748b" stroke-width="1.5"/>
<rect x="152" y="90" width="18" height="10" rx="2" fill="rgba(255,255,255,0.4)" stroke="#64748b" stroke-width="1.5"/>
</g>
<!-- 4. BODY PROTECTION -->
<!-- Heavy Leather Grinding Jacket -->
<g id="layer-gear-heavy_leather_jacket" style="display: none;">
<path d="M 104 136 C 104 126 196 126 196 136 L 202 240 L 98 240 Z" fill="url(#leatherGrad)" stroke="#57260d" stroke-width="2"/>
<!-- Leather Sleeves -->
<path d="M 106 138 L 72 248 L 94 252 L 122 160 Z" fill="url(#leatherGrad)"/>
<path d="M 194 138 L 228 248 L 206 252 L 178 160 Z" fill="url(#leatherGrad)"/>
<line x1="150" y1="140" x2="150" y2="240" stroke="#451a03" stroke-width="3"/>
</g>
<!-- Chemical-Resistant Apron -->
<g id="layer-gear-chemical_resistant_apron" style="display: none;">
<path d="M 126 135 L 174 135 L 188 175 L 192 315 L 108 315 L 112 175 Z" fill="url(#chemicalApronGrad)" stroke="#064e3b" stroke-width="2.5"/>
<path d="M 130 135 L 138 115 M 170 135 L 162 115" stroke="#047857" stroke-width="3"/>
</g>
<!-- Cotton Lab Coat (Distractor) -->
<g id="layer-gear-cotton_lab_coat" style="display: none;">
<path d="M 104 136 L 196 136 L 200 300 L 100 300 Z" fill="#f1f5f9" stroke="#cbd5e1" stroke-width="2"/>
<line x1="150" y1="136" x2="150" y2="300" stroke="#94a3b8" stroke-width="2"/>
</g>
<!-- 5. HAND PROTECTION -->
<!-- Heavy Leather Grinding Gloves -->
<g id="layer-gear-leather_grinding_gloves" style="display: none;">
<path d="M 64 250 L 86 252 L 86 288 L 62 284 Z" fill="url(#leatherGrad)" stroke="#57260d" stroke-width="1.5"/>
<path d="M 236 250 L 214 252 L 214 288 L 238 284 Z" fill="url(#leatherGrad)" stroke="#57260d" stroke-width="1.5"/>
</g>
<!-- Nitrile Gauntlet Chemical Gloves -->
<g id="layer-gear-nitrile_gauntlet_gloves" style="display: none;">
<path d="M 62 235 L 88 238 L 86 288 L 60 284 Z" fill="url(#nitrileGrad)" stroke="#0369a1" stroke-width="2"/>
<path d="M 238 235 L 212 238 L 214 288 L 240 284 Z" fill="url(#nitrileGrad)" stroke="#0369a1" stroke-width="2"/>
</g>
<!-- Thin Latex Gloves (Distractor) -->
<g id="layer-gear-thin_latex_gloves" style="display: none;">
<ellipse cx="74" cy="272" rx="11" ry="15" fill="#bae6fd" stroke="#38bdf8" stroke-width="1"/>
<ellipse cx="226" cy="272" rx="11" ry="15" fill="#bae6fd" stroke="#38bdf8" stroke-width="1"/>
</g>
<!-- Standard Leather Work Gloves (Distractor in Chemical) -->
<g id="layer-gear-leather_work_gloves" style="display: none;">
<path d="M 64 255 L 86 257 L 86 285 L 62 282 Z" fill="#d97706" stroke="#b45309" stroke-width="1.5"/>
<path d="M 236 255 L 214 257 L 214 285 L 238 282 Z" fill="#d97706" stroke="#b45309" stroke-width="1.5"/>
</g>
<!-- 6. FOOT PROTECTION -->
<!-- Steel-Toe Safety Boots -->
<g id="layer-gear-steel_toe_boots" style="display: none;">
<path d="M 100 365 L 142 365 L 146 405 L 94 405 Z" fill="#1c1917" stroke="#44403c" stroke-width="2"/>
<path d="M 94 395 L 115 395 L 115 405 L 94 405 Z" fill="#78716c"/> <!-- Steel Cap Plate -->
<path d="M 158 365 L 200 365 L 206 405 L 154 405 Z" fill="#1c1917" stroke="#44403c" stroke-width="2"/>
<path d="M 185 395 L 206 395 L 206 405 L 185 405 Z" fill="#78716c"/>
</g>
<!-- Chemical Rubber Boots -->
<g id="layer-gear-chemical_rubber_boots" style="display: none;">
<path d="M 98 345 L 144 345 L 146 408 L 92 408 Z" fill="#047857" stroke="#064e3b" stroke-width="2.5"/>
<path d="M 156 345 L 202 345 L 208 408 L 154 408 Z" fill="#047857" stroke="#064e3b" stroke-width="2.5"/>
</g>
<!-- Canvas Running Sneakers (Distractor) -->
<g id="layer-gear-regular_sneakers" style="display: none;">
<path d="M 102 375 L 140 375 L 144 398 L 96 398 Z" fill="#ef4444" stroke="#ffffff" stroke-width="2"/>
<path d="M 160 375 L 198 375 L 204 398 L 156 398 Z" fill="#ef4444" stroke="#ffffff" stroke-width="2"/>
</g>
</svg>
`;
this.updateLayers();
}
updateLayers() {
if (!this.container) return;
// All gear IDs configured across scenarios
const allGearIds = [
'hard_hat_cap', 'hard_hat_chemical',
'faceshield_glasses', 'chemical_splash_goggles', 'regular_sunglasses', 'standard_safety_glasses',
'earmuffs_heavy', 'wireless_earbuds', 'organic_vapor_respirator', 'cloth_dust_mask',
'heavy_leather_jacket', 'chemical_resistant_apron', 'cotton_lab_coat',
'leather_grinding_gloves', 'nitrile_gauntlet_gloves', 'thin_latex_gloves', 'leather_work_gloves',
'steel_toe_boots', 'chemical_rubber_boots', 'regular_sneakers'
];
allGearIds.forEach(gearId => {
const el = document.getElementById(`layer-gear-${gearId}`);
if (el) {
el.style.display = this.equippedItems.has(gearId) ? 'block' : 'none';
}
});
// Toggle bare hands layer if gloves equipped
const bareHandsEl = document.getElementById('layer-hands-bare');
const hasAnyGloves = ['leather_grinding_gloves', 'nitrile_gauntlet_gloves', 'thin_latex_gloves', 'leather_work_gloves']
.some(g => this.equippedItems.has(g));
if (bareHandsEl) {
bareHandsEl.style.display = hasAnyGloves ? 'none' : 'block';
}
// Toggle base feet layer if special boots equipped
const baseFeetEl = document.getElementById('layer-feet-base');
const hasAnyBoots = ['steel_toe_boots', 'chemical_rubber_boots', 'regular_sneakers']
.some(b => this.equippedItems.has(b));
if (baseFeetEl) {
baseFeetEl.style.display = hasAnyBoots ? 'none' : 'block';
}
}
}