diff --git a/ChatGPT Image Sep 25, 2026, 07_31_35 PM.png b/ChatGPT Image Sep 25, 2026, 07_31_35 PM.png new file mode 100644 index 0000000..f6c59f6 Binary files /dev/null and b/ChatGPT Image Sep 25, 2026, 07_31_35 PM.png differ diff --git a/css/styles.css b/css/styles.css index 84bad23..7f081fe 100644 --- a/css/styles.css +++ b/css/styles.css @@ -1,32 +1,36 @@ /* ========================================================================== PPE SAFETY CHALLENGE - MODERN INDUSTRIAL SAFETY STYLESHEET "Choose Right. Work Safe." + Matches the official industrial UI layout and exact 3x3 locker grid ========================================================================== */ :root { - /* Color Palette */ - --bg-dark: #0f141c; - --bg-card: #171f2c; - --bg-card-hover: #1e2838; - --bg-stage: #141b24; - --panel-border: #26354a; - + /* Core Color Palette */ + --bg-dark: #070d18; + --bg-panel: #0d1524; + --bg-card: #131d2e; + --bg-card-hover: #1b283d; + --bg-stage: #0a111e; + --panel-border: #1b283d; + --panel-border-light: #253752; + --safety-yellow: #facc15; + --safety-gold: #eab308; --safety-amber: #f59e0b; --safety-orange: #fb923c; --safety-red: #ef4444; --safety-green: #10b981; --safety-blue: #0ea5e9; - --safety-cyan: #06b6d4; + --safety-cyan: #38bdf8; - --text-main: #f3f4f6; - --text-muted: #9ca3af; - --text-dim: #6b7280; + --text-main: #f8fafc; + --text-muted: #94a3b8; + --text-dim: #64748b; --glow-yellow: rgba(250, 204, 21, 0.35); --glow-green: rgba(16, 185, 129, 0.4); --glow-red: rgba(239, 68, 68, 0.45); - --glow-blue: rgba(14, 165, 233, 0.35); + --glow-blue: rgba(56, 189, 248, 0.35); --font-main: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; --font-display: 'Chakra Petch', 'Inter', sans-serif; @@ -34,7 +38,7 @@ --radius-sm: 6px; --radius-md: 10px; - --radius-lg: 16px; + --radius-lg: 14px; --radius-full: 9999px; --transition-fast: 0.15s ease; @@ -66,7 +70,7 @@ html, body { flex-direction: column; min-height: 100vh; width: 100%; - max-width: 1600px; + max-width: 1680px; margin: 0 auto; padding: 0 16px 16px 16px; } @@ -79,12 +83,12 @@ html, body { display: flex; align-items: center; justify-content: space-between; - padding: 12px 18px; - margin: 12px 0; - background: linear-gradient(90deg, #131a24 0%, #172233 100%); + padding: 10px 18px; + margin: 10px 0 12px 0; + background: linear-gradient(90deg, #0c1422 0%, #111b2b 100%); border: 1px solid var(--panel-border); border-radius: var(--radius-md); - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.35); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.45); } .brand-badge { @@ -93,18 +97,18 @@ html, body { gap: 12px; } -.safety-cross-icon { +.safety-helmet-icon { width: 36px; height: 36px; - background: var(--safety-yellow); - color: #000; - font-weight: 900; - font-size: 20px; display: flex; align-items: center; justify-content: center; - border-radius: var(--radius-sm); - box-shadow: 0 0 12px var(--glow-yellow); +} + +.helmet-svg { + width: 100%; + height: 100%; + filter: drop-shadow(0 0 6px rgba(250, 204, 21, 0.4)); } .brand-text { @@ -121,7 +125,7 @@ html, body { } .game-tagline { - font-size: 0.72rem; + font-size: 0.7rem; font-weight: 700; letter-spacing: 1.5px; color: var(--safety-yellow); @@ -131,9 +135,9 @@ html, body { .scenario-stepper { display: flex; align-items: center; - gap: 10px; - background: rgba(0, 0, 0, 0.3); - padding: 6px 14px; + gap: 8px; + background: rgba(0, 0, 0, 0.4); + padding: 5px 14px; border-radius: var(--radius-full); border: 1px solid var(--panel-border); } @@ -141,10 +145,10 @@ html, body { .step-pill { display: flex; align-items: center; - gap: 8px; + gap: 6px; padding: 4px 12px; border-radius: var(--radius-full); - font-size: 0.82rem; + font-size: 0.8rem; font-weight: 600; color: var(--text-dim); background: transparent; @@ -152,21 +156,22 @@ html, body { } .step-pill .step-num { - width: 20px; - height: 20px; + width: 18px; + height: 18px; border-radius: 50%; background: var(--panel-border); color: var(--text-muted); display: flex; align-items: center; justify-content: center; - font-size: 0.75rem; + font-size: 0.72rem; + font-weight: 700; } .step-pill.active { - background: rgba(250, 204, 21, 0.15); + background: rgba(250, 204, 21, 0.18); color: var(--safety-yellow); - border: 1px solid var(--safety-yellow); + border: 1px solid rgba(250, 204, 21, 0.6); } .step-pill.active .step-num { @@ -184,10 +189,10 @@ html, body { color: #fff; } -.step-connector { - width: 24px; - height: 2px; - background: var(--panel-border); +.step-dots { + color: var(--text-dim); + font-size: 0.85rem; + letter-spacing: 2px; } /* HUD Right: Score & Audio Controls */ @@ -201,14 +206,14 @@ html, body { display: flex; align-items: center; gap: 8px; - background: rgba(0, 0, 0, 0.4); - padding: 6px 14px; + background: rgba(0, 0, 0, 0.45); + padding: 5px 14px; border-radius: var(--radius-md); border: 1px solid var(--panel-border); } .score-label { - font-size: 0.7rem; + font-size: 0.68rem; font-weight: 700; letter-spacing: 1px; color: var(--text-muted); @@ -217,26 +222,30 @@ html, body { .score-val { font-family: var(--font-mono); font-size: 1.15rem; - font-weight: 700; + font-weight: 800; color: var(--safety-yellow); - min-width: 40px; + min-width: 30px; text-align: right; } -.score-stars { +.score-star-icon { color: var(--safety-yellow); font-size: 1rem; - letter-spacing: 2px; } -.btn-icon { - width: 38px; - height: 38px; +.score-stars-sub { + color: var(--text-dim); + font-size: 0.95rem; +} + +.btn-icon-hud { + width: 36px; + height: 36px; border-radius: var(--radius-md); - background: var(--bg-card); + background: #141f2f; border: 1px solid var(--panel-border); color: var(--text-main); - font-size: 1.1rem; + font-size: 1rem; display: flex; align-items: center; justify-content: center; @@ -244,14 +253,14 @@ html, body { transition: var(--transition-fast); } -.btn-icon:hover { - background: var(--bg-card-hover); - border-color: var(--safety-yellow); +.btn-icon-hud:hover { + background: #1c2b42; + border-color: var(--safety-cyan); transform: translateY(-1px); } /* ========================================================================== - SCREEN VIEWS MANAGEMENT + MAIN VIEWPORT & SCREEN MANAGEMENT ========================================================================== */ .main-viewport { @@ -283,7 +292,7 @@ html, body { .intro-card { max-width: 820px; margin: 30px auto; - background: linear-gradient(180deg, #16202e 0%, #101722 100%); + background: linear-gradient(180deg, #131d2e 0%, #0d1524 100%); border: 1px solid var(--panel-border); border-radius: var(--radius-lg); padding: 40px; @@ -300,9 +309,12 @@ html, body { } .safety-shield-animated { - width: 100px; - height: 100px; + width: 80px; + height: 80px; position: relative; + display: flex; + align-items: center; + justify-content: center; } .shield-svg { @@ -318,32 +330,15 @@ html, body { .shield-plus { stroke: var(--safety-yellow); - stroke-width: 8; + stroke-width: 6; stroke-linecap: round; } -.pulse-ring { - position: absolute; - top: -10px; - left: -10px; - right: -10px; - bottom: -10px; - border-radius: 50%; - border: 2px solid var(--safety-yellow); - opacity: 0; - animation: pulseAnim 2s infinite ease-out; -} - -@keyframes pulseAnim { - 0% { transform: scale(0.8); opacity: 0.8; } - 100% { transform: scale(1.3); opacity: 0; } -} - .hero-title { font-family: var(--font-display); - font-size: 2.5rem; + font-size: 2.2rem; font-weight: 700; - letter-spacing: 2px; + letter-spacing: 1px; color: #fff; margin-bottom: 4px; } @@ -358,11 +353,11 @@ html, body { } .mission-briefing { - background: rgba(0, 0, 0, 0.3); + background: rgba(0, 0, 0, 0.35); border: 1px solid var(--panel-border); border-radius: var(--radius-md); padding: 24px; - margin-bottom: 30px; + margin-bottom: 28px; text-align: center; max-width: 700px; } @@ -376,8 +371,9 @@ html, body { .mission-briefing p { color: var(--text-muted); - font-size: 0.95rem; - margin-bottom: 20px; + font-size: 0.92rem; + margin-bottom: 18px; + line-height: 1.5; } .learning-flow-banner { @@ -386,15 +382,15 @@ html, body { justify-content: center; flex-wrap: wrap; gap: 10px; - background: #111827; + background: #090f19; padding: 12px 18px; border-radius: var(--radius-md); - border: 1px solid #374151; + border: 1px solid var(--panel-border); } .flow-item { font-family: var(--font-mono); - font-size: 0.8rem; + font-size: 0.78rem; font-weight: 700; color: var(--safety-yellow); } @@ -416,10 +412,7 @@ html, body { color: var(--text-dim); } -/* ========================================================================== - BUTTONS & COMMON UI - ========================================================================== */ - +/* Common Button Styles */ .btn { font-family: var(--font-display); font-weight: 700; @@ -459,153 +452,84 @@ html, body { border-color: var(--safety-yellow); } -.btn-action { - background: linear-gradient(135deg, var(--safety-amber) 0%, var(--safety-yellow) 100%); - color: #000; - font-size: 1rem; - font-weight: 800; - padding: 14px 20px; -} - -.btn-action:hover { - filter: brightness(1.1); - transform: translateY(-2px); - box-shadow: 0 6px 20px var(--glow-yellow); -} - -.btn-success { - background: var(--safety-green); - color: #fff; - font-size: 1rem; - padding: 12px 24px; -} - -.btn-success:hover { - background: #059669; - box-shadow: 0 6px 20px var(--glow-green); -} - -.btn-warning { - background: var(--safety-orange); - color: #000; - font-weight: 800; - padding: 12px 24px; -} - -.btn-warning:hover { - background: #f97316; - box-shadow: 0 6px 20px rgba(249, 115, 22, 0.4); -} - -.btn-ghost { - background: transparent; - border: 1px dashed var(--panel-border); - color: var(--text-muted); -} - -.btn-ghost:hover { - border-color: var(--text-main); - color: var(--text-main); -} - .btn-large { padding: 14px 32px; font-size: 1.1rem; } -.btn-small { - padding: 6px 12px; - font-size: 0.8rem; -} - -.btn-full { - width: 100%; -} - -.glow-ready { - animation: readyPulse 2s infinite ease-in-out; -} - -@keyframes readyPulse { - 0%, 100% { box-shadow: 0 0 10px rgba(250, 204, 21, 0.3); } - 50% { box-shadow: 0 0 25px rgba(250, 204, 21, 0.7); } -} - -/* Card Panels */ -.card-panel { - background: var(--bg-card); - border: 1px solid var(--panel-border); - border-radius: var(--radius-lg); - padding: 18px; - display: flex; - flex-direction: column; -} - -.panel-header { - margin-bottom: 14px; -} - -.card-subtitle { - font-size: 0.7rem; - font-weight: 700; - letter-spacing: 1.2px; - color: var(--text-dim); - margin-bottom: 6px; - text-transform: uppercase; -} - /* ========================================================================== SCREEN 2: GAMEPLAY 3-COLUMN GRID ========================================================================== */ .gameplay-grid { display: grid; - grid-template-columns: 300px 1fr 390px; - gap: 16px; + grid-template-columns: 290px 1fr 360px; + gap: 14px; width: 100%; flex: 1; } -/* Column 1: Hazard Panel */ +.card-panel { + background: var(--bg-panel); + border: 1px solid var(--panel-border); + border-radius: var(--radius-lg); + padding: 16px; + display: flex; + flex-direction: column; +} + +/* ========================================================================== + LEFT COLUMN: HAZARD ASSESSMENT & WORKER PROTECTION STATUS + ========================================================================== */ + .sidebar-hazard-panel { gap: 12px; overflow-y: auto; } +.panel-header { + margin-bottom: 4px; +} + .scenario-badge { display: inline-block; font-family: var(--font-mono); - font-size: 0.72rem; + font-size: 0.7rem; font-weight: 700; color: var(--safety-cyan); - background: rgba(6, 182, 212, 0.12); - padding: 3px 8px; + background: rgba(56, 189, 248, 0.12); + padding: 2px 8px; border-radius: var(--radius-sm); - margin-bottom: 4px; + margin-bottom: 6px; } .scenario-title { font-family: var(--font-display); font-size: 1.15rem; + font-weight: 700; color: #fff; line-height: 1.25; -} - -.situation-card { - background: rgba(0, 0, 0, 0.25); - padding: 12px; - border-radius: var(--radius-md); - border: 1px solid rgba(255, 255, 255, 0.05); + margin-bottom: 4px; } .situation-desc { - font-size: 0.85rem; + font-size: 0.8rem; color: var(--text-muted); - line-height: 1.4; + line-height: 1.35; } +.card-subtitle-small { + font-size: 0.7rem; + font-weight: 800; + letter-spacing: 1px; + color: var(--text-muted); + margin-bottom: 8px; + text-transform: uppercase; +} + +/* Identify Hazards Card */ .hazard-identification-card { - background: rgba(239, 68, 68, 0.08); + background: #111a28; border: 1px solid rgba(239, 68, 68, 0.25); padding: 12px; border-radius: var(--radius-md); @@ -622,7 +546,7 @@ html, body { display: flex; align-items: flex-start; gap: 8px; - font-size: 0.8rem; + font-size: 0.78rem; color: #fca5a5; line-height: 1.3; } @@ -633,53 +557,115 @@ html, body { margin-top: 1px; } -.equipped-status-card { - background: rgba(0, 0, 0, 0.25); +/* Worker Protection Status Card (With Blue Neon Silhouette) */ +.worker-protection-card { + background: #111a28; + border: 1px solid var(--panel-border-light); padding: 12px; border-radius: var(--radius-md); - border: 1px solid rgba(255, 255, 255, 0.05); } -.coverage-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 6px; +.protection-status-content { + display: flex; + align-items: center; + gap: 14px; } -.coverage-item { +.body-silhouette-box { + width: 56px; + height: 130px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(56, 189, 248, 0.05); + border: 1px solid rgba(56, 189, 248, 0.2); + border-radius: var(--radius-sm); + padding: 4px; +} + +.wireframe-body-svg { + width: 100%; + height: 100%; + filter: drop-shadow(0 0 4px rgba(56, 189, 248, 0.5)); +} + +.protection-rows { + flex: 1; + display: flex; + flex-direction: column; + gap: 5px; +} + +.status-row-item { display: flex; align-items: center; justify-content: space-between; - background: #121924; - padding: 6px 8px; + background: #162235; + padding: 4px 8px; border-radius: var(--radius-sm); border: 1px solid var(--panel-border); - font-size: 0.75rem; + font-size: 0.74rem; + transition: var(--transition-fast); } -.coverage-item .cov-label { - color: var(--text-muted); +.status-row-item .row-icon { + font-size: 0.85rem; + margin-right: 4px; +} + +.status-row-item .row-label { + flex: 1; + color: #fff; font-weight: 500; } -.coverage-item .cov-status.missing { - color: var(--safety-red); - font-weight: 700; -} - -.coverage-item .cov-status.protected { - color: var(--safety-green); - font-weight: 700; -} - -.action-dock { - margin-top: auto; +.status-row-item .row-badge { + width: 18px; + height: 18px; + border-radius: 50%; display: flex; - flex-direction: column; - gap: 8px; + align-items: center; + justify-content: center; + font-size: 0.7rem; + font-weight: 800; } -/* Column 2: Center Stage Panel */ +.status-row-item .row-badge.status-q { + background: #0284c7; + color: #fff; +} + +.status-row-item .row-badge.status-check { + background: var(--safety-green); + color: #fff; +} + +/* Tip Card */ +.tip-card { + background: #131d2e; + border: 1px solid var(--panel-border); + padding: 10px 12px; + border-radius: var(--radius-md); + margin-top: auto; +} + +.tip-title { + font-size: 0.72rem; + font-weight: 800; + color: var(--safety-yellow); + margin-bottom: 3px; +} + +.tip-text { + font-size: 0.75rem; + color: var(--text-muted); + line-height: 1.35; +} + +/* ========================================================================== + CENTER COLUMN: INTERACTIVE WORKPLACE STAGE & 3D WORKER AVATAR + ========================================================================== */ + .center-stage-panel { padding: 0; display: flex; @@ -692,7 +678,7 @@ html, body { .scene-environment-container { flex: 1; position: relative; - min-height: 460px; + min-height: 480px; display: flex; align-items: center; justify-content: center; @@ -709,7 +695,6 @@ html, body { z-index: 15; } -/* Backdrop Layer */ .scene-backdrop { position: absolute; top: 0; @@ -717,7 +702,7 @@ html, body { right: 0; bottom: 0; z-index: 1; - background: #0d121a; + background: #090e18; overflow: hidden; } @@ -729,25 +714,48 @@ html, body { pointer-events: none; } -/* Worker Avatar Area */ .avatar-wrapper { position: relative; z-index: 10; width: 280px; - height: 420px; + height: 440px; display: flex; align-items: center; justify-content: center; } -.worker-svg { +.avatar-step-container { + position: relative; width: 100%; height: 100%; - filter: drop-shadow(0 10px 20px rgba(0, 0, 0, 0.5)); - transition: transform 0.3s ease; + display: flex; + align-items: center; + justify-content: center; } -/* Overlays: Consequence Freeze & Success */ +.avatar-step-img { + width: 100%; + height: 100%; + object-fit: contain; + filter: drop-shadow(0 12px 24px rgba(0, 0, 0, 0.55)); + transition: opacity 0.15s ease; + user-select: none; + pointer-events: none; +} + +.avatar-floor-shadow { + position: absolute; + bottom: 8px; + left: 50%; + transform: translateX(-50%); + width: 150px; + height: 18px; + background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 75%); + border-radius: 50%; + pointer-events: none; +} + +/* Consequence Alert Overlay & Safe Success Overlay */ .consequence-freeze-overlay, .success-overlay { position: absolute; top: 0; @@ -758,7 +766,7 @@ html, body { display: none; align-items: center; justify-content: center; - padding: 24px; + padding: 20px; backdrop-filter: blur(6px); animation: popOverlay 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards; } @@ -781,9 +789,9 @@ html, body { background: #180c0c; border: 2px solid var(--safety-red); border-radius: var(--radius-lg); - padding: 24px; + padding: 22px; width: 95%; - max-width: 640px; + max-width: 620px; max-height: 90vh; display: flex; flex-direction: column; @@ -795,13 +803,13 @@ html, body { .hazard-flasher { display: inline-block; font-family: var(--font-mono); - font-size: 0.85rem; + font-size: 0.82rem; font-weight: 800; color: #fff; background: var(--safety-red); padding: 5px 14px; border-radius: var(--radius-sm); - margin-bottom: 10px; + margin-bottom: 8px; animation: flashRed 0.8s infinite alternate; letter-spacing: 0.5px; } @@ -813,35 +821,35 @@ html, body { .consequence-title { font-family: var(--font-display); - font-size: 1.35rem; + font-size: 1.3rem; color: #fff; - margin-bottom: 6px; + margin-bottom: 4px; } .consequence-desc { - font-size: 0.85rem; + font-size: 0.82rem; color: #fca5a5; line-height: 1.35; - margin-bottom: 14px; + margin-bottom: 12px; } .missing-items-box { background: rgba(0, 0, 0, 0.5); border: 1px solid rgba(239, 68, 68, 0.35); - padding: 12px; + padding: 10px 12px; border-radius: var(--radius-md); - margin-bottom: 16px; + margin-bottom: 14px; text-align: left; } .missing-box-header { margin-bottom: 8px; - padding-bottom: 6px; + padding-bottom: 4px; border-bottom: 1px solid rgba(239, 68, 68, 0.2); } .missing-items-box .box-label { - font-size: 0.72rem; + font-size: 0.7rem; font-weight: 800; color: var(--safety-yellow); letter-spacing: 1px; @@ -850,36 +858,21 @@ html, body { .missing-hazard-cards { display: flex; flex-direction: column; - gap: 8px; - max-height: 240px; + gap: 6px; + max-height: 220px; overflow-y: auto; padding-right: 4px; } -/* Custom Scrollbar for Missing Cards */ -.missing-hazard-cards::-webkit-scrollbar { - width: 6px; -} -.missing-hazard-cards::-webkit-scrollbar-thumb { - background: rgba(239, 68, 68, 0.4); - border-radius: 4px; -} - .hazard-card-item { display: flex; align-items: flex-start; - gap: 10px; + gap: 8px; background: rgba(239, 68, 68, 0.08); border: 1px solid rgba(239, 68, 68, 0.35); border-left: 4px solid var(--safety-red); - padding: 8px 10px; + padding: 6px 8px; border-radius: var(--radius-sm); - transition: var(--transition-fast); -} - -.hazard-card-item:hover { - background: rgba(239, 68, 68, 0.16); - border-color: var(--safety-red); } .hazard-card-item.distractor-card { @@ -889,7 +882,7 @@ html, body { } .hazard-card-item .card-icon { - font-size: 1.25rem; + font-size: 1.1rem; line-height: 1; padding-top: 2px; } @@ -905,11 +898,11 @@ html, body { display: flex; align-items: center; justify-content: space-between; - gap: 8px; + gap: 6px; } .hazard-card-item .card-item-name { - font-size: 0.82rem; + font-size: 0.8rem; font-weight: 700; color: #fff; } @@ -922,34 +915,24 @@ html, body { background: rgba(239, 68, 68, 0.2); padding: 1px 6px; border-radius: 4px; - text-transform: uppercase; -} - -.hazard-card-item.distractor-card .card-slot-badge { - color: var(--safety-amber); - background: rgba(245, 158, 11, 0.2); } .hazard-card-item .card-hazard-text { - font-size: 0.74rem; + font-size: 0.72rem; color: #fca5a5; - line-height: 1.3; + line-height: 1.25; } -.hazard-card-item.distractor-card .card-hazard-text { - color: #fef08a; +.btn-warning { + background: var(--safety-orange); + color: #000; + font-weight: 800; + padding: 10px 20px; } -/* Flashing Alert for Coverage Grid on Failure */ -.coverage-item.alert-missing { - border-color: var(--safety-red); - background: rgba(239, 68, 68, 0.15); - animation: borderAlert 0.8s infinite alternate; -} - -@keyframes borderAlert { - from { box-shadow: 0 0 2px var(--safety-red); } - to { box-shadow: 0 0 8px var(--safety-red); } +.btn-warning:hover { + background: #f97316; + box-shadow: 0 4px 16px rgba(249, 115, 22, 0.4); } /* Success Overlay */ @@ -962,8 +945,8 @@ html, body { background: #091a13; border: 2px solid var(--safety-green); border-radius: var(--radius-lg); - padding: 28px; - max-width: 480px; + padding: 24px; + max-width: 460px; text-align: center; box-shadow: 0 0 40px var(--glow-green); } @@ -972,107 +955,87 @@ html, body { display: flex; align-items: center; justify-content: center; - gap: 10px; - margin-bottom: 10px; + gap: 8px; + margin-bottom: 8px; } .badge-icon { - font-size: 1.6rem; + font-size: 1.5rem; } .badge-title { font-family: var(--font-display); - font-size: 1.5rem; + font-size: 1.4rem; font-weight: 700; color: var(--safety-green); } .success-desc { - font-size: 0.95rem; + font-size: 0.88rem; color: #a7f3d0; line-height: 1.4; - margin-bottom: 18px; + margin-bottom: 16px; } .points-awarded-pill { display: inline-flex; align-items: center; - gap: 8px; + gap: 6px; background: rgba(250, 204, 21, 0.2); border: 1px solid var(--safety-yellow); color: var(--safety-yellow); - padding: 8px 18px; + padding: 6px 16px; border-radius: var(--radius-full); font-family: var(--font-mono); font-weight: 800; - font-size: 1.05rem; - margin-bottom: 20px; - animation: starBounce 0.5s ease; + font-size: 0.95rem; + margin-bottom: 18px; } -@keyframes starBounce { - 0% { transform: scale(0.5); opacity: 0; } - 60% { transform: scale(1.2); opacity: 1; } - 100% { transform: scale(1); } +.btn-success { + background: var(--safety-green); + color: #fff; + font-size: 0.95rem; + padding: 10px 22px; } -/* Stage Footer Bar */ -.stage-footer-bar { +.btn-success:hover { + background: #059669; + box-shadow: 0 4px 16px var(--glow-green); +} + +/* ========================================================================== + RIGHT COLUMN: 3x3 PPE SAFETY LOCKER & ACTION BUTTONS + ========================================================================== */ + +.sidebar-locker-panel { + gap: 12px; display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 16px; - background: #0d131c; - border-top: 1px solid var(--panel-border); + flex-direction: column; } -.status-indicator-light { +.panel-header-locker { + display: flex; + flex-direction: column; + gap: 2px; +} + +.locker-title-row { display: flex; align-items: center; gap: 8px; } -.light-dot { - width: 10px; - height: 10px; - border-radius: 50%; - background: var(--safety-amber); - box-shadow: 0 0 8px var(--safety-amber); -} - -.status-indicator-light.running .light-dot { - background: var(--safety-green); - box-shadow: 0 0 10px var(--safety-green); -} - -.status-indicator-light.danger .light-dot { - background: var(--safety-red); - box-shadow: 0 0 10px var(--safety-red); -} - -.light-text { - font-family: var(--font-mono); - font-size: 0.75rem; - font-weight: 700; - color: var(--text-muted); -} - -.audio-feedback-caption { - font-family: var(--font-mono); - font-size: 0.72rem; - color: var(--text-dim); -} - -/* Column 3: PPE Selection Locker */ -.sidebar-locker-panel { - gap: 12px; - overflow-y: auto; +.locker-icon { + font-size: 1.1rem; } .locker-title { font-family: var(--font-display); - font-size: 1.05rem; + font-size: 1.15rem; + font-weight: 700; color: #fff; + letter-spacing: 0.5px; } .locker-subtitle { @@ -1080,132 +1043,189 @@ html, body { color: var(--text-muted); } +.locker-instruction { + font-size: 0.72rem; + color: var(--safety-cyan); +} + +/* 3x3 Grid Layout (Square Cards) */ .ppe-locker-grid { display: grid; - grid-template-columns: repeat(2, 1fr); + grid-template-columns: repeat(3, 1fr); gap: 10px; + width: 100%; + margin-bottom: auto; } .ppe-item-card { display: flex; flex-direction: column; align-items: center; - justify-content: center; + justify-content: space-between; text-align: center; - background: linear-gradient(180deg, #151e2b 0%, #0f1622 100%); - border: 1.5px solid var(--panel-border); + background: #09121f; + border: 1.5px solid #162740; border-radius: var(--radius-md); - padding: 14px 8px 10px 8px; + padding: 8px 4px 6px 4px; cursor: pointer; transition: var(--transition-normal); position: relative; - min-height: 106px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); + aspect-ratio: 1 / 1.18; + width: 100%; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35); } .ppe-item-card:hover { - background: linear-gradient(180deg, #1c293a 0%, #14202e 100%); - border-color: var(--safety-yellow); + background: #111e33; + border-color: var(--safety-cyan); transform: translateY(-2px); - box-shadow: 0 6px 18px rgba(250, 204, 21, 0.2); + box-shadow: 0 4px 14px rgba(56, 189, 248, 0.25); } .ppe-item-card.equipped { - background: linear-gradient(180deg, rgba(16, 185, 129, 0.2) 0%, rgba(16, 185, 129, 0.08) 100%); - border-color: var(--safety-green); - box-shadow: 0 0 16px rgba(16, 185, 129, 0.28); -} - -.item-key-badge { - position: absolute; - top: 5px; - left: 6px; - font-family: var(--font-mono); - font-size: 0.65rem; - font-weight: 800; - color: var(--text-dim); - background: rgba(0, 0, 0, 0.45); - padding: 1px 5px; - border-radius: 4px; - border: 1px solid rgba(255, 255, 255, 0.08); + background: linear-gradient(180deg, rgba(14, 165, 233, 0.2) 0%, rgba(14, 165, 233, 0.08) 100%); + border-color: var(--safety-cyan); + box-shadow: 0 0 14px rgba(56, 189, 248, 0.35); } +/* Radio Indicator Top-Right */ .item-toggle-indicator { position: absolute; - top: 5px; + top: 6px; right: 6px; - width: 20px; - height: 20px; + width: 16px; + height: 16px; border-radius: 50%; - border: 1px solid var(--panel-border); + border: 1.5px solid #1e3556; background: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; - font-size: 0.72rem; - font-weight: 800; - color: transparent; transition: var(--transition-fast); } .ppe-item-card.equipped .item-toggle-indicator { - background: var(--safety-green); - color: #fff; - border-color: var(--safety-green); - box-shadow: 0 0 8px var(--safety-green); + background: var(--safety-cyan); + border-color: var(--safety-cyan); +} + +.radio-check-mark { + font-size: 0.65rem; + font-weight: 900; + color: #000; + line-height: 1; } .item-icon-box { - width: auto; - height: auto; - background: transparent; - font-size: 2.35rem; - line-height: 1; - margin-top: 4px; - margin-bottom: 8px; + width: 100%; + flex: 1; + min-height: 0; display: flex; align-items: center; justify-content: center; - filter: drop-shadow(0 4px 8px rgba(0,0,0,0.5)); + margin: 2px 0; transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1); } +.ppe-svg-icon { + width: 80%; + height: 80%; + max-width: 68px; + max-height: 68px; + filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.5)); +} + +.ppe-img-icon { + width: 85%; + height: 85%; + max-width: 74px; + max-height: 74px; + object-fit: contain; + filter: drop-shadow(0 3px 8px rgba(0, 0, 0, 0.55)); +} + .ppe-item-card:hover .item-icon-box { - transform: scale(1.18); + transform: scale(1.08); } .item-name { font-family: var(--font-display); - font-size: 0.82rem; + font-size: 0.72rem; font-weight: 700; color: #fff; - line-height: 1.25; + line-height: 1.18; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; + padding: 0 2px; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); } -.locker-guide-card { - background: rgba(14, 165, 233, 0.08); - border: 1px solid rgba(14, 165, 233, 0.2); - padding: 12px; - border-radius: var(--radius-md); +/* Bottom Actions Dock */ +.locker-actions-dock { + display: flex; + flex-direction: column; + gap: 8px; margin-top: auto; + padding-top: 4px; } -.guide-title { - font-size: 0.75rem; +.btn-check-ppe { + background: #1a2536; + border: 1px solid var(--panel-border-light); + color: #cbd5e1; + font-family: var(--font-display); + font-size: 0.85rem; font-weight: 700; - color: var(--safety-cyan); - margin-bottom: 4px; + padding: 8px 16px; + border-radius: var(--radius-md); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + transition: var(--transition-fast); } -.guide-text { - font-size: 0.75rem; - color: var(--text-muted); - line-height: 1.35; +.btn-check-ppe:hover { + background: #24344c; + color: #fff; + border-color: var(--safety-cyan); +} + +.btn-check-icon { + font-weight: 900; + color: var(--safety-cyan); +} + +.btn-start-task { + background: var(--safety-yellow); + color: #000; + font-family: var(--font-display); + font-size: 1.05rem; + font-weight: 900; + letter-spacing: 0.5px; + padding: 12px 18px; + border-radius: var(--radius-md); + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + box-shadow: 0 4px 16px var(--glow-yellow); + transition: var(--transition-normal); +} + +.btn-start-task:hover { + background: #fde047; + transform: translateY(-2px); + box-shadow: 0 6px 24px var(--glow-yellow); +} + +.btn-play-icon { + font-size: 0.95rem; } /* ========================================================================== @@ -1215,14 +1235,14 @@ html, body { .summary-card { max-width: 900px; margin: 20px auto; - background: linear-gradient(180deg, #16202e 0%, #101722 100%); + background: linear-gradient(180deg, #131d2e 0%, #0d1524 100%); border: 1px solid var(--panel-border); border-radius: var(--radius-lg); padding: 36px; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5); display: flex; flex-direction: column; - gap: 24px; + gap: 22px; } .summary-header { @@ -1231,7 +1251,7 @@ html, body { .victory-trophy { font-size: 3rem; - margin-bottom: 8px; + margin-bottom: 6px; } .summary-title { @@ -1250,11 +1270,11 @@ html, body { .metrics-dashboard { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 16px; + gap: 14px; } .metric-box { - background: rgba(0, 0, 0, 0.3); + background: rgba(0, 0, 0, 0.35); border: 1px solid var(--panel-border); border-radius: var(--radius-md); padding: 16px; @@ -1278,20 +1298,16 @@ html, body { color: var(--safety-yellow); } -.metric-val.star-gold { - letter-spacing: 4px; -} - .metric-sub { - font-size: 0.75rem; + font-size: 0.74rem; color: var(--text-dim); } .takeaway-banner { - background: #0d141e; + background: #090f19; border: 2px solid var(--safety-yellow); border-radius: var(--radius-md); - padding: 16px 20px; + padding: 14px 18px; text-align: center; } @@ -1301,7 +1317,7 @@ html, body { font-weight: 700; letter-spacing: 1.5px; color: var(--safety-yellow); - margin-bottom: 12px; + margin-bottom: 10px; } .mantra-chain { @@ -1313,10 +1329,10 @@ html, body { } .mantra-step { - background: #192333; - padding: 6px 12px; + background: #162235; + padding: 5px 10px; border-radius: var(--radius-sm); - font-size: 0.8rem; + font-size: 0.78rem; color: #fff; border: 1px solid var(--panel-border); } @@ -1329,20 +1345,20 @@ html, body { .scenarios-review-box { background: rgba(0, 0, 0, 0.25); border-radius: var(--radius-md); - padding: 16px; + padding: 14px; } .scenarios-review-box h4 { font-size: 0.9rem; color: #fff; - margin-bottom: 12px; + margin-bottom: 10px; } .review-row { display: flex; align-items: center; gap: 12px; - padding: 10px 0; + padding: 8px 0; border-bottom: 1px solid var(--panel-border); } @@ -1351,7 +1367,7 @@ html, body { } .rev-icon { - font-size: 1.4rem; + font-size: 1.3rem; } .rev-info { @@ -1359,31 +1375,31 @@ html, body { } .rev-info strong { - font-size: 0.85rem; + font-size: 0.82rem; color: #fff; display: block; } .rev-info p { - font-size: 0.78rem; + font-size: 0.75rem; color: var(--text-muted); line-height: 1.3; } .rev-status { font-family: var(--font-mono); - font-size: 0.75rem; + font-size: 0.72rem; font-weight: 700; color: var(--safety-green); background: rgba(16, 185, 129, 0.15); - padding: 4px 8px; + padding: 3px 8px; border-radius: var(--radius-sm); } .summary-actions { display: flex; justify-content: center; - gap: 16px; + gap: 14px; } /* ========================================================================== @@ -1414,11 +1430,11 @@ html, body { border-radius: var(--radius-lg); width: 90%; max-width: 440px; - padding: 24px; + padding: 22px; box-shadow: 0 16px 40px rgba(0, 0, 0, 0.6); display: flex; flex-direction: column; - gap: 18px; + gap: 16px; } .modal-header { @@ -1471,7 +1487,7 @@ html, body { } .audio-info-note { - font-size: 0.75rem; + font-size: 0.74rem; color: var(--text-muted); line-height: 1.35; background: rgba(0, 0, 0, 0.3); @@ -1564,22 +1580,22 @@ html, body { } /* ========================================================================== - RESPONSIVE ADJUSTMENTS + RESPONSIVE LAYOUT BREAKPOINTS ========================================================================== */ -@media (max-width: 1100px) { +@media (max-width: 1180px) { .gameplay-grid { - grid-template-columns: 280px 1fr 280px; + grid-template-columns: 260px 1fr 320px; } } -@media (max-width: 900px) { +@media (max-width: 960px) { .gameplay-grid { grid-template-columns: 1fr; grid-template-rows: auto auto auto; } .sidebar-hazard-panel, .sidebar-locker-panel { - max-height: 380px; + max-height: 400px; } .scene-environment-container { min-height: 380px; diff --git a/grinding_ppe_icons_png/01_safety_helmet.png b/grinding_ppe_icons_png/01_safety_helmet.png new file mode 100644 index 0000000..d37c71c Binary files /dev/null and b/grinding_ppe_icons_png/01_safety_helmet.png differ diff --git a/grinding_ppe_icons_png/02_safety_goggles.png b/grinding_ppe_icons_png/02_safety_goggles.png new file mode 100644 index 0000000..711dc0f Binary files /dev/null and b/grinding_ppe_icons_png/02_safety_goggles.png differ diff --git a/grinding_ppe_icons_png/03_face_shield.png b/grinding_ppe_icons_png/03_face_shield.png new file mode 100644 index 0000000..a80eeea Binary files /dev/null and b/grinding_ppe_icons_png/03_face_shield.png differ diff --git a/grinding_ppe_icons_png/04_hearing_protection.png b/grinding_ppe_icons_png/04_hearing_protection.png new file mode 100644 index 0000000..700659d Binary files /dev/null and b/grinding_ppe_icons_png/04_hearing_protection.png differ diff --git a/grinding_ppe_icons_png/05_work_gloves.png b/grinding_ppe_icons_png/05_work_gloves.png new file mode 100644 index 0000000..714b592 Binary files /dev/null and b/grinding_ppe_icons_png/05_work_gloves.png differ diff --git a/grinding_ppe_icons_png/06_leather_gloves.png b/grinding_ppe_icons_png/06_leather_gloves.png new file mode 100644 index 0000000..ff4a092 Binary files /dev/null and b/grinding_ppe_icons_png/06_leather_gloves.png differ diff --git a/grinding_ppe_icons_png/07_reflective_vest.png b/grinding_ppe_icons_png/07_reflective_vest.png new file mode 100644 index 0000000..0e27863 Binary files /dev/null and b/grinding_ppe_icons_png/07_reflective_vest.png differ diff --git a/grinding_ppe_icons_png/08_safety_shoes.png b/grinding_ppe_icons_png/08_safety_shoes.png new file mode 100644 index 0000000..5e7bf6f Binary files /dev/null and b/grinding_ppe_icons_png/08_safety_shoes.png differ diff --git a/grinding_ppe_icons_png/09_dust_mask.png b/grinding_ppe_icons_png/09_dust_mask.png new file mode 100644 index 0000000..33f84f6 Binary files /dev/null and b/grinding_ppe_icons_png/09_dust_mask.png differ diff --git a/images/gear/chem_boot_left.png b/images/gear/chem_boot_left.png new file mode 100644 index 0000000..2ef41a6 Binary files /dev/null and b/images/gear/chem_boot_left.png differ diff --git a/images/gear/chem_boot_right.png b/images/gear/chem_boot_right.png new file mode 100644 index 0000000..8b82e41 Binary files /dev/null and b/images/gear/chem_boot_right.png differ diff --git a/images/gear/chem_glove_left.png b/images/gear/chem_glove_left.png new file mode 100644 index 0000000..037cf8c Binary files /dev/null and b/images/gear/chem_glove_left.png differ diff --git a/images/gear/chem_glove_right.png b/images/gear/chem_glove_right.png new file mode 100644 index 0000000..1ae3fc6 Binary files /dev/null and b/images/gear/chem_glove_right.png differ diff --git a/images/gear/chem_work_glove_left.png b/images/gear/chem_work_glove_left.png new file mode 100644 index 0000000..a3ab5b0 Binary files /dev/null and b/images/gear/chem_work_glove_left.png differ diff --git a/images/gear/chem_work_glove_right.png b/images/gear/chem_work_glove_right.png new file mode 100644 index 0000000..6ac2ca9 Binary files /dev/null and b/images/gear/chem_work_glove_right.png differ diff --git a/images/gear/grind_boot_left.png b/images/gear/grind_boot_left.png new file mode 100644 index 0000000..821dc0a Binary files /dev/null and b/images/gear/grind_boot_left.png differ diff --git a/images/gear/grind_boot_right.png b/images/gear/grind_boot_right.png new file mode 100644 index 0000000..d18f240 Binary files /dev/null and b/images/gear/grind_boot_right.png differ diff --git a/images/gear/grind_leather_glove_left.png b/images/gear/grind_leather_glove_left.png new file mode 100644 index 0000000..cac23ab Binary files /dev/null and b/images/gear/grind_leather_glove_left.png differ diff --git a/images/gear/grind_leather_glove_right.png b/images/gear/grind_leather_glove_right.png new file mode 100644 index 0000000..a9585db Binary files /dev/null and b/images/gear/grind_leather_glove_right.png differ diff --git a/images/gear/grind_work_glove_left.png b/images/gear/grind_work_glove_left.png new file mode 100644 index 0000000..b458d32 Binary files /dev/null and b/images/gear/grind_work_glove_left.png differ diff --git a/images/gear/grind_work_glove_right.png b/images/gear/grind_work_glove_right.png new file mode 100644 index 0000000..a234a64 Binary files /dev/null and b/images/gear/grind_work_glove_right.png differ diff --git a/images/steps/01_no_ppe.png b/images/steps/01_no_ppe.png new file mode 100644 index 0000000..7ef1cfb Binary files /dev/null and b/images/steps/01_no_ppe.png differ diff --git a/images/steps/02_helmet.png b/images/steps/02_helmet.png new file mode 100644 index 0000000..79163ff Binary files /dev/null and b/images/steps/02_helmet.png differ diff --git a/images/steps/03_helmet_goggles.png b/images/steps/03_helmet_goggles.png new file mode 100644 index 0000000..81c4f07 Binary files /dev/null and b/images/steps/03_helmet_goggles.png differ diff --git a/images/steps/04_helmet_goggles_face_shield.png b/images/steps/04_helmet_goggles_face_shield.png new file mode 100644 index 0000000..8c7a0d0 Binary files /dev/null and b/images/steps/04_helmet_goggles_face_shield.png differ diff --git a/images/steps/05_helmet_goggles_hearing.png b/images/steps/05_helmet_goggles_hearing.png new file mode 100644 index 0000000..00f0c9a Binary files /dev/null and b/images/steps/05_helmet_goggles_hearing.png differ diff --git a/images/steps/06_helmet_goggles_hearing_work_gloves.png b/images/steps/06_helmet_goggles_hearing_work_gloves.png new file mode 100644 index 0000000..fd5323e Binary files /dev/null and b/images/steps/06_helmet_goggles_hearing_work_gloves.png differ diff --git a/images/steps/07_helmet_goggles_hearing_leather_gloves.png b/images/steps/07_helmet_goggles_hearing_leather_gloves.png new file mode 100644 index 0000000..f7752c0 Binary files /dev/null and b/images/steps/07_helmet_goggles_hearing_leather_gloves.png differ diff --git a/images/steps/08_helmet_goggles_hearing_leather_gloves_vest.png b/images/steps/08_helmet_goggles_hearing_leather_gloves_vest.png new file mode 100644 index 0000000..54540c5 Binary files /dev/null and b/images/steps/08_helmet_goggles_hearing_leather_gloves_vest.png differ diff --git a/images/steps/09_helmet_goggles_hearing_leather_gloves_vest_boots.png b/images/steps/09_helmet_goggles_hearing_leather_gloves_vest_boots.png new file mode 100644 index 0000000..e1f8db1 Binary files /dev/null and b/images/steps/09_helmet_goggles_hearing_leather_gloves_vest_boots.png differ diff --git a/images/steps/10_full_ppe_with_mask.png b/images/steps/10_full_ppe_with_mask.png new file mode 100644 index 0000000..02399b1 Binary files /dev/null and b/images/steps/10_full_ppe_with_mask.png differ diff --git a/images/worker_base.png b/images/worker_base.png new file mode 100644 index 0000000..857c382 Binary files /dev/null and b/images/worker_base.png differ diff --git a/index.html b/index.html index 9a76738..168d42f 100644 --- a/index.html +++ b/index.html @@ -16,7 +16,13 @@
-
✚
+
+ + + + + +
PPE SAFETY CHALLENGE CHOOSE RIGHT. WORK SAFE. @@ -29,7 +35,7 @@ 1 Grinding Ops
-
+
·······
2 Chemical Handling @@ -41,15 +47,19 @@
SAFETY SCORE 0 - ☆☆ + ★ + ☆
- - - +
@@ -84,7 +94,7 @@ ➔ 🦺 SELECT PPE ➔ - ⚙️ TEST SAFETY + ⚙️ START TASK @@ -94,7 +104,7 @@ START CHALLENGE
- ⌨️ Mouse Click or Number Keys (1-8) to equip • Spacebar to Test Task + ⌨️ Mouse Click or Number Keys (1-9) to equip • Spacebar to Start Task
@@ -104,71 +114,90 @@
- + @@ -180,12 +209,7 @@
-
-
-
-
-
-
+
@@ -239,35 +263,35 @@
- - - - + diff --git a/js/avatar.js b/js/avatar.js index 54a28cd..e74d188 100644 --- a/js/avatar.js +++ b/js/avatar.js @@ -1,6 +1,7 @@ /** - * PPE SAFETY CHALLENGE - DYNAMIC WORKER AVATAR SYSTEM - * Generates an SVG industrial worker avatar with modular layered PPE gear. + * PPE SAFETY CHALLENGE - DYNAMIC 2D WORKER AVATAR SYSTEM + * Uses the step-by-step transparent character images from ppe_character_steps_transparent_png! + * Dynamically switches character states as safety gear is equipped. */ class WorkerAvatar { @@ -30,7 +31,7 @@ class WorkerAvatar { } getAvatarCenterCoordinates() { - if (!this.container) return { x: 150, y: 200, eyeX: 140, eyeY: 100, handX: 190, handY: 220 }; + if (!this.container) return { x: 170, y: 220, eyeX: 170, eyeY: 110, handX: 230, handY: 260 }; const rect = this.container.getBoundingClientRect(); const parentRect = this.container.parentElement.getBoundingClientRect(); const relX = rect.left - parentRect.left + (rect.width * 0.5); @@ -38,260 +39,94 @@ class WorkerAvatar { return { x: relX, y: relY, - eyeX: relX - 10, - eyeY: relY - 110, - handX: relX + 45, - handY: relY + 15 + eyeX: relX, + eyeY: relY - 130, + handX: relX + 55, + handY: relY + 30 }; } + getStepImagePath() { + const hasHelmet = this.equippedItems.has('safety_helmet_grind') || this.equippedItems.has('safety_helmet_chem'); + const hasGoggles = this.equippedItems.has('chem_splash_goggles') || this.equippedItems.has('safety_goggles_grind'); + const hasFaceShield = this.equippedItems.has('face_shield_grind') || this.equippedItems.has('face_shield_chem'); + const hasHearing = this.equippedItems.has('hearing_protection_grind') || this.equippedItems.has('hearing_protection_chem'); + const hasLeatherGloves = this.equippedItems.has('leather_grinding_gloves'); + const hasWorkGloves = this.equippedItems.has('work_gloves_grind') || this.equippedItems.has('standard_work_gloves'); + const hasChemGloves = this.equippedItems.has('chem_resistant_gloves'); + const hasVest = this.equippedItems.has('reflective_vest_grind'); + const hasApron = this.equippedItems.has('chem_resistant_apron'); + const hasCottonCoat = this.equippedItems.has('cotton_coat_chem'); + const hasBoots = this.equippedItems.has('steel_toe_boots_grind') || this.equippedItems.has('chem_safety_shoes'); + const hasMask = this.equippedItems.has('dust_mask_grind'); + + // 10. Full PPE with Mask + if (hasMask) { + return 'images/steps/10_full_ppe_with_mask.png'; + } + + // 9. Full Grinding Set with Boots + if (hasHelmet && (hasGoggles || hasFaceShield) && hasHearing && (hasLeatherGloves || hasChemGloves) && (hasVest || hasApron || hasBoots)) { + return 'images/steps/09_helmet_goggles_hearing_leather_gloves_vest_boots.png'; + } + + // 8. With Vest / Body Wear + if (hasHelmet && (hasGoggles || hasFaceShield) && hasHearing && (hasLeatherGloves || hasChemGloves) && (hasVest || hasApron || hasCottonCoat)) { + return 'images/steps/08_helmet_goggles_hearing_leather_gloves_vest.png'; + } + + // 7. With Leather / Chem Gloves + if (hasHelmet && (hasGoggles || hasFaceShield) && hasHearing && (hasLeatherGloves || hasChemGloves)) { + return 'images/steps/07_helmet_goggles_hearing_leather_gloves.png'; + } + + // 6. With Work Gloves + if (hasHelmet && (hasGoggles || hasFaceShield) && hasHearing && hasWorkGloves) { + return 'images/steps/06_helmet_goggles_hearing_work_gloves.png'; + } + + // 5. With Earmuffs / Hearing Protection + if (hasHelmet && (hasGoggles || hasFaceShield) && hasHearing) { + return 'images/steps/05_helmet_goggles_hearing.png'; + } + + // 4. With Face Shield + if (hasFaceShield || (hasHelmet && hasFaceShield)) { + return 'images/steps/04_helmet_goggles_face_shield.png'; + } + + // 3. With Goggles & Helmet + if (hasHelmet && hasGoggles) { + return 'images/steps/03_helmet_goggles.png'; + } + + // 2. Helmet Only + if (hasHelmet) { + return 'images/steps/02_helmet.png'; + } + + // Individual items + if (hasFaceShield) return 'images/steps/04_helmet_goggles_face_shield.png'; + if (hasGoggles) return 'images/steps/03_helmet_goggles.png'; + if (hasHearing) return 'images/steps/05_helmet_goggles_hearing.png'; + if (hasLeatherGloves) return 'images/steps/07_helmet_goggles_hearing_leather_gloves.png'; + if (hasWorkGloves) return 'images/steps/06_helmet_goggles_hearing_work_gloves.png'; + + // 1. Base Worker (No PPE) + return 'images/steps/01_no_ppe.png'; + } + render() { if (!this.container) return; this.container.innerHTML = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ +
+ + + Worker Character +
`; this.updateLayers(); @@ -300,37 +135,12 @@ class WorkerAvatar { 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'; + const imgEl = document.getElementById('avatar-step-img'); + if (imgEl) { + const targetSrc = this.getStepImagePath(); + if (imgEl.getAttribute('src') !== targetSrc) { + imgEl.src = targetSrc; } - }); - - // 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'; } } } diff --git a/js/game.js b/js/game.js index 04d5a84..8f6187c 100644 --- a/js/game.js +++ b/js/game.js @@ -15,7 +15,7 @@ class SafetyGame { this.avatar = null; this.particles = null; - // Dom Elements Cache + // DOM Elements Cache this.dom = { screenStart: document.getElementById('screen-start'), screenGame: document.getElementById('screen-game'), @@ -24,7 +24,7 @@ class SafetyGame { btnStartGame: document.getElementById('btn-start-game'), btnTestSafety: document.getElementById('btn-test-safety'), btnTestSafetyText: document.getElementById('btn-test-safety-text'), - btnClearPpe: document.getElementById('btn-clear-ppe'), + btnCheckPpe: document.getElementById('btn-check-ppe'), btnFixPpe: document.getElementById('btn-fix-ppe'), btnNextScenario: document.getElementById('btn-next-scenario'), btnNextScenarioText: document.getElementById('btn-next-scenario-text'), @@ -36,8 +36,11 @@ class SafetyGame { hudStars: document.getElementById('hud-stars'), stepPill1: document.getElementById('step-pill-1'), stepPill2: document.getElementById('step-pill-2'), + btnSoundToggle: document.getElementById('btn-sound-toggle'), + btnMusicToggle: document.getElementById('btn-music-toggle'), + btnAudioSettings: document.getElementById('btn-audio-settings'), - // Scene + // Scene Info & Hazards sceneBadge: document.getElementById('scene-badge'), sceneTitle: document.getElementById('scene-title'), sceneDesc: document.getElementById('scene-desc'), @@ -45,9 +48,6 @@ class SafetyGame { coverageGrid: document.getElementById('coverage-grid'), sceneStage: document.getElementById('scene-stage'), sceneBackdrop: document.getElementById('scene-backdrop'), - statusLight: document.getElementById('status-light'), - statusText: document.getElementById('status-text'), - audioCaption: document.getElementById('audio-caption'), // Locker lockerGrid: document.getElementById('ppe-locker-grid'), @@ -66,15 +66,13 @@ class SafetyGame { successDesc: document.getElementById('success-desc'), awardedPointsText: document.getElementById('awarded-points-text'), - // Summary + // Summary Screen summaryScore: document.getElementById('summary-score'), summaryPercentage: document.getElementById('summary-percentage'), summaryStars: document.getElementById('summary-stars'), summaryRank: document.getElementById('summary-rank'), // Audio Modal - btnAudioSettings: document.getElementById('btn-audio-settings'), - btnRestart: document.getElementById('btn-restart'), modalAudio: document.getElementById('modal-audio'), btnCloseAudioModal: document.getElementById('btn-close-audio-modal'), btnSaveAudio: document.getElementById('btn-save-audio'), @@ -103,121 +101,154 @@ class SafetyGame { } bindEvents() { - // Start Game - this.dom.btnStartGame.addEventListener('click', () => { - window.soundEngine.ensureContext(); - window.soundEngine.playStartIntroSting(); - this.switchScreen('game'); - - setTimeout(() => { - window.soundEngine.startBackgroundMusic(); - this.loadScenario(0); - }, 900); - }); + // Start Game from Intro + if (this.dom.btnStartGame) { + this.dom.btnStartGame.addEventListener('click', () => { + window.soundEngine.ensureContext(); + window.soundEngine.playStartIntroSting(); + this.switchScreen('game'); + + setTimeout(() => { + window.soundEngine.startBackgroundMusic(); + this.loadScenario(0); + }, 800); + }); + } - // Test Worker Safety - this.dom.btnTestSafety.addEventListener('click', () => { - this.executeSafetyTest(); - }); + // Check My PPE Button (Safe Preview & Validation Feedback) + if (this.dom.btnCheckPpe) { + this.dom.btnCheckPpe.addEventListener('click', () => { + this.checkPpeStatus(); + }); + } - // Clear PPE - this.dom.btnClearPpe.addEventListener('click', () => { - window.soundEngine.playDeselectSFX(); - this.equippedItems.clear(); - this.avatar.setEquipped([]); - this.renderLockerItems(); - this.updateCoverageIndicators(); - }); + // Start Task Safety Test Button + if (this.dom.btnTestSafety) { + this.dom.btnTestSafety.addEventListener('click', () => { + this.executeSafetyTest(); + }); + } - // Fix PPE (From Consequence Modal) - this.dom.btnFixPpe.addEventListener('click', () => { - window.soundEngine.playButtonClick(); - this.dismissConsequenceModal(); - }); + // Fix PPE (Dismiss Consequence Modal & Return to Edit) + if (this.dom.btnFixPpe) { + this.dom.btnFixPpe.addEventListener('click', () => { + window.soundEngine.playButtonClick(); + this.dismissConsequenceModal(); + }); + } - // Next Scenario - this.dom.btnNextScenario.addEventListener('click', () => { - window.soundEngine.playButtonClick(); - this.dom.successOverlay.classList.remove('active'); - this.particles.stopMode(); + // Next Scenario / View Summary Button + if (this.dom.btnNextScenario) { + this.dom.btnNextScenario.addEventListener('click', () => { + window.soundEngine.playButtonClick(); + this.dom.successOverlay.classList.remove('active'); + this.particles.stopMode(); - if (this.currentScenarioIndex < SCENARIO_DATA.length - 1) { - this.loadScenario(this.currentScenarioIndex + 1); - } else { - this.showFinalSummary(); - } - }); + if (this.currentScenarioIndex < SCENARIO_DATA.length - 1) { + this.loadScenario(this.currentScenarioIndex + 1); + } else { + this.showFinalSummary(); + } + }); + } // Replay Challenge - this.dom.btnReplay.addEventListener('click', () => { - window.soundEngine.playButtonClick(); - this.score = 0; - this.scenarioCompleted = [false, false]; - this.switchScreen('game'); - window.soundEngine.startBackgroundMusic(); - this.loadScenario(0); - }); - - // Print Certificate - this.dom.btnPrintCertificate.addEventListener('click', () => { - const dateEl = document.getElementById('cert-date-val'); - if (dateEl) { - dateEl.textContent = new Date().toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' }); - } - window.print(); - }); - - // Restart Button (Top Bar) - this.dom.btnRestart.addEventListener('click', () => { - if (confirm("Restart the PPE Safety Challenge from the beginning?")) { + if (this.dom.btnReplay) { + this.dom.btnReplay.addEventListener('click', () => { window.soundEngine.playButtonClick(); this.score = 0; this.scenarioCompleted = [false, false]; - this.switchScreen('start'); - window.soundEngine.stopBackgroundMusic(); - window.soundEngine.stopAmbient(); - window.soundEngine.stopGrinderSFX(); - window.soundEngine.stopLiquidTransferSFX(); - } - }); + this.switchScreen('game'); + window.soundEngine.startBackgroundMusic(); + this.loadScenario(0); + }); + } - // Audio Modal Controls - this.dom.btnAudioSettings.addEventListener('click', () => { - this.dom.modalAudio.classList.add('active'); - }); + // Print Certificate + if (this.dom.btnPrintCertificate) { + this.dom.btnPrintCertificate.addEventListener('click', () => { + const dateEl = document.getElementById('cert-date-val'); + if (dateEl) { + dateEl.textContent = new Date().toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' }); + } + window.print(); + }); + } - this.dom.btnCloseAudioModal.addEventListener('click', () => { - this.dom.modalAudio.classList.remove('active'); - }); + // Audio & Settings Header Controls + if (this.dom.btnSoundToggle) { + this.dom.btnSoundToggle.addEventListener('click', () => { + window.soundEngine.ensureContext(); + const isMuted = !window.soundEngine.isMasterMuted; + window.soundEngine.setMasterMute(isMuted); + this.dom.btnSoundToggle.querySelector('span').textContent = isMuted ? '🔇' : '🔊'; + if (this.dom.toggleMasterMute) this.dom.toggleMasterMute.checked = !isMuted; + }); + } - this.dom.btnSaveAudio.addEventListener('click', () => { - this.dom.modalAudio.classList.remove('active'); - }); + if (this.dom.btnMusicToggle) { + this.dom.btnMusicToggle.addEventListener('click', () => { + window.soundEngine.ensureContext(); + const currentVol = window.soundEngine.musicVolume; + const newVol = currentVol > 0.01 ? 0 : 0.25; + window.soundEngine.setMusicVolume(newVol); + this.dom.btnMusicToggle.querySelector('span').textContent = newVol > 0 ? '🎵' : '🔇'; + if (this.dom.sliderMusicVol) this.dom.sliderMusicVol.value = Math.round(newVol * 100); + if (this.dom.musicVolNum) this.dom.musicVolNum.textContent = `${Math.round(newVol * 100)}%`; + }); + } - this.dom.toggleMasterMute.addEventListener('change', (e) => { - window.soundEngine.setMasterMute(!e.target.checked); - document.getElementById('audio-icon').textContent = e.target.checked ? '🔊' : '🔇'; - }); + if (this.dom.btnAudioSettings) { + this.dom.btnAudioSettings.addEventListener('click', () => { + if (this.dom.modalAudio) this.dom.modalAudio.classList.add('active'); + }); + } - this.dom.sliderMusicVol.addEventListener('input', (e) => { - const val = parseInt(e.target.value) / 100; - this.dom.musicVolNum.textContent = `${e.target.value}%`; - window.soundEngine.setMusicVolume(val); - }); + if (this.dom.btnCloseAudioModal) { + this.dom.btnCloseAudioModal.addEventListener('click', () => { + if (this.dom.modalAudio) this.dom.modalAudio.classList.remove('active'); + }); + } - this.dom.sliderSfxVol.addEventListener('input', (e) => { - const val = parseInt(e.target.value) / 100; - this.dom.sfxVolNum.textContent = `${e.target.value}%`; - window.soundEngine.setSfxVolume(val); - }); + if (this.dom.btnSaveAudio) { + this.dom.btnSaveAudio.addEventListener('click', () => { + if (this.dom.modalAudio) this.dom.modalAudio.classList.remove('active'); + }); + } - this.dom.toggleAmbientHum.addEventListener('change', (e) => { - window.soundEngine.setAmbientEnabled(e.target.checked); - }); + if (this.dom.toggleMasterMute) { + this.dom.toggleMasterMute.addEventListener('change', (e) => { + window.soundEngine.setMasterMute(!e.target.checked); + if (this.dom.btnSoundToggle) { + this.dom.btnSoundToggle.querySelector('span').textContent = e.target.checked ? '🔊' : '🔇'; + } + }); + } - // Keyboard Shortcuts + if (this.dom.sliderMusicVol) { + this.dom.sliderMusicVol.addEventListener('input', (e) => { + const val = parseInt(e.target.value) / 100; + if (this.dom.musicVolNum) this.dom.musicVolNum.textContent = `${e.target.value}%`; + window.soundEngine.setMusicVolume(val); + }); + } + + if (this.dom.sliderSfxVol) { + this.dom.sliderSfxVol.addEventListener('input', (e) => { + const val = parseInt(e.target.value) / 100; + if (this.dom.sfxVolNum) this.dom.sfxVolNum.textContent = `${e.target.value}%`; + window.soundEngine.setSfxVolume(val); + }); + } + + if (this.dom.toggleAmbientHum) { + this.dom.toggleAmbientHum.addEventListener('change', (e) => { + window.soundEngine.setAmbientEnabled(e.target.checked); + }); + } + + // Keyboard Shortcuts (1-9 for Locker, Space for Start Task, Esc to close modals) window.addEventListener('keydown', (e) => { - // Numbers 1-9 to toggle PPE const num = parseInt(e.key); if (!isNaN(num) && num >= 1 && num <= 9) { const cards = document.querySelectorAll('.ppe-item-card'); @@ -226,7 +257,6 @@ class SafetyGame { } } - // Spacebar to Test Safety if (e.code === 'Space' && this.dom.screenGame.classList.contains('active')) { if (!this.dom.consequenceOverlay.classList.contains('active') && !this.dom.successOverlay.classList.contains('active')) { e.preventDefault(); @@ -234,19 +264,13 @@ class SafetyGame { } } - // Esc to close overlays/modals if (e.key === 'Escape') { - if (this.dom.modalAudio.classList.contains('active')) { + if (this.dom.modalAudio && this.dom.modalAudio.classList.contains('active')) { this.dom.modalAudio.classList.remove('active'); } else if (this.dom.consequenceOverlay.classList.contains('active')) { this.dismissConsequenceModal(); } } - - // 'M' to toggle Audio Settings - if (e.key === 'm' || e.key === 'M') { - this.dom.modalAudio.classList.toggle('active'); - } }); } @@ -270,36 +294,34 @@ class SafetyGame { this.particles.stopMode(); // Update Stepper HUD - this.dom.stepPill1.className = index === 0 ? 'step-pill active' : (this.scenarioCompleted[0] ? 'step-pill completed' : 'step-pill'); - this.dom.stepPill2.className = index === 1 ? 'step-pill active' : (this.scenarioCompleted[1] ? 'step-pill completed' : 'step-pill'); + if (this.dom.stepPill1 && this.dom.stepPill2) { + this.dom.stepPill1.className = index === 0 ? 'step-pill active' : (this.scenarioCompleted[0] ? 'step-pill completed' : 'step-pill'); + this.dom.stepPill2.className = index === 1 ? 'step-pill active' : (this.scenarioCompleted[1] ? 'step-pill completed' : 'step-pill'); + } - // Scene Styling - this.dom.sceneStage.className = `scene-environment-container ${this.currentScenario.sceneClass}`; - this.dom.sceneBadge.textContent = `SCENARIO ${index + 1} OF ${SCENARIO_DATA.length}`; - this.dom.sceneTitle.textContent = this.currentScenario.title; - this.dom.sceneDesc.textContent = this.currentScenario.environmentDesc; - this.dom.lockerGuideText.textContent = this.currentScenario.guideTip; - this.dom.audioCaption.textContent = `[Audio: ${this.currentScenario.audioCaption}]`; + // Scenario Details + if (this.dom.sceneBadge) this.dom.sceneBadge.textContent = `SCENARIO ${index + 1} OF ${SCENARIO_DATA.length}`; + if (this.dom.sceneTitle) this.dom.sceneTitle.textContent = this.currentScenario.title; + if (this.dom.sceneDesc) this.dom.sceneDesc.textContent = this.currentScenario.environmentDesc; + if (this.dom.lockerGuideText) this.dom.lockerGuideText.textContent = this.currentScenario.guideTip; - // Status Bar - this.dom.statusLight.className = 'status-indicator-light'; - this.dom.statusText.textContent = `TASK IDLE - EQUIP WORKER BEFORE STARTING ${this.currentScenario.taskName}`; - - // Render Scenario-Specific Realistic Workplace Environment + // Render Scenario Background this.renderScenarioEnvironment(this.currentScenario.id); - // Populate Hazard List - this.dom.hazardList.innerHTML = this.currentScenario.hazards.map(h => ` -
  • - ${h.label} -
  • - `).join(''); + // Render Hazards List + if (this.dom.hazardList) { + this.dom.hazardList.innerHTML = this.currentScenario.hazards.map(h => ` +
  • + ${h.label} +
  • + `).join(''); + } - // Populate Locker Grid + // Populate 3x3 Equipment Locker Grid this.renderLockerItems(); this.updateCoverageIndicators(); - // Start Ambient Sound for this scenario + // Start Ambient Sound window.soundEngine.setAmbientScenario(this.currentScenario.id); } @@ -307,407 +329,290 @@ class SafetyGame { if (!this.dom.sceneBackdrop) return; if (scenarioId === 'grinding') { - // SCENARIO 1: Vector Industrial Fabrication & Welding Workshop + // SCENARIO 1: Vector Industrial Fabrication & Grinding Workshop (Matching Reference Image) this.dom.sceneBackdrop.innerHTML = ` - - - - + + + - - + + - - - + + - - - - + + + + - + - - - - - - - - - - + + + + + + + - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + WEAR + PPE - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + - - - - - ⚡ CAUTION - GRINDING & WELDING - PPE MANDATORY + + + + + + + + - - - - - - + + + + + + + - - - - - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + `; - } else if (scenarioId === 'chemical') { - // SCENARIO 2: Vector Chemical Laboratory & Dispensing Station (Matching Reference Image) + } else { + // SCENARIO 2: Vector Industrial Chemical Transfer Station this.dom.sceneBackdrop.innerHTML = ` - - - - + + + - - - - - - - + + - + + + + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + EYE + WASH + + - - - - - - - - - + + - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 🧪 + + + + + + + + + 🧪 @@ -716,20 +621,145 @@ class SafetyGame { } } + // ========================================================================= + // 3x3 PPE SAFETY LOCKER RENDERING + // ========================================================================= + + getEquipmentIconSvg(iconType) { + switch (iconType) { + case 'helmet': + return ` + + + + + + `; + case 'face_shield': + return ` + + + + + + `; + case 'splash_goggles': + return ` + + + + + + + + `; + case 'sunglasses': + return ` + + + + + + `; + case 'earmuffs': + return ` + + + + + + `; + case 'leather_gloves': + return ` + + + + + `; + case 'chem_gloves': + return ` + + + + + `; + case 'work_gloves': + return ` + + + + + `; + case 'latex_gloves': + return ` + + + + `; + case 'leather_jacket': + return ` + + + + + + `; + case 'chem_apron': + return ` + + + + + `; + case 'cotton_coat': + return ` + + + + + `; + case 'safety_boots': + return ` + + + + + + `; + case 'sneakers': + return ` + + + + + + `; + default: + return `🦺`; + } + } + renderLockerItems() { + if (!this.dom.lockerGrid) return; + this.dom.lockerGrid.innerHTML = this.currentScenario.lockerItems.map((item, idx) => { const isEquipped = this.equippedItems.has(item.id); + const iconContent = item.iconImg + ? `${item.name}` + : this.getEquipmentIconSvg(item.iconType); + return `
    - ${idx + 1} -
    ${isEquipped ? '✓' : ''}
    -
    ${item.icon}
    +
    + ${isEquipped ? '✓' : ''} +
    +
    ${iconContent}
    ${item.name}
    `; }).join(''); - // Bind click events on cards + // Bind click handlers to cards this.dom.lockerGrid.querySelectorAll('.ppe-item-card').forEach(card => { card.addEventListener('click', () => { const itemId = card.dataset.itemId; @@ -765,27 +795,59 @@ class SafetyGame { } updateCoverageIndicators() { - const coverageItems = this.dom.coverageGrid.querySelectorAll('.coverage-item'); - coverageItems.forEach(itemEl => { - const slot = itemEl.dataset.slot; - const statusEl = itemEl.querySelector('.cov-status'); - - // Find if any item is equipped in this slot + if (!this.dom.coverageGrid) return; + + const rows = this.dom.coverageGrid.querySelectorAll('.status-row-item'); + rows.forEach(row => { + const slot = row.dataset.slot; + const badge = row.querySelector('.row-badge'); + if (!badge) return; + + // Check if this body slot has an equipped item const isSlotEquipped = Array.from(this.equippedItems).some(itemId => { const itemObj = this.currentScenario.lockerItems.find(it => it.id === itemId); return itemObj && itemObj.slot === slot; }); if (isSlotEquipped) { - statusEl.className = 'cov-status protected'; - statusEl.textContent = 'Equipped'; + badge.className = 'row-badge status-check'; + badge.textContent = '✓'; } else { - statusEl.className = 'cov-status missing'; - statusEl.textContent = 'Missing'; + badge.className = 'row-badge status-q'; + badge.textContent = '?'; } }); } + // ========================================================================= + // CHECK MY PPE (GUIDANCE & FEEDBACK) + // ========================================================================= + + checkPpeStatus() { + window.soundEngine.playButtonClick(); + + const equippedCount = this.equippedItems.size; + if (equippedCount === 0) { + alert("No PPE selected yet! Click equipment cards in the PPE Safety Locker to protect the worker."); + return; + } + + // Check how many of 6 slots are covered + const slots = ['head', 'eyes', 'ears', 'hands', 'body', 'feet']; + const coveredSlots = slots.filter(slot => { + return Array.from(this.equippedItems).some(itemId => { + const it = this.currentScenario.lockerItems.find(x => x.id === itemId); + return it && it.slot === slot; + }); + }); + + if (coveredSlots.length < 6) { + alert(`Worker Status: ${coveredSlots.length}/6 protection zones covered. Please review the missing body areas in the Worker Protection Status checklist before starting the task.`); + } else { + alert(`All 6 protection zones covered! Click START TASK to begin the operation.`); + } + } + // ========================================================================= // SAFETY CHECK EXECUTION & CONSEQUENCE DETECTION // ========================================================================= @@ -815,11 +877,7 @@ class SafetyGame { } handleSafeSuccess() { - // 1. Update visual state - this.dom.statusLight.className = 'status-indicator-light running'; - this.dom.statusText.textContent = `OPERATION RUNNING SAFELY - ALL HAZARDS MITIGATED`; - - // 2. Start Task Simulation SFX & Particles + // Start Task Simulation SFX & Particles if (this.currentScenario.id === 'grinding') { window.soundEngine.startGrinderSFX(); this.particles.startMode('sparks'); @@ -828,7 +886,7 @@ class SafetyGame { this.particles.startMode('chemical'); } - // 3. Play Success Chime & Star sound after short operational run + // Play Success Chime & Star sound after short operational run setTimeout(() => { window.soundEngine.playSuccessChime(); window.soundEngine.playSafetyStarSound(); @@ -840,29 +898,26 @@ class SafetyGame { this.updateHUD(); // Show Success Modal Overlay - this.dom.successTitle.textContent = this.currentScenario.successMessage.title; - this.dom.successDesc.textContent = this.currentScenario.successMessage.body; - this.dom.awardedPointsText.textContent = `+${points} Safety Points ⭐`; + if (this.dom.successTitle) this.dom.successTitle.textContent = this.currentScenario.successMessage.title; + if (this.dom.successDesc) this.dom.successDesc.textContent = this.currentScenario.successMessage.body; + if (this.dom.awardedPointsText) this.dom.awardedPointsText.textContent = `+${points} Safety Points ⭐`; - if (this.currentScenarioIndex === SCENARIO_DATA.length - 1) { - this.dom.btnNextScenarioText.textContent = 'VIEW FINAL RESULTS ➔'; - } else { - this.dom.btnNextScenarioText.textContent = 'NEXT SCENARIO ➔'; + if (this.dom.btnNextScenarioText) { + if (this.currentScenarioIndex === SCENARIO_DATA.length - 1) { + this.dom.btnNextScenarioText.textContent = 'VIEW FINAL RESULTS ➔'; + } else { + this.dom.btnNextScenarioText.textContent = 'NEXT SCENARIO ➔'; + } } - this.dom.successOverlay.classList.add('active'); + if (this.dom.successOverlay) this.dom.successOverlay.classList.add('active'); }, 1200); } handleUnsafeConsequence(allMissingChecks, allEquippedDistractors) { this.scenarioFirstAttempt = false; - const totalIssues = allMissingChecks.length + allEquippedDistractors.length; - - // 1. Start task machine momentarily - this.dom.statusLight.className = 'status-indicator-light danger'; - this.dom.statusText.textContent = `CRITICAL HAZARD DETECTED (${totalIssues} ISSUES) - FREEZING OPERATION!`; - + // Start task machine momentarily if (this.currentScenario.id === 'grinding') { window.soundEngine.startGrinderSFX(); this.particles.startMode('sparks'); @@ -871,10 +926,10 @@ class SafetyGame { this.particles.startMode('chemical'); } - // 2. Determine target position for incoming projectile (prioritize eye/face if missing, otherwise hand/body) + // Determine target position for incoming projectile const avatarCoords = this.avatar.getAvatarCenterCoordinates(); - const stageWidth = this.dom.sceneStage.clientWidth; - const stageHeight = this.dom.sceneStage.clientHeight; + const stageWidth = this.dom.sceneStage ? this.dom.sceneStage.clientWidth : 600; + const stageHeight = this.dom.sceneStage ? this.dom.sceneStage.clientHeight : 450; const originX = stageWidth * 0.72; const originY = stageHeight * 0.65; @@ -910,7 +965,6 @@ class SafetyGame { const { allMissingChecks, allEquippedDistractors } = this.activeConsequenceData; const totalIssues = allMissingChecks.length + allEquippedDistractors.length; - // Slot icons mapping const slotIcons = { head: '🧢', eyes: '👓', @@ -921,28 +975,29 @@ class SafetyGame { }; // 4. Update Header & Badges - this.dom.hazardFlasherBadge.textContent = `⚠️ STOP! ${totalIssues} SAFETY ${totalIssues === 1 ? 'HAZARD' : 'HAZARDS'} DETECTED`; - this.dom.missingCountNum.textContent = totalIssues; + if (this.dom.hazardFlasherBadge) { + this.dom.hazardFlasherBadge.textContent = `⚠️ STOP! ${totalIssues} SAFETY ${totalIssues === 1 ? 'HAZARD' : 'HAZARDS'} DETECTED`; + } + if (this.dom.missingCountNum) this.dom.missingCountNum.textContent = totalIssues; if (totalIssues === 1) { if (allEquippedDistractors.length > 0) { const d = allEquippedDistractors[0]; - this.dom.consequenceTitle.textContent = "Incorrect / Ineffective PPE Detected"; - this.dom.consequenceDesc.textContent = d.rejectReason; + if (this.dom.consequenceTitle) this.dom.consequenceTitle.textContent = "Incorrect / Ineffective PPE Detected"; + if (this.dom.consequenceDesc) this.dom.consequenceDesc.textContent = d.rejectReason; } else { const m = allMissingChecks[0]; - this.dom.consequenceTitle.textContent = m.consequenceTitle; - this.dom.consequenceDesc.textContent = m.consequenceDesc; + if (this.dom.consequenceTitle) this.dom.consequenceTitle.textContent = m.consequenceTitle; + if (this.dom.consequenceDesc) this.dom.consequenceDesc.textContent = m.consequenceDesc; } } else { - this.dom.consequenceTitle.textContent = "Multiple Critical Protection Gaps Detected"; - this.dom.consequenceDesc.textContent = "Operating without complete PPE exposes multiple body zones to direct, severe workplace injuries. All required safety equipment must be equipped before starting."; + if (this.dom.consequenceTitle) this.dom.consequenceTitle.textContent = "Critical Protection Gaps Detected"; + if (this.dom.consequenceDesc) this.dom.consequenceDesc.textContent = "Operating without complete PPE exposes multiple body zones to direct, severe workplace injuries. All required safety equipment must be equipped before starting."; } // 5. Build HTML for ALL Missing & Incorrect Items Simultaneously let cardsHtml = ''; - // Render all missing critical checks allMissingChecks.forEach(chk => { const icon = slotIcons[chk.slot] || '⚠️'; cardsHtml += ` @@ -959,7 +1014,6 @@ class SafetyGame { `; }); - // Render all equipped distractors that need to be removed allEquippedDistractors.forEach(dist => { cardsHtml += `
    @@ -975,42 +1029,45 @@ class SafetyGame { `; }); - this.dom.consequenceHazardList.innerHTML = cardsHtml; + if (this.dom.consequenceHazardList) { + this.dom.consequenceHazardList.innerHTML = cardsHtml; + } - // 6. Highlight missing slots in Coverage Grid with visual alert animation - const coverageItems = this.dom.coverageGrid.querySelectorAll('.coverage-item'); - coverageItems.forEach(itemEl => { - const slot = itemEl.dataset.slot; - const isSlotMissing = allMissingChecks.some(chk => chk.slot === slot); - if (isSlotMissing) { - itemEl.classList.add('alert-missing'); - } else { - itemEl.classList.remove('alert-missing'); - } - }); + // 6. Highlight missing slots in Coverage Grid + if (this.dom.coverageGrid) { + const rows = this.dom.coverageGrid.querySelectorAll('.status-row-item'); + rows.forEach(row => { + const slot = row.dataset.slot; + const isSlotMissing = allMissingChecks.some(chk => chk.slot === slot); + if (isSlotMissing) { + row.classList.add('alert-missing'); + } else { + row.classList.remove('alert-missing'); + } + }); + } // 7. Display freeze overlay - this.dom.consequenceOverlay.classList.add('active'); + if (this.dom.consequenceOverlay) { + this.dom.consequenceOverlay.classList.add('active'); + } } dismissConsequenceModal() { - // Unfreeze and restore normal background music - this.dom.consequenceOverlay.classList.remove('active'); + if (this.dom.consequenceOverlay) this.dom.consequenceOverlay.classList.remove('active'); this.particles.clear(); this.particles.setFrozen(false); this.particles.stopMode(); window.soundEngine.duckMusic(false); - // Clear alert classes on coverage items - const coverageItems = this.dom.coverageGrid.querySelectorAll('.coverage-item'); - coverageItems.forEach(itemEl => itemEl.classList.remove('alert-missing')); - - this.dom.statusLight.className = 'status-indicator-light'; - this.dom.statusText.textContent = `TASK IDLE - ADJUST PPE SELECTION`; + if (this.dom.coverageGrid) { + const rows = this.dom.coverageGrid.querySelectorAll('.status-row-item'); + rows.forEach(row => row.classList.remove('alert-missing')); + } } // ========================================================================= - // SUMMARY & COMPLETION SCREEN + // SUMMARY SCREEN // ========================================================================= showFinalSummary() { @@ -1018,29 +1075,30 @@ class SafetyGame { window.soundEngine.playVictorySting(); this.particles.spawnConfetti(); - // Update metrics const totalMax = SCENARIO_DATA.length * 100; const pct = Math.round((this.score / totalMax) * 100); - this.dom.summaryScore.textContent = `${this.score} / ${totalMax}`; - this.dom.summaryPercentage.textContent = `${pct}% Safety Compliance Rating`; + if (this.dom.summaryScore) this.dom.summaryScore.textContent = `${this.score} / ${totalMax}`; + if (this.dom.summaryPercentage) this.dom.summaryPercentage.textContent = `${pct}% Safety Compliance Rating`; if (this.score >= 180) { - this.dom.summaryStars.textContent = "⭐⭐"; - this.dom.summaryRank.textContent = "Master Safety Practitioner (Zero Accidents)"; + if (this.dom.summaryStars) this.dom.summaryStars.textContent = "⭐⭐"; + if (this.dom.summaryRank) this.dom.summaryRank.textContent = "Master Safety Practitioner (Zero Accidents)"; } else { - this.dom.summaryStars.textContent = "⭐"; - this.dom.summaryRank.textContent = "Safety Certified (Refresher Recommended)"; + if (this.dom.summaryStars) this.dom.summaryStars.textContent = "⭐"; + if (this.dom.summaryRank) this.dom.summaryRank.textContent = "Safety Certified (Refresher Recommended)"; } } updateHUD() { - this.dom.hudScore.textContent = this.score; + if (this.dom.hudScore) this.dom.hudScore.textContent = this.score; const starCount = Math.floor(this.score / 100); - this.dom.hudStars.textContent = "⭐".repeat(starCount) + "☆".repeat(Math.max(0, 2 - starCount)); + if (this.dom.hudStars) { + this.dom.hudStars.textContent = "★".repeat(starCount) + "☆".repeat(Math.max(0, 2 - starCount)); + } } } -// Instantiate game on load +// Instantiate on load window.addEventListener('DOMContentLoaded', () => { window.safetyGame = new SafetyGame(); }); diff --git a/js/scenarios.js b/js/scenarios.js index 4f9f734..cfd7493 100644 --- a/js/scenarios.js +++ b/js/scenarios.js @@ -1,13 +1,16 @@ /** - * PPE SAFETY CHALLENGE - SCENARIO DEFINITIONS & SAFETY DATABASE - * Implements: SEE THE SITUATION -> IDENTIFY HAZARDS -> SELECT PPE -> CONSEQUENCES -> CORRECT + * PPE SAFETY CHALLENGE - SCENARIO DEFINITIONS & 3x3 SAFETY LOCKER DATABASE + * "Choose Right. Work Safe." + * + * Scenario 1: Angle Grinding Operation (PNG Icons from grinding_ppe_icons_png) + * Scenario 2: Chemical Handling & Dispensing (PNG Icons from ppe_icons_png) */ const SCENARIO_DATA = [ { id: "grinding", index: 1, - title: "Metal Grinding Operation", + title: "Angle Grinding Operation", taskName: "ANGLE GRINDER WELD SMOOTHING", sceneClass: "stage-grinding", environmentDesc: "An industrial worker is preparing to grind rough weld seams on heavy structural steel using a high-speed angle grinder (11,000 RPM).", @@ -15,176 +18,150 @@ const SCENARIO_DATA = [ guideTip: "High-speed spinning discs eject fiery sparks and shrapnel at over 100 m/s with extreme noise (95+ dB).", hazards: [ - { - id: "flying_sparks", - label: "Flying hot metal sparks & abrasive wheel particles (11,000 RPM)", - criticalSlot: "eyes" - }, - { - id: "high_noise", - label: "Continuous high decibel motor and grinding noise (95-105 dB)", - criticalSlot: "ears" - }, - { - id: "sharp_burrs", - label: "Sharp metal burrs, intense friction heat & wheel kickback hazard", - criticalSlot: "hands" - }, - { - id: "falling_debris", - label: "Heavy steel drop hazard & structural workshop overhead risks", - criticalSlot: "feet" - } + { id: "flying_sparks", label: "Flying Hot Sparks (1000°C+)" }, + { id: "high_noise", label: "High Decibel Noise (95+ dB)" }, + { id: "sharp_burrs", label: "Sharp Metal Burrs & Jagged Edges" }, + { id: "falling_debris", label: "Heavy Structural Steel Drop Hazard" } ], - // Required protective gear items requiredPpe: [ - "faceshield_glasses", + "safety_helmet_grind", + "face_shield_grind", + "hearing_protection_grind", "leather_grinding_gloves", - "earmuffs_heavy", - "hard_hat_cap", - "heavy_leather_jacket", - "steel_toe_boots" + "steel_toe_boots_grind" ], - // Critical slots for safety check evaluation criticalChecks: [ { slot: "eyes", - requiredItem: "faceshield_glasses", - consequenceTitle: "Flying Particle Hazard", - consequenceDesc: "Without full face & eye impact protection, flying abrasive particles at 11,000 RPM can cause catastrophic corneal penetration or severe facial lacerations.", - missingLabel: "Full Face Shield + Safety Glasses (ANSI Z87+)" + requiredItem: "face_shield_grind", + consequenceTitle: "High-Speed Flying Particle Hazard", + consequenceDesc: "Without full face & eye impact protection, flying abrasive disc particles and 1000°C sparks cause instant severe corneal burns and permanent vision loss.", + missingLabel: "Face Shield (ANSI Z87+ Full Impact)" }, { slot: "ears", - requiredItem: "earmuffs_heavy", - consequenceTitle: "Noise-Induced Hearing Damage", - consequenceDesc: "Angle grinding routinely generates 100+ dB. Operating without rated hearing protection causes irreversible inner-ear acoustic trauma.", - missingLabel: "Heavy Duty Hearing Protection (Earmuffs / Plugs)" + requiredItem: "hearing_protection_grind", + consequenceTitle: "Noise-Induced Acoustic Trauma", + consequenceDesc: "Angle grinding routinely exceeds 100 dB. Operating without acoustic earmuffs causes irreversible inner-ear cochlear damage and permanent tinnitus.", + missingLabel: "Hearing Protection (NRR 30 Earmuffs)" }, { slot: "hands", requiredItem: "leather_grinding_gloves", - consequenceTitle: "Thermal Burn & Abrasion Danger", - consequenceDesc: "Thin latex or bare hands offer zero protection against glowing 800°C sparks and jagged steel edges. Heavy-duty leather gloves are required.", - missingLabel: "Heavy-Duty Split Cowhide Leather Grinding Gloves" + consequenceTitle: "Severe Thermal Burn & Laceration Danger", + consequenceDesc: "Standard cloth work gloves melt instantly from 800°C sparks and provide zero protection against razor-sharp steel burrs. Heavy split cowhide leather gloves are mandatory.", + missingLabel: "Leather Gloves (Split Cowhide)" }, { slot: "feet", - requiredItem: "steel_toe_boots", - consequenceTitle: "Crush & Penetration Hazard", - consequenceDesc: "Standard footwear cannot protect against dropped structural steel sections or sharp discarded metal shards on workshop floors.", - missingLabel: "Steel-Toe Safety Boots with Puncture Resistant Sole" + requiredItem: "steel_toe_boots_grind", + consequenceTitle: "Crush & Puncture Hazard", + consequenceDesc: "Standard footwear offers zero impact protection against dropped structural steel sections or sharp discarded metal shards on workshop floors.", + missingLabel: "Safety Shoes (Steel-Toe ASTM F2413)" }, { slot: "head", - requiredItem: "hard_hat_cap", + requiredItem: "safety_helmet_grind", consequenceTitle: "Overhead Impact & Debris Risk", - consequenceDesc: "In fabrication bays, overhead cranes and moving metal fixtures require impact-certified head protection.", - missingLabel: "Industrial Hard Hat (Type 1, Class E)" - }, - { - slot: "body", - requiredItem: "heavy_leather_jacket", - consequenceTitle: "Clothing Ignition / Hot Spark Burn", - consequenceDesc: "Synthetic regular clothing easily melts or ignites from dense grinder spark streams. Heavy flame-resistant leather/cotton protective wear is essential.", - missingLabel: "Flame-Resistant Heavy Leather Welder/Grinder Jacket" + consequenceDesc: "In fabrication bays, overhead cranes, suspended steel beams, and falling debris require impact-certified head protection.", + missingLabel: "Safety Helmet (Industrial Hard Hat)" } ], - // Locker items available for selection (including distractors) lockerItems: [ { - id: "faceshield_glasses", - name: "Face Shield + Safety Glasses", - slot: "eyes", - icon: "🛡️", - spec: "ANSI Z87+ Impact Dual Protection", - isCorrect: true + id: "safety_helmet_grind", + name: "Safety Helmet", + slot: "head", + iconType: "helmet", + iconImg: "grinding_ppe_icons_png/01_safety_helmet.png", + isCorrect: true, + spec: "ANSI Z89.1 Type 1 Class E Hard Hat" }, { - id: "regular_sunglasses", - name: "Standard Tinted Sunglasses", + id: "safety_goggles_grind", + name: "Safety Goggles", slot: "eyes", - icon: "🕶️", - spec: "Non-impact rated tinted plastic", + iconType: "sunglasses", + iconImg: "grinding_ppe_icons_png/02_safety_goggles.png", isCorrect: false, - rejectReason: "Standard sunglasses shatter upon impact and leave the face exposed to flying debris." + rejectReason: "Standard safety glasses/goggles leave face and neck vulnerable to hot flying sparks and shattering grinding fragments. A full face shield is required.", + spec: "Non-impact safety goggles" + }, + { + id: "face_shield_grind", + name: "Face Shield", + slot: "eyes", + iconType: "face_shield", + iconImg: "grinding_ppe_icons_png/03_face_shield.png", + isCorrect: true, + spec: "ANSI Z87+ Full Impact Face Visor" + }, + { + id: "hearing_protection_grind", + name: "Hearing Protection", + slot: "ears", + iconType: "earmuffs", + iconImg: "grinding_ppe_icons_png/04_hearing_protection.png", + isCorrect: true, + spec: "High noise reduction acoustic seal (NRR 30)" + }, + { + id: "work_gloves_grind", + name: "Work Gloves", + slot: "hands", + iconType: "work_gloves", + iconImg: "grinding_ppe_icons_png/05_work_gloves.png", + isCorrect: false, + rejectReason: "Thin work gloves offer zero thermal or cut protection against glowing 1000°C sparks and jagged burrs. Heavy split cowhide leather gloves are required.", + spec: "Porous cloth work gloves" }, { id: "leather_grinding_gloves", - name: "Leather Grinding Gloves", + name: "Leather Gloves", slot: "hands", - icon: "🧤", - spec: "Heavy split cowhide heat/spark resistant", - isCorrect: true + iconType: "leather_gloves", + iconImg: "grinding_ppe_icons_png/06_leather_gloves.png", + isCorrect: true, + spec: "Heavy split cowhide heat/spark resistant" }, { - id: "thin_latex_gloves", - name: "Thin Disposable Latex Gloves", - slot: "hands", - icon: "🧤", - spec: "Medical grade 4mil rubber (Flaggable)", - isCorrect: false, - rejectReason: "Thin latex immediately melts onto skin when hit by 800°C grinder sparks." - }, - { - id: "earmuffs_heavy", - name: "Industrial Ear Muffs (NRR 30)", - slot: "ears", - icon: "🎧", - spec: "High noise reduction acoustic seal", - isCorrect: true - }, - { - id: "wireless_earbuds", - name: "Consumer Music Earbuds", - slot: "ears", - icon: "🎵", - spec: "Audio playback (Non-certified)", - isCorrect: false, - rejectReason: "Music earphones are not certified PPE and distract the operator from machine warnings." - }, - { - id: "hard_hat_cap", - name: "Industrial Hard Hat", - slot: "head", - icon: "⛑️", - spec: "ANSI Z89.1 Type 1 Class E", - isCorrect: true - }, - { - id: "heavy_leather_jacket", - name: "Flame-Resistant Work Jacket", + id: "reflective_vest_grind", + name: "Reflective Vest", slot: "body", - icon: "🦺", - spec: "Heavy-duty spark-resistant leather/cotton", - isCorrect: true - }, - { - id: "steel_toe_boots", - name: "Steel-Toe Safety Boots", - slot: "feet", - icon: "🥾", - spec: "ASTM F2413 Impact & Puncture Rated", - isCorrect: true - }, - { - id: "regular_sneakers", - name: "Canvas Running Sneakers", - slot: "feet", - icon: "👟", - spec: "Fabric sports shoe (Zero drop rating)", + iconType: "leather_jacket", + iconImg: "grinding_ppe_icons_png/07_reflective_vest.png", isCorrect: false, - rejectReason: "Fabric sneakers ignite from sparks and offer zero crush protection from dropped metal." + rejectReason: "Synthetic high-visibility vests melt or ignite from dense grinder spark streams.", + spec: "Synthetic mesh hi-vis vest" + }, + { + id: "steel_toe_boots_grind", + name: "Safety Shoes", + slot: "feet", + iconType: "safety_boots", + iconImg: "grinding_ppe_icons_png/08_safety_shoes.png", + isCorrect: true, + spec: "ASTM F2413 Impact & Puncture Rated" + }, + { + id: "dust_mask_grind", + name: "Dust Mask", + slot: "body", + iconType: "cotton_coat", + iconImg: "grinding_ppe_icons_png/09_dust_mask.png", + isCorrect: false, + rejectReason: "A simple dust mask does not protect the face or body from high-speed thermal sparks.", + spec: "N95 particulate filter mask" } ], successMessage: { title: "SAFE TO START", - headline: "Correct PPE selected.", - body: "Your eyes, face, hands, hearing and feet are protected from the main hazards of this task." + headline: "Correct Grinding Protection Selected.", + body: "Your eyes, face, hands, hearing and feet are fully protected from all hazards of this grinding task." } }, @@ -194,180 +171,162 @@ const SCENARIO_DATA = [ title: "Chemical Handling & Dispensing", taskName: "CORROSIVE SOLVENT TRANSFER", sceneClass: "stage-chemical", - environmentDesc: "An employee is preparing to transfer an industrial corrosive solvent between approved containment drums inside a ventilated chemical dispensing station.", + environmentDesc: "An employee is transferring an industrial corrosive solvent between approved containers at a ventilated dispensing station.", audioCaption: "Ventilation Ambience: Fume extraction hood airflow & quiet laboratory background", - guideTip: "Corrosive chemicals cause irreversible chemical burns upon skin contact and severe vapor irritation.", + guideTip: "Observe the scene, identify the hazards and select the correct PPE before starting the task.", hazards: [ - { - id: "chemical_splash", - label: "Corrosive liquid splash risk during pouring & transfer", - criticalSlot: "eyes" - }, - { - id: "skin_absorption", - label: "Acute dermal chemical burn and toxic absorption", - criticalSlot: "hands" - }, - { - id: "body_soak", - label: "Direct container overflow or accidental splash onto torso & legs", - criticalSlot: "body" - }, - { - id: "foot_contact", - label: "Chemical pooling on floor and footwear degradation", - criticalSlot: "feet" - } + { id: "chemical_splash", label: "Chemical Splash" }, + { id: "skin_exposure", label: "Skin Exposure" }, + { id: "vapour_exposure", label: "Vapour Exposure" }, + { id: "floor_contamination", label: "Floor Contamination" } ], - // Required protective gear items requiredPpe: [ - "chemical_splash_goggles", - "nitrile_gauntlet_gloves", - "chemical_resistant_apron", - "chemical_rubber_boots", - "hard_hat_chemical", - "organic_vapor_respirator" + "safety_helmet_chem", + "chem_splash_goggles", + "hearing_protection_chem", + "chem_resistant_gloves", + "chem_resistant_apron", + "chem_safety_shoes" ], - // Critical slots evaluation criticalChecks: [ { slot: "eyes", - requiredItem: "chemical_splash_goggles", + requiredItem: "chem_splash_goggles", consequenceTitle: "Chemical Splash to Eyes", - consequenceDesc: "Standard safety glasses with open side vents allow hazardous liquid droplets to roll directly into the eyes, causing permanent corneal scarring.", - missingLabel: "Indirectly Vented Chemical Splash Goggles (ANSI Z87.1 D3)" + consequenceDesc: "Without sealed indirectly-vented chemical splash goggles, corrosive solvent droplets roll directly into the eyes, causing permanent corneal scarring.", + missingLabel: "Chemical Splash Goggles (ANSI Z87.1 D3)" }, { slot: "hands", - requiredItem: "nitrile_gauntlet_gloves", + requiredItem: "chem_resistant_gloves", consequenceTitle: "Dermal Chemical Permeation", - consequenceDesc: "Porous leather or basic household gloves rapidly absorb harsh solvents, causing chemical burns and systemic toxicity through the skin.", - missingLabel: "Heavy-Duty Chemical-Resistant Nitrile/Neoprene Gauntlets" + consequenceDesc: "Standard cloth/leather or bare hands rapidly absorb harsh corrosive solvents, causing severe chemical burns and toxic absorption.", + missingLabel: "Chemical Resistant Gloves (Nitrile / Neoprene)" }, { slot: "body", - requiredItem: "chemical_resistant_apron", + requiredItem: "chem_resistant_apron", consequenceTitle: "Body Splash & Acid Soak Hazard", - consequenceDesc: "Standard porous fabric clothing absorbs chemical splashes immediately, holding corrosive liquids against the skin.", - missingLabel: "Chemical-Resistant Impervious PVC/Neoprene Apron" + consequenceDesc: "Standard cotton coats absorb corrosive chemicals immediately, holding toxic liquids against the skin. Impervious chemical apron is required.", + missingLabel: "Chemical Resistant Apron (PVC / Neoprene)" }, { slot: "feet", - requiredItem: "chemical_rubber_boots", + requiredItem: "chem_safety_shoes", consequenceTitle: "Corrosive Liquid Footwear Infiltration", - consequenceDesc: "Standard leather safety boots dissolve and degrade when exposed to pooled industrial acids and solvents.", - missingLabel: "Chemical-Resistant Neoprene/Rubber Boots" + consequenceDesc: "Standard footwear degrades rapidly when exposed to pooled industrial acids. Chemical-resistant safety boots are required.", + missingLabel: "Chemical Safety Shoes / Boots" }, { slot: "ears", - requiredItem: "organic_vapor_respirator", // Uses respiratory/head slot - consequenceTitle: "Hazardous Vapor Inhalation", - consequenceDesc: "Concentrated chemical vapors overcome unprotected workers quickly. Half-mask respirator with chemical cartridges is required.", - missingLabel: "Half-Mask Respirator with Organic Vapor/Acid Gas Cartridges" + requiredItem: "hearing_protection_chem", + consequenceTitle: "High Ventilation & Pump Noise Hazard", + consequenceDesc: "Continuous industrial ventilation extractors and dispensing pumps exceed safe acoustic thresholds.", + missingLabel: "Hearing Protection (Earmuffs)" }, { slot: "head", - requiredItem: "hard_hat_chemical", + requiredItem: "safety_helmet_chem", consequenceTitle: "Overhead Chemical Piping Hazard", - consequenceDesc: "Chemical distribution zones require head protection against overhead pipe drips and valve maintenance risks.", - missingLabel: "Chemical-Resistant Industrial Hard Hat" + consequenceDesc: "Chemical distribution bays require certified head protection against overhead pipe drips and valve fixtures.", + missingLabel: "Safety Helmet (Type 1 Class E)" } ], lockerItems: [ { - id: "chemical_splash_goggles", + id: "safety_helmet_chem", + name: "Safety Helmet", + slot: "head", + iconType: "helmet", + iconImg: "ppe_icons_png/01_safety_helmet.png", + isCorrect: true, + spec: "ANSI Z89.1 Certified Industrial Hard Hat" + }, + { + id: "chem_splash_goggles", name: "Chemical Splash Goggles", slot: "eyes", - icon: "🥽", - spec: "ANSI Z87.1 D3 Sealed Splash Protection", - isCorrect: true + iconType: "splash_goggles", + iconImg: "ppe_icons_png/02_chemical_splash_goggles.png", + isCorrect: true, + spec: "ANSI Z87.1 D3 Sealed Chemical Splash Goggles" }, { - id: "standard_safety_glasses", - name: "Open Safety Glasses", + id: "face_shield_chem", + name: "Face Shield", slot: "eyes", - icon: "👓", - spec: "Impact rated with open side vents (Non-liquid)", + iconType: "face_shield", + iconImg: "ppe_icons_png/03_face_shield.png", isCorrect: false, - rejectReason: "Open side vents allow liquid splashes and corrosive droplets to enter the eye." + rejectReason: "A standalone face shield without sealed splash goggles underneath allows liquid splashes to roll up into eyes.", + spec: "Full Face Visor (Secondary Protection)" }, { - id: "nitrile_gauntlet_gloves", - name: "Nitrile Gauntlet Gloves", - slot: "hands", - icon: "🧤", - spec: "15-mil Heavy Nitrile Chemical Gauntlets", - isCorrect: true - }, - { - id: "leather_work_gloves", - name: "Standard Leather Work Gloves", - slot: "hands", - icon: "🧤", - spec: "Porous split cowhide (Absorbent)", - isCorrect: false, - rejectReason: "Leather absorbs corrosive chemicals like a sponge, causing deep chemical burns." - }, - { - id: "chemical_resistant_apron", - name: "Chemical-Resistant Apron", - slot: "body", - icon: "🦺", - spec: "Impermeable Heavy PVC / Neoprene Apron", - isCorrect: true - }, - { - id: "cotton_lab_coat", - name: "Standard Cotton Coat", - slot: "body", - icon: "🥼", - spec: "Porous woven cotton (Non-impervious)", - isCorrect: false, - rejectReason: "Cotton absorbs acids and solvents directly into underclothes." - }, - { - id: "chemical_rubber_boots", - name: "Chemical Rubber Boots", - slot: "feet", - icon: "🥾", - spec: "Seamless Neoprene Acid-Resistant Boots", - isCorrect: true - }, - { - id: "organic_vapor_respirator", - name: "Organic Vapor Respirator", - slot: "ears", // Grouped in face/respiratory slot - icon: "😷", - spec: "Half-Face Mask with Dual Acid/Vapor Cartridges", - isCorrect: true - }, - { - id: "hard_hat_chemical", - name: "Safety Hard Hat", - slot: "head", - icon: "⛑️", - spec: "Chemical-Resistant High Density Polyethylene", - isCorrect: true - }, - { - id: "cloth_dust_mask", - name: "Simple Cloth Dust Mask", + id: "hearing_protection_chem", + name: "Hearing Protection", slot: "ears", - icon: "😷", - spec: "Particulate only (No gas/vapor barrier)", + iconType: "earmuffs", + iconImg: "ppe_icons_png/04_hearing_protection.png", + isCorrect: true, + spec: "NRR 30 Acoustic Earmuffs" + }, + { + id: "chem_resistant_gloves", + name: "Chemical Resistant Gloves", + slot: "hands", + iconType: "chem_gloves", + iconImg: "ppe_icons_png/05_chemical_resistant_gloves.png", + isCorrect: true, + spec: "15-mil Heavy Nitrile Chemical Gauntlets" + }, + { + id: "standard_work_gloves", + name: "Standard Work Gloves", + slot: "hands", + iconType: "work_gloves", + iconImg: "ppe_icons_png/06_standard_work_gloves.png", isCorrect: false, - rejectReason: "Cloth dust masks offer ZERO filtration against toxic chemical fumes and organic solvent vapors." + rejectReason: "Standard porous work gloves absorb corrosive liquids like a sponge, causing deep chemical burns.", + spec: "Cloth/Leather Work Gloves (Porous)" + }, + { + id: "chem_resistant_apron", + name: "Chemical Resistant Apron", + slot: "body", + iconType: "chem_apron", + iconImg: "ppe_icons_png/07_chemical_resistant_apron.png", + isCorrect: true, + spec: "Impermeable Heavy PVC / Neoprene Apron" + }, + { + id: "chem_safety_shoes", + name: "Safety Shoes", + slot: "feet", + iconType: "safety_boots", + iconImg: "ppe_icons_png/08_safety_shoes.png", + isCorrect: true, + spec: "Chemical & Acid Resistant Safety Boots" + }, + { + id: "cotton_coat_chem", + name: "Cotton Coat", + slot: "body", + iconType: "cotton_coat", + iconImg: "ppe_icons_png/09_cotton_coat.png", + isCorrect: false, + rejectReason: "Cotton laboratory coats absorb acids and solvents directly into underclothing.", + spec: "Standard Woven Cotton Lab Coat" } ], successMessage: { title: "SAFE TO PROCEED", - headline: "Correct chemical protection selected.", - body: "Your PPE helps protect the eyes, respiratory system and skin against accidental chemical splashes." + headline: "Correct Chemical Protection Selected.", + body: "Your PPE protects the eyes, skin, hearing, body and feet against hazardous chemical splashes and fumes." } } ]; diff --git a/ppe_character_steps_transparent_png/01_no_ppe.png b/ppe_character_steps_transparent_png/01_no_ppe.png new file mode 100644 index 0000000..94ee737 Binary files /dev/null and b/ppe_character_steps_transparent_png/01_no_ppe.png differ diff --git a/ppe_character_steps_transparent_png/02_helmet.png b/ppe_character_steps_transparent_png/02_helmet.png new file mode 100644 index 0000000..aade6c9 Binary files /dev/null and b/ppe_character_steps_transparent_png/02_helmet.png differ diff --git a/ppe_character_steps_transparent_png/03_helmet_goggles.png b/ppe_character_steps_transparent_png/03_helmet_goggles.png new file mode 100644 index 0000000..0aabaf7 Binary files /dev/null and b/ppe_character_steps_transparent_png/03_helmet_goggles.png differ diff --git a/ppe_character_steps_transparent_png/04_helmet_goggles_face_shield.png b/ppe_character_steps_transparent_png/04_helmet_goggles_face_shield.png new file mode 100644 index 0000000..cc6cf61 Binary files /dev/null and b/ppe_character_steps_transparent_png/04_helmet_goggles_face_shield.png differ diff --git a/ppe_character_steps_transparent_png/05_helmet_goggles_hearing.png b/ppe_character_steps_transparent_png/05_helmet_goggles_hearing.png new file mode 100644 index 0000000..0b6d2ee Binary files /dev/null and b/ppe_character_steps_transparent_png/05_helmet_goggles_hearing.png differ diff --git a/ppe_character_steps_transparent_png/06_helmet_goggles_hearing_work_gloves.png b/ppe_character_steps_transparent_png/06_helmet_goggles_hearing_work_gloves.png new file mode 100644 index 0000000..911a06f Binary files /dev/null and b/ppe_character_steps_transparent_png/06_helmet_goggles_hearing_work_gloves.png differ diff --git a/ppe_character_steps_transparent_png/07_helmet_goggles_hearing_leather_gloves.png b/ppe_character_steps_transparent_png/07_helmet_goggles_hearing_leather_gloves.png new file mode 100644 index 0000000..bf9a7ff Binary files /dev/null and b/ppe_character_steps_transparent_png/07_helmet_goggles_hearing_leather_gloves.png differ diff --git a/ppe_character_steps_transparent_png/08_helmet_goggles_hearing_leather_gloves_vest.png b/ppe_character_steps_transparent_png/08_helmet_goggles_hearing_leather_gloves_vest.png new file mode 100644 index 0000000..7fc1a3a Binary files /dev/null and b/ppe_character_steps_transparent_png/08_helmet_goggles_hearing_leather_gloves_vest.png differ diff --git a/ppe_character_steps_transparent_png/09_helmet_goggles_hearing_leather_gloves_vest_boots.png b/ppe_character_steps_transparent_png/09_helmet_goggles_hearing_leather_gloves_vest_boots.png new file mode 100644 index 0000000..fd02396 Binary files /dev/null and b/ppe_character_steps_transparent_png/09_helmet_goggles_hearing_leather_gloves_vest_boots.png differ diff --git a/ppe_character_steps_transparent_png/10_full_ppe_with_mask.png b/ppe_character_steps_transparent_png/10_full_ppe_with_mask.png new file mode 100644 index 0000000..23d6561 Binary files /dev/null and b/ppe_character_steps_transparent_png/10_full_ppe_with_mask.png differ diff --git a/ppe_icons_png/01_safety_helmet.png b/ppe_icons_png/01_safety_helmet.png new file mode 100644 index 0000000..8777b6c Binary files /dev/null and b/ppe_icons_png/01_safety_helmet.png differ diff --git a/ppe_icons_png/02_chemical_splash_goggles.png b/ppe_icons_png/02_chemical_splash_goggles.png new file mode 100644 index 0000000..bb86609 Binary files /dev/null and b/ppe_icons_png/02_chemical_splash_goggles.png differ diff --git a/ppe_icons_png/03_face_shield.png b/ppe_icons_png/03_face_shield.png new file mode 100644 index 0000000..25e075c Binary files /dev/null and b/ppe_icons_png/03_face_shield.png differ diff --git a/ppe_icons_png/04_hearing_protection.png b/ppe_icons_png/04_hearing_protection.png new file mode 100644 index 0000000..2215d8c Binary files /dev/null and b/ppe_icons_png/04_hearing_protection.png differ diff --git a/ppe_icons_png/05_chemical_resistant_gloves.png b/ppe_icons_png/05_chemical_resistant_gloves.png new file mode 100644 index 0000000..f14d4ec Binary files /dev/null and b/ppe_icons_png/05_chemical_resistant_gloves.png differ diff --git a/ppe_icons_png/06_standard_work_gloves.png b/ppe_icons_png/06_standard_work_gloves.png new file mode 100644 index 0000000..986d4b5 Binary files /dev/null and b/ppe_icons_png/06_standard_work_gloves.png differ diff --git a/ppe_icons_png/07_chemical_resistant_apron.png b/ppe_icons_png/07_chemical_resistant_apron.png new file mode 100644 index 0000000..77feed4 Binary files /dev/null and b/ppe_icons_png/07_chemical_resistant_apron.png differ diff --git a/ppe_icons_png/08_safety_shoes.png b/ppe_icons_png/08_safety_shoes.png new file mode 100644 index 0000000..216395b Binary files /dev/null and b/ppe_icons_png/08_safety_shoes.png differ diff --git a/ppe_icons_png/09_cotton_coat.png b/ppe_icons_png/09_cotton_coat.png new file mode 100644 index 0000000..5cc2f88 Binary files /dev/null and b/ppe_icons_png/09_cotton_coat.png differ