diff --git a/audio/countdown_start.wav b/audio/countdown_start.wav new file mode 100644 index 0000000..e5912cd Binary files /dev/null and b/audio/countdown_start.wav differ diff --git a/audio/countdown_tick.mp3 b/audio/countdown_tick.mp3 new file mode 100644 index 0000000..8908a0c Binary files /dev/null and b/audio/countdown_tick.mp3 differ diff --git a/audio/timer_last_10s.wav b/audio/timer_last_10s.wav new file mode 100644 index 0000000..191c12d Binary files /dev/null and b/audio/timer_last_10s.wav differ diff --git a/audio/timer_warning_8s.wav b/audio/timer_warning_8s.wav new file mode 100644 index 0000000..acee156 Binary files /dev/null and b/audio/timer_warning_8s.wav differ diff --git a/css/styles.css b/css/styles.css index 76f285d..f859f4e 100644 --- a/css/styles.css +++ b/css/styles.css @@ -70,9 +70,45 @@ html, body { flex-direction: column; min-height: 100vh; width: 100%; - max-width: 1680px; - margin: 0 auto; + max-width: 100%; + margin: 0; padding: 0 16px 16px 16px; + box-sizing: border-box; +} + +:fullscreen, +:-webkit-full-screen, +:fullscreen body, +:-webkit-full-screen body { + width: 100vw; + height: 100vh; + margin: 0; + padding: 0; + overflow: hidden; +} + +:fullscreen .app-container, +:-webkit-full-screen .app-container, +.fullscreen-mode .app-container { + width: 100vw; + max-width: 100vw; + height: 100vh; + min-height: 100vh; + margin: 0; + padding: 8px 16px 12px 16px; + box-sizing: border-box; +} + +:fullscreen .top-bar, +:-webkit-full-screen .top-bar, +.fullscreen-mode .top-bar { + margin: 0 0 10px 0; +} + +:fullscreen .gameplay-grid, +:-webkit-full-screen .gameplay-grid, +.fullscreen-mode .gameplay-grid { + height: calc(100vh - 80px); } /* ========================================================================== @@ -97,18 +133,20 @@ html, body { gap: 12px; } -.safety-helmet-icon { - width: 36px; - height: 36px; +.safety-shield-logo-box { + width: 38px; + height: 38px; display: flex; align-items: center; justify-content: center; } -.helmet-svg { +.brand-shield-img { width: 100%; height: 100%; - filter: drop-shadow(0 0 6px rgba(250, 204, 21, 0.4)); + object-fit: contain; + filter: drop-shadow(0 0 8px rgba(250, 204, 21, 0.7)); + display: block; } .brand-text { @@ -195,13 +233,72 @@ html, body { letter-spacing: 2px; } -/* HUD Right: Score & Audio Controls */ +/* HUD Right: Score, Timer & Audio Controls */ .hud-right { display: flex; align-items: center; gap: 12px; } +.timer-display { + display: flex; + align-items: center; + gap: 8px; + background: rgba(0, 0, 0, 0.45); + padding: 5px 14px; + border-radius: var(--radius-md); + border: 1px solid rgba(56, 189, 248, 0.35); + box-shadow: 0 0 10px rgba(56, 189, 248, 0.12); + transition: all 0.3s ease; +} + +.timer-icon { + font-size: 0.95rem; +} + +.timer-label { + font-size: 0.68rem; + font-weight: 700; + letter-spacing: 1px; + color: var(--text-muted); +} + +.timer-val { + font-family: var(--font-mono); + font-size: 1.15rem; + font-weight: 800; + color: #38bdf8; + min-width: 48px; + text-align: center; + letter-spacing: 0.5px; +} + +.timer-display.timer-warning { + border-color: #f59e0b; + box-shadow: 0 0 14px rgba(245, 158, 11, 0.35); +} + +.timer-display.timer-warning .timer-val { + color: #fbbf24; +} + +.timer-display.timer-danger { + border-color: #ef4444; + background: rgba(239, 68, 68, 0.18); + box-shadow: 0 0 18px rgba(239, 68, 68, 0.5); + animation: timerPulse 1s ease-in-out infinite; +} + +.timer-display.timer-danger .timer-val { + color: #ef4444; + text-shadow: 0 0 8px rgba(239, 68, 68, 0.8); +} + +@keyframes timerPulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.05); } +} + .score-display { display: flex; align-items: center; @@ -259,6 +356,13 @@ html, body { transform: translateY(-1px); } +.btn-icon-hud.fullscreen-active { + background: rgba(14, 165, 233, 0.25); + border-color: var(--safety-cyan); + color: var(--safety-cyan); + box-shadow: 0 0 10px rgba(56, 189, 248, 0.35); +} + /* ========================================================================== MAIN VIEWPORT & SCREEN MANAGEMENT ========================================================================== */ @@ -302,7 +406,7 @@ html, body { .home-screen-backdrop { position: relative; width: 100%; - max-width: 1440px; + max-width: 100%; margin: 0 auto; aspect-ratio: 1024 / 458; border-radius: var(--radius-lg); @@ -422,19 +526,54 @@ html, body { .gameplay-grid { display: grid; - grid-template-columns: 290px 1fr 360px; + grid-template-columns: 295px 1fr 370px; gap: 14px; width: 100%; flex: 1; } .card-panel { - background: var(--bg-panel); - border: 1px solid var(--panel-border); + background: rgba(10, 17, 28, 0.92); + border: 1px solid rgba(250, 204, 21, 0.28); border-radius: var(--radius-lg); padding: 16px; display: flex; flex-direction: column; + position: relative; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.65), 0 0 20px rgba(250, 204, 21, 0.08); +} + +/* High-Tech Industrial Golden Corner Brackets */ +.sidebar-hazard-panel::before, +.sidebar-hazard-panel::after, +.sidebar-locker-panel::before, +.sidebar-locker-panel::after { + content: ""; + position: absolute; + width: 16px; + height: 16px; + pointer-events: none; + border-color: var(--safety-yellow); + border-style: solid; + z-index: 5; +} + +.sidebar-hazard-panel::before, +.sidebar-locker-panel::before { + top: -1px; + left: -1px; + border-width: 3px 0 0 3px; + border-top-left-radius: var(--radius-lg); + filter: drop-shadow(0 0 6px var(--glow-yellow)); +} + +.sidebar-hazard-panel::after, +.sidebar-locker-panel::after { + bottom: -1px; + right: -1px; + border-width: 0 3px 3px 0; + border-bottom-right-radius: var(--radius-lg); + filter: drop-shadow(0 0 6px var(--glow-yellow)); } /* ========================================================================== @@ -447,50 +586,57 @@ html, body { } .panel-header { - margin-bottom: 4px; + margin-bottom: 2px; } .scenario-badge { display: inline-block; - font-family: var(--font-mono); - font-size: 0.7rem; - font-weight: 700; - color: var(--safety-cyan); - background: rgba(56, 189, 248, 0.12); - padding: 2px 8px; - border-radius: var(--radius-sm); - margin-bottom: 6px; + font-family: var(--font-display); + font-size: 0.72rem; + font-weight: 900; + color: #000; + background: var(--safety-yellow); + padding: 3px 12px; + border-radius: var(--radius-full); + margin-bottom: 8px; + letter-spacing: 0.8px; + text-transform: uppercase; + box-shadow: 0 2px 10px rgba(250, 204, 21, 0.4); } .scenario-title { font-family: var(--font-display); - font-size: 1.15rem; - font-weight: 700; + font-size: 1.25rem; + font-weight: 800; color: #fff; - line-height: 1.25; + line-height: 1.2; margin-bottom: 4px; } .situation-desc { - font-size: 0.8rem; + font-size: 0.78rem; color: var(--text-muted); line-height: 1.35; } .card-subtitle-small { - font-size: 0.7rem; + display: inline-flex; + align-items: center; + gap: 6px; + font-family: var(--font-display); + font-size: 0.72rem; font-weight: 800; - letter-spacing: 1px; - color: var(--text-muted); + letter-spacing: 0.5px; + color: var(--safety-yellow); margin-bottom: 8px; text-transform: uppercase; } /* Identify Hazards Card */ .hazard-identification-card { - background: #111a28; - border: 1px solid rgba(239, 68, 68, 0.25); - padding: 12px; + background: #0d1624; + border: 1px solid rgba(250, 204, 21, 0.2); + padding: 10px 12px; border-radius: var(--radius-md); } @@ -508,6 +654,7 @@ html, body { font-size: 0.78rem; color: #fca5a5; line-height: 1.3; + font-weight: 500; } .hazard-list-item::before { @@ -518,64 +665,74 @@ html, body { /* Worker Protection Status Card (With Blue Neon Silhouette) */ .worker-protection-card { - background: #111a28; - border: 1px solid var(--panel-border-light); - padding: 12px; + background: #0d1624; + border: 1px solid rgba(56, 189, 248, 0.25); + padding: 10px 12px; border-radius: var(--radius-md); } .protection-status-content { display: flex; align-items: center; - gap: 14px; + gap: 12px; } .body-silhouette-box { - width: 56px; - height: 130px; + width: 66px; + height: 154px; 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); + background: rgba(9, 16, 26, 0.85); + border: 1px solid rgba(56, 189, 248, 0.35); + border-radius: 8px; padding: 4px; + overflow: hidden; + box-shadow: 0 0 14px rgba(56, 189, 248, 0.18), inset 0 0 8px rgba(56, 189, 248, 0.06); } -.wireframe-body-svg { +.hologram-silhouette-img { width: 100%; height: 100%; - filter: drop-shadow(0 0 4px rgba(56, 189, 248, 0.5)); + object-fit: contain; + filter: drop-shadow(0 0 6px rgba(56, 189, 248, 0.75)); + animation: hologramGlowPulse 3s ease-in-out infinite alternate; +} + +@keyframes hologramGlowPulse { + 0% { filter: drop-shadow(0 0 4px rgba(56, 189, 248, 0.5)); opacity: 0.92; } + 100% { filter: drop-shadow(0 0 12px rgba(56, 189, 248, 0.95)) drop-shadow(0 0 20px rgba(14, 165, 233, 0.55)); opacity: 1; } } .protection-rows { flex: 1; display: flex; flex-direction: column; - gap: 5px; + gap: 4px; } .status-row-item { display: flex; align-items: center; justify-content: space-between; - background: #162235; + background: #121e30; padding: 4px 8px; border-radius: var(--radius-sm); border: 1px solid var(--panel-border); - font-size: 0.74rem; + font-size: 0.75rem; transition: var(--transition-fast); } .status-row-item .row-icon { font-size: 0.85rem; - margin-right: 4px; + margin-right: 6px; } .status-row-item .row-label { flex: 1; color: #fff; - font-weight: 500; + font-weight: 600; + font-size: 0.74rem; } .status-row-item .row-badge { @@ -597,11 +754,12 @@ html, body { .status-row-item .row-badge.status-check { background: var(--safety-green); color: #fff; + box-shadow: 0 0 8px var(--glow-green); } /* Tip Card */ .tip-card { - background: #131d2e; + background: #0d1624; border: 1px solid var(--panel-border); padding: 10px 12px; border-radius: var(--radius-md); @@ -609,14 +767,18 @@ html, body { } .tip-title { - font-size: 0.72rem; + font-family: var(--font-display); + font-size: 0.74rem; font-weight: 800; color: var(--safety-yellow); margin-bottom: 3px; + display: flex; + align-items: center; + gap: 5px; } .tip-text { - font-size: 0.75rem; + font-size: 0.74rem; color: var(--text-muted); line-height: 1.35; } @@ -683,7 +845,7 @@ html, body { width: 100%; height: 100%; object-fit: cover; - object-position: center 60%; + object-position: center 56%; display: block; filter: contrast(1.03) brightness(0.98); image-rendering: -webkit-optimize-contrast; @@ -698,7 +860,7 @@ html, body { } .stage-grinding .env-stage-photo { - object-position: center 58%; + object-position: center 55%; } .env-stage-overlay { @@ -719,6 +881,9 @@ html, body { display: flex; align-items: center; justify-content: center; + margin: 0 auto; + transform: scale(1.45); + transform-origin: center center; } .worker-svg { @@ -832,228 +997,699 @@ html, body { to { opacity: 1; transform: scale(1); } } +/* ========================================================================== + CONSEQUENCE ALERT OVERLAY & CYBER HUD MODAL (EXACT MATCH TO REFERENCE UI) + ========================================================================== */ + .consequence-freeze-overlay { - background: rgba(18, 4, 4, 0.88); - border: 3px solid var(--safety-red); -} - -.freeze-banner { - background: #180c0c; - border: 2px solid var(--safety-red); - border-radius: var(--radius-lg); - padding: 22px; - width: 95%; - max-width: 620px; - max-height: 90vh; - display: flex; - flex-direction: column; - text-align: center; - box-shadow: 0 0 50px var(--glow-red); - overflow-y: auto; -} - -.hazard-flasher { - display: inline-block; - font-family: var(--font-mono); - font-size: 0.82rem; - font-weight: 800; - color: #fff; - background: var(--safety-red); - padding: 5px 14px; - border-radius: var(--radius-sm); - margin-bottom: 8px; - animation: flashRed 0.8s infinite alternate; - letter-spacing: 0.5px; -} - -@keyframes flashRed { - from { opacity: 0.85; transform: scale(0.98); } - to { opacity: 1; filter: brightness(1.25); transform: scale(1.02); } -} - -.consequence-title { - font-family: var(--font-display); - font-size: 1.3rem; - color: #fff; - margin-bottom: 4px; -} - -.consequence-desc { - font-size: 0.82rem; - color: #fca5a5; - line-height: 1.35; - margin-bottom: 12px; -} - -.missing-items-box { - background: rgba(0, 0, 0, 0.5); - border: 1px solid rgba(239, 68, 68, 0.35); - padding: 10px 12px; - border-radius: var(--radius-md); - margin-bottom: 14px; - text-align: left; -} - -.missing-box-header { - margin-bottom: 8px; - padding-bottom: 4px; - border-bottom: 1px solid rgba(239, 68, 68, 0.2); -} - -.missing-items-box .box-label { - font-size: 0.7rem; - font-weight: 800; - color: var(--safety-yellow); - letter-spacing: 1px; -} - -.missing-hazard-cards { - display: flex; - flex-direction: column; - gap: 6px; - max-height: 220px; - overflow-y: auto; - padding-right: 4px; -} - -.hazard-card-item { - display: flex; - align-items: flex-start; - 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: 6px 8px; - border-radius: var(--radius-sm); -} - -.hazard-card-item.distractor-card { - border-left-color: var(--safety-amber); - background: rgba(245, 158, 11, 0.1); - border-color: rgba(245, 158, 11, 0.4); -} - -.hazard-card-item .card-icon { - font-size: 1.1rem; - line-height: 1; - padding-top: 2px; -} - -.hazard-card-item .card-body { - flex: 1; - display: flex; - flex-direction: column; - gap: 2px; -} - -.hazard-card-item .card-header-line { - display: flex; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 35; + display: none; align-items: center; - justify-content: space-between; - gap: 6px; -} - -.hazard-card-item .card-item-name { - font-size: 0.8rem; - font-weight: 700; - color: #fff; -} - -.hazard-card-item .card-slot-badge { - font-family: var(--font-mono); - font-size: 0.65rem; - font-weight: 700; - color: var(--safety-red); - background: rgba(239, 68, 68, 0.2); - padding: 1px 6px; - border-radius: 4px; -} - -.hazard-card-item .card-hazard-text { - font-size: 0.72rem; - color: #fca5a5; - line-height: 1.25; -} - -.btn-warning { - background: var(--safety-orange); - color: #000; - font-weight: 800; - padding: 10px 20px; -} - -.btn-warning:hover { - background: #f97316; - box-shadow: 0 4px 16px rgba(249, 115, 22, 0.4); -} - -/* Success Overlay */ -.success-overlay { - background: rgba(5, 25, 15, 0.85); - border: 3px solid var(--safety-green); -} - -.success-banner { - background: #091a13; - border: 2px solid var(--safety-green); - border-radius: var(--radius-lg); + justify-content: center; padding: 24px; - max-width: 460px; - text-align: center; - box-shadow: 0 0 40px var(--glow-green); + background: rgba(18, 4, 4, 0.82); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + animation: popOverlay 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; + border: none; } -.success-badge { +.consequence-freeze-overlay.active { + display: flex; +} + +.cyber-consequence-modal { + position: relative; + width: 95%; + max-width: 630px; + max-height: 92vh; + background-color: #0e0505; + background-image: + radial-gradient(ellipse at 50% 15%, rgba(239, 68, 68, 0.16) 0%, transparent 60%), + radial-gradient(circle, rgba(239, 68, 68, 0.12) 1.2px, transparent 1.2px); + background-size: 100% 100%, 14px 14px; + background-position: center center, 0 0; + border: 2px solid #ef4444; + border-radius: 26px; + padding: 42px 30px 26px 30px; + text-align: center; + box-shadow: + 0 0 35px rgba(239, 68, 68, 0.5), + inset 0 0 25px rgba(239, 68, 68, 0.12), + 0 25px 60px rgba(0, 0, 0, 0.9); + display: flex; + flex-direction: column; + align-items: center; + overflow-y: auto; +} + +/* Side Hazard Flange Vents */ +.hazard-flange .hazard-vent-bar { + width: 4px; + height: 18px; + background: #ef4444; + box-shadow: 0 0 6px #ef4444; + border-radius: 1px; + opacity: 0.85; +} + +.flange-left.hazard-flange .hazard-vent-bar { + transform: skewY(-35deg); +} + +.flange-right.hazard-flange .hazard-vent-bar { + transform: skewY(35deg); +} + +/* Red Corner Accent Notches */ +.red-notch-tl { + top: 8px; + left: 8px; + border-top: 3px solid #ff2244; + border-left: 3px solid #ff2244; + border-top-left-radius: 18px; +} + +.red-notch-tr { + top: 8px; + right: 8px; + border-top: 3px solid #ff2244; + border-right: 3px solid #ff2244; + border-top-right-radius: 18px; +} + +.red-notch-bl { + bottom: 8px; + left: 8px; + border-bottom: 3px solid #ff2244; + border-left: 3px solid #ff2244; + border-bottom-left-radius: 18px; +} + +.red-notch-br { + bottom: 8px; + right: 8px; + border-bottom: 3px solid #ff2244; + border-right: 3px solid #ff2244; + border-bottom-right-radius: 18px; +} + +/* Top Triangle Hazard Badge */ +.hud-hazard-badge-wrapper { + position: absolute; + top: -36px; + left: 50%; + transform: translateX(-50%); + width: 72px; + height: 60px; display: flex; align-items: center; justify-content: center; - gap: 8px; - margin-bottom: 8px; + z-index: 5; } -.badge-icon { - font-size: 1.5rem; +.hazard-triangle-svg { + width: 100%; + height: 100%; + filter: drop-shadow(0 0 12px rgba(255, 34, 68, 0.85)) drop-shadow(0 0 24px rgba(239, 68, 68, 0.5)); } -.badge-title { - font-family: var(--font-display); - font-size: 1.4rem; - font-weight: 700; - color: var(--safety-green); +/* Modal Inner Body */ +.cyber-consequence-body { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + position: relative; + z-index: 3; } -.success-desc { - font-size: 0.88rem; - color: #a7f3d0; - line-height: 1.4; - margin-bottom: 16px; -} - -.points-awarded-pill { +/* Stop Flasher Pill */ +.cyber-hazard-flasher { display: inline-flex; align-items: center; + justify-content: center; + gap: 8px; + background: linear-gradient(90deg, rgba(239, 68, 68, 0.25) 0%, rgba(239, 68, 68, 0.95) 50%, rgba(239, 68, 68, 0.25) 100%); + border: 1.5px solid #ef4444; + border-radius: 20px; + padding: 5px 22px; + margin-top: 10px; + margin-bottom: 8px; + box-shadow: 0 0 16px rgba(239, 68, 68, 0.6); + animation: flashRedPulse 1.2s infinite alternate; +} + +@keyframes flashRedPulse { + 0% { transform: scale(0.98); opacity: 0.9; } + 100% { transform: scale(1.02); opacity: 1; filter: brightness(1.2); } +} + +.flasher-icon { + font-size: 0.95rem; +} + +.flasher-text { + font-family: var(--font-display); + font-size: 0.85rem; + font-weight: 900; + color: #ffffff; + letter-spacing: 1px; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); +} + +.cyber-consequence-title { + font-family: var(--font-display); + font-size: 1.85rem; + font-weight: 900; + letter-spacing: 2px; + margin-top: 4px; + margin-bottom: 6px; + line-height: 1.2; +} + +.cyber-consequence-title .title-red { + color: #ef4444; + text-shadow: 0 0 16px rgba(239, 68, 68, 0.85); +} + +.cyber-consequence-title .title-white { + color: #ffffff; + text-shadow: 0 0 12px rgba(255, 255, 255, 0.35); +} + +.cyber-consequence-desc { + font-size: 0.84rem; + color: #fca5a5; + line-height: 1.4; + max-width: 520px; + margin: 0 auto 16px auto; + font-weight: 500; +} + +/* Missing Issues Box */ +.cyber-missing-box { + width: 100%; + background: rgba(0, 0, 0, 0.58); + border: 1.5px solid rgba(239, 68, 68, 0.45); + border-radius: 14px; + padding: 12px 14px; + box-shadow: inset 0 0 16px rgba(239, 68, 68, 0.1); + margin-bottom: 20px; + text-align: left; +} + +.cyber-missing-header { + display: flex; + align-items: center; gap: 6px; - background: rgba(250, 204, 21, 0.2); - border: 1px solid var(--safety-yellow); - color: var(--safety-yellow); - padding: 6px 16px; - border-radius: var(--radius-full); - font-family: var(--font-mono); + margin-bottom: 10px; + padding-bottom: 6px; + border-bottom: 1px solid rgba(239, 68, 68, 0.25); + font-family: var(--font-display); + font-size: 0.82rem; font-weight: 800; - font-size: 0.95rem; - margin-bottom: 18px; + color: #facc15; + letter-spacing: 0.8px; + text-shadow: 0 0 8px rgba(250, 204, 21, 0.45); } -.btn-success { - background: var(--safety-green); - color: #fff; - font-size: 0.95rem; - padding: 10px 22px; +.cyber-hazard-list { + display: flex; + flex-direction: column; + gap: 8px; + max-height: 220px; + overflow-y: auto; + padding-right: 6px; } -.btn-success:hover { - background: #059669; - box-shadow: 0 4px 16px var(--glow-green); +.cyber-hazard-list::-webkit-scrollbar { + width: 6px; +} + +.cyber-hazard-list::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.4); + border-radius: 4px; +} + +.cyber-hazard-list::-webkit-scrollbar-thumb { + background: linear-gradient(180deg, #ef4444 0%, #f59e0b 100%); + border-radius: 4px; + box-shadow: 0 0 8px rgba(239, 68, 68, 0.6); +} + +/* Cyber Hazard Card */ +.cyber-hazard-card { + display: flex; + align-items: flex-start; + gap: 12px; + background: rgba(22, 9, 9, 0.9); + border: 1px solid rgba(239, 68, 68, 0.45); + border-radius: 10px; + padding: 10px 12px; + text-align: left; + transition: all 0.2s ease; +} + +.cyber-hazard-card:hover { + border-color: #ef4444; + box-shadow: 0 0 14px rgba(239, 68, 68, 0.35); + background: rgba(30, 12, 12, 0.95); +} + +.cyber-hazard-card.distractor-card { + background: rgba(26, 16, 8, 0.9); + border-color: rgba(245, 158, 11, 0.5); +} + +.hazard-card-icon-box { + width: 48px; + height: 48px; + min-width: 48px; + background: rgba(0, 0, 0, 0.65); + border: 1px solid rgba(239, 68, 68, 0.35); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.hazard-card-ppe-img { + width: 82%; + height: 82%; + object-fit: contain; + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.6)); +} + +.hazard-card-fallback-icon { + font-size: 1.4rem; +} + +.hazard-card-content { + flex: 1; + display: flex; + flex-direction: column; + gap: 3px; +} + +.hazard-card-top-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.hazard-card-name { + font-family: var(--font-display); + font-size: 0.86rem; + font-weight: 800; + color: #ffffff; +} + +.hazard-card-badge { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 0.7rem; + font-weight: 700; + padding: 2px 9px; + border-radius: 12px; + white-space: nowrap; +} + +.hazard-card-badge.missing-badge { + background: rgba(239, 68, 68, 0.25); + border: 1px solid #ef4444; + color: #fca5a5; +} + +.hazard-card-badge.distractor-badge { + background: rgba(245, 158, 11, 0.25); + border: 1px solid #f59e0b; + color: #fde047; +} + +.badge-exclamation { + display: inline-flex; + align-items: center; + justify-content: center; + width: 13px; + height: 13px; + border-radius: 50%; + background: #ef4444; + color: #000; + font-size: 0.62rem; + font-weight: 900; + line-height: 1; +} + +.distractor-badge .badge-exclamation { + background: #f59e0b; +} + +.hazard-card-desc { + font-size: 0.74rem; + color: #fca5a5; + line-height: 1.35; + margin: 0; +} + +/* Fix All Worker PPE Button */ +.btn-cyber-fix { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 10px; + background: linear-gradient(180deg, #ef4444 0%, #b91c1c 100%); + color: #ffffff; + border: 1.5px solid #ff4466; + border-radius: 14px; + padding: 13px 36px; + font-family: var(--font-display); + font-size: 1.08rem; + font-weight: 800; + letter-spacing: 1.5px; + cursor: pointer; + box-shadow: + 0 0 25px rgba(239, 68, 68, 0.65), + 0 4px 14px rgba(0, 0, 0, 0.5); + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); + user-select: none; +} + +.btn-cyber-fix:hover { + transform: translateY(-2px); + filter: brightness(1.15); + box-shadow: + 0 0 35px rgba(239, 68, 68, 0.9), + 0 6px 18px rgba(0, 0, 0, 0.6); +} + +.btn-cyber-fix:active { + transform: translateY(0); +} + +.btn-fix-icon { + font-size: 1rem; +} + +/* ========================================================================== + SUCCESS OVERLAY & CYBER HUD MODAL (EXACT MATCH TO REFERENCE UI) + ========================================================================== */ + +.success-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 35; + display: none; + align-items: center; + justify-content: center; + padding: 24px; + background: rgba(4, 12, 9, 0.76); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + animation: popOverlay 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; + border: none; +} + +.success-overlay.active { + display: flex; +} + +.cyber-success-modal { + position: relative; + width: 95%; + max-width: 560px; + background-color: #06130e; + background-image: + radial-gradient(ellipse at 50% 15%, rgba(0, 255, 136, 0.12) 0%, transparent 60%), + radial-gradient(circle, rgba(0, 255, 136, 0.15) 1.2px, transparent 1.2px); + background-size: 100% 100%, 14px 14px; + background-position: center center, 0 0; + border: 2px solid #00e699; + border-radius: 26px; + padding: 44px 34px 30px 34px; + text-align: center; + box-shadow: + 0 0 28px rgba(0, 230, 153, 0.45), + inset 0 0 24px rgba(0, 230, 153, 0.1), + 0 24px 60px rgba(0, 0, 0, 0.85); + display: flex; + flex-direction: column; + align-items: center; +} + +/* Side Flange Decorative Vents */ +.cyber-flange { + position: absolute; + top: 50%; + transform: translateY(-50%); + display: flex; + flex-direction: column; + gap: 5px; + pointer-events: none; + z-index: 2; +} + +.flange-left { + left: 10px; +} + +.flange-right { + right: 10px; +} + +.vent-bar { + width: 4px; + height: 18px; + background: #00e699; + box-shadow: 0 0 6px #00e699; + border-radius: 1px; + opacity: 0.85; +} + +.flange-left .vent-bar { + transform: skewY(-35deg); +} + +.flange-right .vent-bar { + transform: skewY(35deg); +} + +/* Corner Notches */ +.cyber-corner-notch { + position: absolute; + width: 24px; + height: 24px; + border-color: #00ff88; + pointer-events: none; +} + +.notch-tl { + top: 8px; + left: 8px; + border-top: 3px solid #00ff88; + border-left: 3px solid #00ff88; + border-top-left-radius: 18px; +} + +.notch-tr { + top: 8px; + right: 8px; + border-top: 3px solid #00ff88; + border-right: 3px solid #00ff88; + border-top-right-radius: 18px; +} + +.notch-bl { + bottom: 8px; + left: 8px; + border-bottom: 3px solid #00ff88; + border-left: 3px solid #00ff88; + border-bottom-left-radius: 18px; +} + +.notch-br { + bottom: 8px; + right: 8px; + border-bottom: 3px solid #00ff88; + border-right: 3px solid #00ff88; + border-bottom-right-radius: 18px; +} + +/* Top Circular HUD Checkmark Badge */ +.hud-check-badge-wrapper { + position: absolute; + top: -44px; + left: 50%; + transform: translateX(-50%); + width: 88px; + height: 88px; + display: flex; + align-items: center; + justify-content: center; + z-index: 5; +} + +.hud-radar-ring { + position: absolute; + width: 120px; + height: 120px; + pointer-events: none; + animation: radarSpin 20s linear infinite; +} + +.radar-svg { + width: 100%; + height: 100%; +} + +@keyframes radarSpin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.hud-check-circle { + width: 74px; + height: 74px; + border-radius: 50%; + background: radial-gradient(circle at 35% 35%, #00e676 0%, #008744 65%, #004d26 100%); + border: 2.5px solid #00ff88; + box-shadow: + 0 0 22px rgba(0, 255, 136, 0.9), + 0 0 45px rgba(0, 230, 118, 0.5), + inset 0 0 12px rgba(255, 255, 255, 0.45); + display: flex; + align-items: center; + justify-content: center; +} + +.hud-check-svg { + width: 36px; + height: 36px; + filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.6)); +} + +/* Modal Inner Content */ +.cyber-success-body { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + position: relative; + z-index: 3; +} + +.cyber-success-title { + font-family: var(--font-display); + font-size: 1.85rem; + font-weight: 900; + letter-spacing: 2px; + margin-top: 14px; + margin-bottom: 3px; + line-height: 1.2; +} + +.cyber-success-title .title-green { + color: #00e699; + text-shadow: 0 0 16px rgba(0, 230, 153, 0.65); +} + +.cyber-success-title .title-white { + color: #ffffff; + text-shadow: 0 0 12px rgba(255, 255, 255, 0.35); +} + +.cyber-title-dash { + width: 32px; + height: 2.5px; + background: #00e699; + box-shadow: 0 0 8px #00e699; + border-radius: 2px; + margin: 4px auto 14px auto; +} + +.cyber-success-desc { + font-size: 0.88rem; + color: #cbd5e1; + line-height: 1.45; + max-width: 440px; + margin: 0 auto 18px auto; + font-weight: 500; +} + +/* Safety Points Pill */ +.cyber-points-pill { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 10px; + background: rgba(18, 12, 3, 0.92); + border: 1.5px solid #f59e0b; + border-radius: 30px; + padding: 8px 24px; + box-shadow: + 0 0 18px rgba(245, 158, 11, 0.45), + inset 0 0 10px rgba(245, 158, 11, 0.15); + margin-bottom: 22px; +} + +.points-star { + color: #fbbf24; + font-size: 1.15rem; + text-shadow: 0 0 10px #f59e0b; +} + +.points-value { + font-family: var(--font-display); + font-size: 1.18rem; + font-weight: 800; + color: #fbbf24; + letter-spacing: 1px; + text-shadow: 0 0 10px rgba(251, 191, 36, 0.6); +} + +/* Next Scenario Button */ +.btn-cyber-next { + display: inline-flex; + align-items: center; + justify-content: center; + background: linear-gradient(180deg, #00e699 0%, #00b377 100%); + color: #ffffff; + border: none; + border-radius: 12px; + padding: 13px 36px; + font-family: var(--font-display); + font-size: 1.05rem; + font-weight: 800; + letter-spacing: 1.5px; + cursor: pointer; + box-shadow: + 0 0 22px rgba(0, 230, 153, 0.6), + 0 4px 14px rgba(0, 0, 0, 0.4); + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); + user-select: none; +} + +.btn-cyber-next:hover { + transform: translateY(-2px); + filter: brightness(1.15); + box-shadow: + 0 0 30px rgba(0, 230, 153, 0.85), + 0 6px 18px rgba(0, 0, 0, 0.5); +} + +.btn-cyber-next:active { + transform: translateY(0); } /* ========================================================================== @@ -1079,14 +1715,14 @@ html, body { } .locker-icon { - font-size: 1.1rem; + font-size: 1.15rem; } .locker-title { font-family: var(--font-display); - font-size: 1.15rem; - font-weight: 700; - color: #fff; + font-size: 1.22rem; + font-weight: 800; + color: var(--safety-yellow); letter-spacing: 0.5px; } @@ -1096,7 +1732,7 @@ html, body { } .locker-instruction { - font-size: 0.72rem; + font-size: 0.74rem; color: var(--safety-cyan); } @@ -1107,7 +1743,7 @@ html, body { gap: 8px; width: 100%; margin-bottom: auto; - max-height: calc(100vh - 265px); + max-height: calc(100vh - 255px); overflow-y: auto; padding-right: 2px; } @@ -1136,57 +1772,38 @@ html, body { align-items: center; justify-content: space-between; text-align: center; - background: #09121f; - border: 1.5px solid #162740; - border-radius: var(--radius-md); - padding: 6px 3px 5px 3px; + background: #0d1624; + border: 1px solid #1a293d; + border-radius: 8px; + padding: 8px 4px 6px 4px; cursor: pointer; - transition: var(--transition-normal); + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); position: relative; aspect-ratio: 1 / 1.15; width: 100%; + outline: none; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35); + user-select: none; + overflow: hidden; } .ppe-item-card:hover { - background: #111e33; - border-color: var(--safety-cyan); + background: #122035; + border-color: rgba(56, 189, 248, 0.75); transform: translateY(-2px); - box-shadow: 0 4px 14px rgba(56, 189, 248, 0.25); + box-shadow: 0 4px 16px rgba(56, 189, 248, 0.28), inset 0 0 10px rgba(56, 189, 248, 0.08); } .ppe-item-card.equipped { - background: linear-gradient(180deg, rgba(14, 165, 233, 0.2) 0%, rgba(14, 165, 233, 0.08) 100%); + background: linear-gradient(180deg, rgba(14, 165, 233, 0.18) 0%, rgba(13, 22, 36, 0.95) 100%); border-color: var(--safety-cyan); - box-shadow: 0 0 14px rgba(56, 189, 248, 0.35); + box-shadow: 0 0 16px rgba(56, 189, 248, 0.4), inset 0 0 12px rgba(14, 165, 233, 0.15); } -/* Radio Indicator Top-Right */ -.item-toggle-indicator { - position: absolute; - top: 6px; - right: 6px; - width: 16px; - height: 16px; - border-radius: 50%; - border: 1.5px solid #1e3556; - background: rgba(0, 0, 0, 0.45); - display: flex; - align-items: center; - justify-content: center; - transition: var(--transition-fast); -} - -.ppe-item-card.equipped .item-toggle-indicator { - background: var(--safety-cyan); - border-color: var(--safety-cyan); -} - -.radio-check-mark { - font-size: 0.65rem; - font-weight: 900; - color: #000; - line-height: 1; +.ppe-item-card.equipped:hover { + border-color: #7dd3fc; + box-shadow: 0 0 20px rgba(56, 189, 248, 0.55), inset 0 0 14px rgba(14, 165, 233, 0.22); + transform: translateY(-2px); } .item-icon-box { @@ -1223,7 +1840,7 @@ html, body { .item-name { font-family: var(--font-display); - font-size: 0.72rem; + font-size: 0.74rem; font-weight: 700; color: #fff; line-height: 1.18; @@ -1241,159 +1858,337 @@ html, body { flex-direction: column; gap: 8px; margin-top: auto; - padding-top: 4px; + padding-top: 6px; } .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; - padding: 8px 16px; - border-radius: var(--radius-md); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - gap: 6px; - transition: var(--transition-fast); -} - -.btn-check-ppe:hover { - background: #24344c; - color: #fff; - border-color: var(--safety-cyan); -} - -.btn-check-icon { - font-weight: 900; - color: var(--safety-cyan); + display: none; } .btn-start-task { background: var(--safety-yellow); color: #000; font-family: var(--font-display); - font-size: 1.05rem; + font-size: 1.15rem; font-weight: 900; - letter-spacing: 0.5px; - padding: 12px 18px; + letter-spacing: 0.8px; + padding: 13px 20px; border-radius: var(--radius-md); border: none; cursor: pointer; + width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px; - box-shadow: 0 4px 16px var(--glow-yellow); + box-shadow: 0 4px 18px var(--glow-yellow); transition: var(--transition-normal); } .btn-start-task:hover { background: #fde047; transform: translateY(-2px); - box-shadow: 0 6px 24px var(--glow-yellow); + box-shadow: 0 6px 26px var(--glow-yellow); } .btn-play-icon { - font-size: 0.95rem; + font-size: 1rem; } /* ========================================================================== - SCREEN 3: SUMMARY / COMPLETION VIEW + SCREEN 3: SUMMARY / COMPLETION VIEW (EXACT MATCH TO REFERENCE UI) ========================================================================== */ -.summary-card { - max-width: 900px; - margin: 20px auto; - 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); +#screen-summary { + display: none; + justify-content: center; + align-items: center; + padding: 24px 16px; + min-height: calc(100vh - 70px); +} + +#screen-summary.active { + display: flex; +} + +.cyber-summary-container { + position: relative; + width: 100%; + max-width: 960px; + background-color: #0a111a; + background-image: + radial-gradient(ellipse at 50% 10%, rgba(245, 158, 11, 0.14) 0%, transparent 60%), + radial-gradient(circle, rgba(245, 158, 11, 0.12) 1.2px, transparent 1.2px); + background-size: 100% 100%, 16px 16px; + background-position: center center, 0 0; + border: 2px solid #f59e0b; + border-radius: 28px; + padding: 36px 36px 30px 36px; + box-shadow: + 0 0 35px rgba(245, 158, 11, 0.45), + inset 0 0 25px rgba(245, 158, 11, 0.1), + 0 25px 60px rgba(0, 0, 0, 0.9); display: flex; flex-direction: column; - gap: 22px; -} - -.summary-header { + gap: 18px; text-align: center; + animation: popOverlay 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards; } -.victory-trophy { - font-size: 3rem; - margin-bottom: 6px; +/* Summary Flange Gold Vents */ +.summary-flange .gold-vent-bar { + width: 4px; + height: 22px; + background: #f59e0b; + box-shadow: 0 0 6px #f59e0b; + border-radius: 1px; + opacity: 0.85; } -.summary-title { - font-family: var(--font-display); - font-size: 1.8rem; - color: #fff; +.flange-left.summary-flange .gold-vent-bar { + transform: skewY(-35deg); } -.summary-subtitle { - font-size: 0.95rem; - font-weight: 700; - color: var(--safety-yellow); - letter-spacing: 2px; +.flange-right.summary-flange .gold-vent-bar { + transform: skewY(35deg); } -.metrics-dashboard { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 14px; +/* Gold Corner Accents */ +.gold-notch-tl { + top: 8px; + left: 8px; + border-top: 3.5px solid #fbbf24; + border-left: 3.5px solid #fbbf24; + border-top-left-radius: 20px; } -.metric-box { - background: rgba(0, 0, 0, 0.35); - border: 1px solid var(--panel-border); - border-radius: var(--radius-md); - padding: 16px; - text-align: center; +.gold-notch-tr { + top: 8px; + right: 8px; + border-top: 3.5px solid #fbbf24; + border-right: 3.5px solid #fbbf24; + border-top-right-radius: 20px; +} + +.gold-notch-bl { + bottom: 8px; + left: 8px; + border-bottom: 3.5px solid #fbbf24; + border-left: 3.5px solid #fbbf24; + border-bottom-left-radius: 20px; +} + +.gold-notch-br { + bottom: 8px; + right: 8px; + border-bottom: 3.5px solid #fbbf24; + border-right: 3.5px solid #fbbf24; + border-bottom-right-radius: 20px; +} + +/* Trophy Header */ +.summary-trophy-header { display: flex; flex-direction: column; + align-items: center; gap: 4px; } -.metric-label { - font-size: 0.7rem; +.trophy-artwork-box { + width: 140px; + height: 80px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 2px; +} + +.trophy-laurel-svg { + width: 100%; + height: 100%; + filter: drop-shadow(0 0 14px rgba(245, 158, 11, 0.7)); +} + +.summary-champion-title { + font-family: var(--font-display); + font-size: 2.1rem; + font-weight: 900; + letter-spacing: 2px; + margin: 0; + line-height: 1.15; +} + +.summary-champion-title .title-white { + color: #ffffff; + text-shadow: 0 0 12px rgba(255, 255, 255, 0.35); +} + +.summary-champion-title .title-gold { + color: #fbbf24; + text-shadow: 0 0 18px rgba(245, 158, 11, 0.75); +} + +.summary-champion-subtitle { + font-family: var(--font-display); + font-size: 1.02rem; font-weight: 700; + color: #fde047; letter-spacing: 1px; + margin: 0; +} + +/* 3 Highlight Metric Cards */ +.summary-metrics-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 16px; + margin-top: 4px; +} + +.summary-stat-card { + display: flex; + align-items: center; + gap: 14px; + padding: 14px 18px; + border-radius: 14px; + text-align: left; + transition: transform 0.2s ease; +} + +.summary-stat-card:hover { + transform: translateY(-2px); +} + +.summary-stat-card.stat-green { + background: rgba(6, 22, 16, 0.9); + border: 1.5px solid #10b981; + box-shadow: 0 0 16px rgba(16, 185, 129, 0.25), inset 0 0 12px rgba(16, 185, 129, 0.08); +} + +.summary-stat-card.stat-cyan { + background: rgba(8, 22, 34, 0.9); + border: 1.5px solid #0ea5e9; + box-shadow: 0 0 16px rgba(14, 165, 233, 0.25), inset 0 0 12px rgba(14, 165, 233, 0.08); +} + +.summary-stat-card.stat-gold { + background: rgba(26, 18, 6, 0.9); + border: 1.5px solid #f59e0b; + box-shadow: 0 0 16px rgba(245, 158, 11, 0.25), inset 0 0 12px rgba(245, 158, 11, 0.08); +} + +.stat-icon-badge { + width: 44px; + height: 44px; + min-width: 44px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + padding: 6px; +} + +.icon-shield-green { + background: rgba(16, 185, 129, 0.18); + border: 1px solid rgba(16, 185, 129, 0.4); +} + +.icon-clipboard-cyan { + background: rgba(14, 165, 233, 0.18); + border: 1px solid rgba(14, 165, 233, 0.4); +} + +.icon-star-gold { + background: rgba(245, 158, 11, 0.18); + border: 1px solid rgba(245, 158, 11, 0.4); +} + +.stat-svg { + width: 100%; + height: 100%; +} + +.stat-text-group { + display: flex; + flex-direction: column; + gap: 2px; +} + +.stat-label { + font-family: var(--font-display); + font-size: 0.72rem; + font-weight: 800; + letter-spacing: 0.8px; color: var(--text-muted); } -.metric-val { - font-family: var(--font-mono); - font-size: 1.6rem; - font-weight: 800; - color: var(--safety-yellow); -} +.stat-green .stat-label { color: #6ee7b7; } +.stat-cyan .stat-label { color: #7dd3fc; } +.stat-gold .stat-label { color: #fcd34d; } -.metric-sub { - font-size: 0.74rem; - color: var(--text-dim); -} - -.takeaway-banner { - background: #090f19; - border: 2px solid var(--safety-yellow); - border-radius: var(--radius-md); - padding: 14px 18px; - text-align: center; -} - -.takeaway-heading { +.stat-val { font-family: var(--font-display); - font-size: 0.8rem; - font-weight: 700; - letter-spacing: 1.5px; - color: var(--safety-yellow); - margin-bottom: 10px; + font-size: 1.6rem; + font-weight: 900; + color: #ffffff; + line-height: 1.1; } -.mantra-chain { +.stat-stars-text { + color: #fbbf24; + letter-spacing: 2px; +} + +.stat-sub { + font-size: 0.72rem; + color: var(--text-dim); + line-height: 1.25; +} + +.stat-green .stat-sub { color: #a7f3d0; } +.stat-cyan .stat-sub { color: #bae6fd; } +.stat-gold .stat-sub { color: #fef08a; } + +/* Core Safety Workflow Dock */ +.summary-workflow-dock { + background: rgba(0, 0, 0, 0.45); + border: 1.5px solid rgba(245, 158, 11, 0.35); + border-radius: 14px; + padding: 10px 16px 12px 16px; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; +} + +.workflow-dock-title { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + justify-content: center; +} + +.dock-title-line { + flex: 1; + height: 1px; + background: linear-gradient(90deg, transparent 0%, rgba(245, 158, 11, 0.4) 100%); +} + +.dock-title-line:last-child { + background: linear-gradient(90deg, rgba(245, 158, 11, 0.4) 0%, transparent 100%); +} + +.dock-title-text { + font-family: var(--font-display); + font-size: 0.74rem; + font-weight: 800; + letter-spacing: 1.5px; + color: #facc15; + text-shadow: 0 0 8px rgba(250, 204, 21, 0.4); +} + +.workflow-steps-chain { display: flex; align-items: center; justify-content: center; @@ -1401,78 +2196,185 @@ html, body { gap: 8px; } -.mantra-step { - background: #162235; - padding: 5px 10px; - border-radius: var(--radius-sm); - font-size: 0.78rem; - color: #fff; - border: 1px solid var(--panel-border); +.workflow-step-pill { + display: inline-flex; + align-items: center; + gap: 6px; + background: #111a28; + border: 1px solid rgba(56, 189, 248, 0.25); + padding: 4px 12px; + border-radius: 8px; + font-family: var(--font-display); + font-size: 0.76rem; + font-weight: 800; + color: #e2e8f0; + letter-spacing: 0.5px; } -.mantra-arrow { - color: var(--safety-yellow); +.step-dot { + font-size: 0.75rem; +} + +.workflow-step-arrow { + color: #f59e0b; font-weight: 900; + font-size: 0.85rem; } -.scenarios-review-box { - background: rgba(0, 0, 0, 0.25); - border-radius: var(--radius-md); - padding: 14px; +/* Scenarios Review Summary Box */ +.summary-scenarios-review { + background: rgba(0, 0, 0, 0.55); + border: 1.5px solid rgba(245, 158, 11, 0.3); + border-radius: 14px; + padding: 12px 16px; + text-align: left; } -.scenarios-review-box h4 { - font-size: 0.9rem; - color: #fff; - margin-bottom: 10px; +.scenarios-review-heading { + font-family: var(--font-display); + font-size: 0.88rem; + font-weight: 800; + color: #ffffff; + margin-bottom: 8px; + letter-spacing: 0.5px; } -.review-row { +.summary-review-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.summary-review-row { display: flex; align-items: center; gap: 12px; - padding: 8px 0; - border-bottom: 1px solid var(--panel-border); + background: rgba(15, 23, 42, 0.6); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 10px; + padding: 8px 12px; } -.review-row:last-child { - border-bottom: none; +.review-icon-box { + width: 36px; + height: 36px; + min-width: 36px; + background: rgba(0, 0, 0, 0.6); + border: 1px solid rgba(245, 158, 11, 0.3); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.15rem; } -.rev-icon { - font-size: 1.3rem; -} - -.rev-info { +.review-text-content { flex: 1; } -.rev-info strong { +.review-scenario-name { + font-family: var(--font-display); font-size: 0.82rem; - color: #fff; + font-weight: 800; + color: #facc15; display: block; } -.rev-info p { - font-size: 0.75rem; - color: var(--text-muted); - line-height: 1.3; +.review-scenario-desc { + font-size: 0.73rem; + color: #cbd5e1; + line-height: 1.35; + margin: 2px 0 0 0; } -.rev-status { - font-family: var(--font-mono); - font-size: 0.72rem; - font-weight: 700; - color: var(--safety-green); - background: rgba(16, 185, 129, 0.15); - padding: 3px 8px; - border-radius: var(--radius-sm); +.review-passed-badge { + display: inline-flex; + align-items: center; + gap: 4px; + background: rgba(16, 185, 129, 0.18); + border: 1.5px solid #10b981; + color: #10b981; + font-family: var(--font-display); + font-size: 0.78rem; + font-weight: 900; + padding: 4px 12px; + border-radius: 8px; + box-shadow: 0 0 10px rgba(16, 185, 129, 0.25); + white-space: nowrap; } -.summary-actions { +.passed-check { + font-size: 0.85rem; +} + +/* Summary Bottom Action Buttons */ +.summary-action-buttons { display: flex; + align-items: center; justify-content: center; - gap: 14px; + gap: 16px; + margin-top: 4px; +} + +.btn-summary-replay { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + background: linear-gradient(180deg, #fbbf24 0%, #d97706 100%); + color: #000000; + border: none; + border-radius: 12px; + padding: 13px 32px; + font-family: var(--font-display); + font-size: 1.05rem; + font-weight: 900; + letter-spacing: 1px; + cursor: pointer; + box-shadow: + 0 0 22px rgba(245, 158, 11, 0.6), + 0 4px 12px rgba(0, 0, 0, 0.4); + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); + user-select: none; +} + +.btn-summary-replay:hover { + transform: translateY(-2px); + filter: brightness(1.12); + box-shadow: + 0 0 30px rgba(245, 158, 11, 0.85), + 0 6px 16px rgba(0, 0, 0, 0.5); +} + +.btn-summary-cert { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + background: #0d1624; + color: #ffffff; + border: 1.5px solid rgba(245, 158, 11, 0.5); + border-radius: 12px; + padding: 13px 32px; + font-family: var(--font-display); + font-size: 1.05rem; + font-weight: 800; + letter-spacing: 1px; + cursor: pointer; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); + user-select: none; +} + +.btn-summary-cert:hover { + border-color: #fbbf24; + color: #fbbf24; + transform: translateY(-2px); + box-shadow: 0 0 20px rgba(245, 158, 11, 0.4), 0 6px 16px rgba(0, 0, 0, 0.5); +} + +.summary-btn-icon { + font-size: 1.05rem; } /* ========================================================================== @@ -1574,7 +2476,90 @@ html, body { } /* ========================================================================== - PRINTABLE CERTIFICATE + CERTIFICATE VIEWER MODAL & PRINT STYLING (EXACT MATCH TO REFERENCE) + ========================================================================== */ + +.certificate-modal-card { + background: #0d1522; + border: 2px solid #f59e0b; + border-radius: 20px; + width: 95%; + max-width: 680px; + max-height: 92vh; + padding: 20px 24px; + box-shadow: 0 0 40px rgba(245, 158, 11, 0.4), 0 20px 60px rgba(0, 0, 0, 0.9); + display: flex; + flex-direction: column; + gap: 14px; + animation: popOverlay 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +.cert-modal-header-title { + display: flex; + align-items: center; + gap: 8px; +} + +.cert-modal-header-title h3 { + font-family: var(--font-display); + font-size: 1.25rem; + color: #fbbf24; + margin: 0; +} + +.cert-header-icon { + font-size: 1.3rem; +} + +.certificate-preview-container { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + overflow-y: auto; + max-height: calc(92vh - 160px); + padding: 4px; + background: rgba(0, 0, 0, 0.4); + border-radius: 12px; +} + +.certificate-visual-frame { + position: relative; + width: 100%; + max-width: 580px; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.8); +} + +.certificate-base-img { + width: 100%; + height: auto; + display: block; +} + +.certificate-modal-footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + padding-top: 4px; + flex-wrap: wrap; +} + +.cert-tip-hint { + font-size: 0.76rem; + color: var(--text-muted); + flex: 1; +} + +.cert-print-btn { + padding: 10px 24px; + font-size: 0.95rem; +} + +/* ========================================================================== + FULL PRINTABLE CERTIFICATE (PRINT & PDF OUTPUT) ========================================================================== */ .printable-certificate { @@ -1582,73 +2567,58 @@ html, body { } @media print { + @page { + size: A4 portrait; + margin: 0; + } + + html, body { + margin: 0 !important; + padding: 0 !important; + background: #000 !important; + height: 100% !important; + overflow: hidden !important; + } + body * { - visibility: hidden; + visibility: hidden !important; } - .printable-certificate, .printable-certificate * { - visibility: visible; + + #printable-certificate, #printable-certificate * { + visibility: visible !important; } - .printable-certificate { - display: block !important; - position: absolute; - left: 0; - top: 0; - width: 100%; - height: 100%; - background: #fff; - color: #000; - padding: 40px; + + #printable-certificate { + display: flex !important; + position: fixed !important; + left: 0 !important; + top: 0 !important; + width: 100vw !important; + height: 100vh !important; + align-items: center !important; + justify-content: center !important; + background: #000 !important; + z-index: 999999 !important; + margin: 0 !important; + padding: 0 !important; } - .cert-border { - border: 10px double #1e293b; - padding: 40px; - text-align: center; - height: 100%; - display: flex; - flex-direction: column; - justify-content: space-between; + + .print-cert-wrapper { + position: relative !important; + width: 100vw !important; + height: 100vh !important; + max-height: 100vh !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; } - .cert-logo { - font-size: 1.4rem; - font-weight: 800; - color: #b45309; - margin-bottom: 20px; - } - .cert-header h1 { - font-size: 2.2rem; - letter-spacing: 2px; - margin-bottom: 10px; - } - .cert-name { - font-size: 1.8rem; - color: #1e3a8a; - text-decoration: underline; - margin: 15px 0; - } - .cert-desc { - font-size: 1.1rem; - max-width: 600px; - margin: 0 auto; - line-height: 1.5; - } - .cert-footer { - display: flex; - justify-content: space-between; - align-items: flex-end; - margin-top: 50px; - } - .sig-line { - width: 200px; - border-bottom: 2px solid #000; - margin-bottom: 6px; - height: 30px; - } - .cert-seal { - border: 3px solid #b45309; - padding: 10px 20px; - border-radius: 50px; - font-weight: 800; - color: #b45309; + + .print-cert-bg { + width: 100% !important; + height: 100% !important; + object-fit: contain !important; + -webkit-print-color-adjust: exact !important; + print-color-adjust: exact !important; } } @@ -1673,4 +2643,180 @@ html, body { .scene-environment-container { min-height: 380px; } + .avatar-wrapper { + width: 270px; + height: 410px; + } +} + +/* ========================================================================== + CENTRAL SCREEN START COUNTDOWN OVERLAY (3, 2, 1, START!) + ========================================================================== */ + +.countdown-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(4, 9, 18, 0.78); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + z-index: 99999; + align-items: center; + justify-content: center; +} + +.countdown-overlay.active { + display: flex; + animation: countdownFadeIn 0.25s ease forwards; +} + +@keyframes countdownFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.countdown-hud-card { + position: relative; + width: 320px; + height: 320px; + background: radial-gradient(circle at center, rgba(14, 25, 42, 0.96) 0%, rgba(6, 12, 22, 0.98) 100%); + border: 2px solid rgba(56, 189, 248, 0.55); + border-radius: 28px; + box-shadow: + 0 0 35px rgba(56, 189, 248, 0.35), + inset 0 0 25px rgba(56, 189, 248, 0.12), + 0 25px 60px rgba(0, 0, 0, 0.9); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + padding: 20px; + box-sizing: border-box; +} + +.countdown-radar-ring { + position: absolute; + top: 50%; + left: 50%; + width: 240px; + height: 240px; + transform: translate(-50%, -50%); + border: 1.5px dashed rgba(56, 189, 248, 0.4); + border-radius: 50%; + pointer-events: none; + animation: radarRotate 8s linear infinite; +} + +@keyframes radarRotate { + from { transform: translate(-50%, -50%) rotate(0deg); } + to { transform: translate(-50%, -50%) rotate(360deg); } +} + +.countdown-corner { + position: absolute; + width: 14px; + height: 14px; + border-color: #38bdf8; + border-style: solid; + pointer-events: none; +} + +.countdown-corner.c-tl { + top: 10px; + left: 10px; + border-width: 3px 0 0 3px; + border-top-left-radius: 8px; +} + +.countdown-corner.c-tr { + top: 10px; + right: 10px; + border-width: 3px 3px 0 0; + border-top-right-radius: 8px; +} + +.countdown-corner.c-bl { + bottom: 10px; + left: 10px; + border-width: 0 0 3px 3px; + border-bottom-left-radius: 8px; +} + +.countdown-corner.c-br { + bottom: 10px; + right: 10px; + border-width: 0 3px 3px 0; + border-bottom-right-radius: 8px; +} + +.countdown-sub-header { + font-family: var(--font-display); + font-size: 0.72rem; + font-weight: 800; + letter-spacing: 2px; + color: #38bdf8; + text-transform: uppercase; + z-index: 2; +} + +.countdown-number-box { + width: 170px; + height: 170px; + display: flex; + align-items: center; + justify-content: center; + z-index: 2; +} + +.countdown-digit { + font-family: var(--font-display); + font-size: 6.5rem; + font-weight: 900; + line-height: 1; + color: var(--safety-yellow); + text-shadow: + 0 0 20px rgba(250, 204, 21, 0.8), + 0 0 45px rgba(250, 204, 21, 0.4); + transform: scale(1); + transition: all 0.15s ease-out; +} + +.countdown-digit.pop { + animation: countdownDigitPop 0.65s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +.countdown-digit.go-text { + font-size: 4.6rem; + color: #10b981; + text-shadow: + 0 0 25px rgba(16, 185, 129, 0.9), + 0 0 50px rgba(16, 185, 129, 0.5); +} + +@keyframes countdownDigitPop { + 0% { + transform: scale(0.4); + opacity: 0; + } + 50% { + transform: scale(1.15); + opacity: 1; + } + 100% { + transform: scale(1); + opacity: 1; + } +} + +.countdown-sub-tip { + font-size: 0.76rem; + font-weight: 700; + letter-spacing: 1.5px; + color: var(--text-muted); + text-transform: uppercase; + z-index: 2; } diff --git a/images/certificate_template.jpg b/images/certificate_template.jpg new file mode 100644 index 0000000..b621de8 Binary files /dev/null and b/images/certificate_template.jpg differ diff --git a/images/home_page_bg.png b/images/home_page_bg.png index b762dab..94ffb7f 100644 Binary files a/images/home_page_bg.png and b/images/home_page_bg.png differ diff --git a/images/safety_shield_logo.png b/images/safety_shield_logo.png new file mode 100644 index 0000000..b309f96 Binary files /dev/null and b/images/safety_shield_logo.png differ diff --git a/images/worker_hologram_silhouette.png b/images/worker_hologram_silhouette.png new file mode 100644 index 0000000..e44c5d3 Binary files /dev/null and b/images/worker_hologram_silhouette.png differ diff --git a/index.html b/index.html index 2e2e840..23c4c90 100644 --- a/index.html +++ b/index.html @@ -20,12 +20,8 @@
-
- - - - - +
+ Safety Shield Logo
PPE SAFETY CHALLENGE @@ -48,6 +44,13 @@
+ +
+ ⏱️ + TIME + 00:30 +
+
SAFETY SCORE 0 @@ -55,13 +58,16 @@ β˜†
- + + @@ -107,24 +113,9 @@
WORKER PROTECTION STATUS
- +
- - - - - - - - - - - - - - - - + Worker Hologram Silhouette
@@ -195,49 +186,135 @@
- +
-
-
- ⚠️ STOP! CRITICAL HAZARDS DETECTED +
+ +
+ + + +
-

Critical Protection Gaps Detected

-

- Multiple required protective gear items are missing or improper for this task. Unprotected body zones are exposed to severe workplace trauma. -

- -
-
- ⚠️ ALL REQUIRED CORRECTIONS (0 ISSUES): -
-
- +
+ + + + +
+ + +
+
+
+
+ + +
+
+ + + + + +
- + +
+ +
+ ⚠️ + STOP! 5 SAFETY HAZARDS DETECTED +
+ +

+ CRITICAL PROTECTION GAPS DETECTED +

+ +

+ Operating without complete PPE exposes multiple body zones to direct, severe workplace injuries. All required safety equipment must be equipped before starting. +

+ +
+
+ ⚠️ + ALL REQUIRED CORRECTIONS (0 ISSUES): +
+
+ +
+
+ + +
- +
-
-
- βœ… - SAFE TO START +
+ +
+ + + +
-

- Correct PPE selected. Your eyes, face, hands, hearing and feet are fully protected against all task hazards. -

-
- ⭐ - +100 Safety Points +
+ + + + +
+ + +
+
+
+
+ + +
+
+ + + + + +
+
+ + + +
+
+ + +
+

+ SAFE TO START +

+
+ +

+ Your eyes, face, hands, hearing and feet are fully protected from all hazards of this grinding task. +

+ +
+ β˜… + +100 Safety Points + β˜… +
+ +
-
@@ -276,76 +353,184 @@
- +
-
-
-
πŸ†
-

PPE SAFETY CHALLENGE COMPLETE

-

β€œChoose Right. Work Safe.”

+
+ +
+ + + + +
+
+ + + +
-
-
- FINAL SAFETY SCORE - 200 / 200 - 100% Safety Compliance -
-
- SCENARIOS COMPLETED - 2 / 2 - Zero Workplace Accidents -
-
- SAFETY STARS - ⭐⭐ - Master Safety Practitioner + +
+
+
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+

+ PPE SAFETY CHALLENGE COMPLETE +

+

β€œChoose Right. Work Safe.”

- -
-
CORE SAFETY WORKFLOW
-
-
πŸ›‘ STOP
-
βž”
-
πŸ‘οΈ LOOK
-
βž”
-
⚠️ IDENTIFY HAZARDS
-
βž”
-
🦺 SELECT PPE
-
βž”
-
πŸ›‘οΈ WORK SAFELY
-
-
- - -
-

Safety Assessment Summary

-
-
⚑
-
- Scenario 1: Angle Grinding Operation -

Protected against flying hot sparks, high decibel noise (95+ dB), and sharp metal burrs using Full Face Shield + Glasses, Leather Grinding Gloves, Hearing Protection & Safety Boots.

+ +
+ +
+
+ + + +
-
βœ… PASSED
+
+ FINAL SAFETY SCORE + 200 / 200 + 100% Safety Compliance Rating +
-
-
πŸ§ͺ
-
- Scenario 2: Industrial Chemical Transfer -

Prevented chemical burn & splash injury using Chemical Splash Goggles, Heavy Nitrile Gauntlet Gloves, Chemical Resistant Apron & Rubber Safety Boots.

+ + +
+
+ + + + + + + +
+
+ SCENARIOS COMPLETED + 2 / 2 + Zero Workplace Accidents +
+
+ + +
+
+ + + + +
+
+ SAFETY STARS + β˜…β˜… + Master Safety Practitioner (Zero Accidents)
-
βœ… PASSED
-
- -
@@ -353,6 +538,23 @@ + +
+
+
+
+
+
+
+ +
SCENARIO INITIALIZING
+
+ 3 +
+
PREPARE YOUR PPE
+
+
+ - + + + +
-
-
- -

CERTIFICATE OF SAFETY EXCELLENCE

-

This certifies that

-

QUALIFIED INDUSTRIAL EMPLOYEE

-

has successfully demonstrated 100% competency in Hazard Identification & Personal Protective Equipment (PPE) Selection across all high-risk industrial scenarios.

-
- +
diff --git a/js/audio.js b/js/audio.js index 86c47e6..2781601 100644 --- a/js/audio.js +++ b/js/audio.js @@ -25,6 +25,22 @@ class SoundEngine { this.activeGrinderNode = null; this.activeLiquidNode = null; + // Dedicated Countdown Tick Sound Clip (User uploaded custom audio) + try { + this.countdownTickAudio = new Audio('audio/countdown_tick.mp3'); + this.countdownTickAudio.preload = 'auto'; + } catch (e) { + this.countdownTickAudio = null; + } + + // Dedicated Top Timer Last 10-Second Warning Sound Clip (User uploaded custom audio) + try { + this.timerLast10sAudio = new Audio('audio/timer_last_10s.wav'); + this.timerLast10sAudio.preload = 'auto'; + } catch (e) { + this.timerLast10sAudio = null; + } + this.isInitialized = false; } @@ -91,6 +107,12 @@ class SoundEngine { if (this.masterGain && this.ctx) { this.masterGain.gain.setTargetAtTime(this.isMuted ? 0 : 1.0, this.ctx.currentTime, 0.05); } + if (this.countdownTickAudio) { + this.countdownTickAudio.volume = this.isMuted ? 0 : this.sfxVolume; + } + if (this.timerLast10sAudio) { + this.timerLast10sAudio.volume = this.isMuted ? 0 : this.sfxVolume; + } } setMusicVolume(val) { @@ -105,6 +127,94 @@ class SoundEngine { if (this.sfxGain && this.ctx) { this.sfxGain.gain.setTargetAtTime(this.sfxVolume, this.ctx.currentTime, 0.05); } + if (this.countdownTickAudio) { + this.countdownTickAudio.volume = this.isMuted ? 0 : this.sfxVolume; + } + if (this.timerLast10sAudio) { + this.timerLast10sAudio.volume = this.isMuted ? 0 : this.sfxVolume; + } + } + + playCountdownTickAudio() { + this.ensureContext(); + if (this.isMuted) return; + + if (this.countdownTickAudio) { + try { + const sound = this.countdownTickAudio.cloneNode(); + sound.volume = Math.max(0, Math.min(1, this.sfxVolume)); + sound.play().catch(err => { + console.warn("Countdown tick audio playback note:", err); + }); + } catch (e) { + try { + this.countdownTickAudio.currentTime = 0; + this.countdownTickAudio.volume = Math.max(0, Math.min(1, this.sfxVolume)); + this.countdownTickAudio.play().catch(() => {}); + } catch (err) {} + } + } + } + + /** + * Normal Clock Tick for the first 20 seconds (30s down to 11s) + */ + playTimerTick() { + this.ensureContext(); + if (!this.ctx || this.isMuted) return; + + const t = this.ctx.currentTime; + const osc = this.ctx.createOscillator(); + const gain = this.ctx.createGain(); + const filter = this.ctx.createBiquadFilter(); + + // Crisp high-tech clock tick matching 1st beat + osc.type = 'triangle'; + osc.frequency.setValueAtTime(1100, t); + osc.frequency.exponentialRampToValueAtTime(320, t + 0.035); + + filter.type = 'bandpass'; + filter.frequency.setValueAtTime(1200, t); + filter.Q.setValueAtTime(3.5, t); + + gain.gain.setValueAtTime(0.22, t); + gain.gain.exponentialRampToValueAtTime(0.001, t + 0.045); + + osc.connect(filter); + filter.connect(gain); + gain.connect(this.sfxGain); + + osc.start(t); + osc.stop(t + 0.05); + } + + /** + * Dedicated Last 10 Seconds Audio Track (User uploaded custom audio) + */ + playTimerLast10sAudio() { + this.ensureContext(); + if (this.isMuted) return; + + if (this.timerLast10sAudio) { + try { + this.timerLast10sAudio.currentTime = 0; + this.timerLast10sAudio.volume = Math.max(0, Math.min(1, this.sfxVolume)); + this.timerLast10sAudio.play().catch(err => { + console.warn("Timer last 10s audio playback note:", err); + }); + } catch (e) { + console.warn("Error playing timer last 10s audio:", e); + } + } + } + + stopTimerLast10sAudio() { + if (this.timerLast10sAudio) { + try { + this.timerLast10sAudio.pause(); + this.timerLast10sAudio.currentTime = 0; + } catch (e) {} + } } setAmbientEnabled(enabled) { @@ -520,6 +630,155 @@ class SoundEngine { }); } + /** + * Timer Warning Tick & Urgent Countdown Beep SFX + */ + playTimerWarningTick(isUrgent = false) { + this.ensureContext(); + if (!this.ctx || this.isMuted) return; + + const t = this.ctx.currentTime; + const osc = this.ctx.createOscillator(); + const gain = this.ctx.createGain(); + const filter = this.ctx.createBiquadFilter(); + + if (isUrgent) { + // Rapid, tense dual-tone hazard emergency beep + osc.type = 'sawtooth'; + osc.frequency.setValueAtTime(880, t); + osc.frequency.setValueAtTime(700, t + 0.06); + + filter.type = 'bandpass'; + filter.frequency.setValueAtTime(1400, t); + filter.Q.setValueAtTime(4.0, t); + + gain.gain.setValueAtTime(0.22, t); + gain.gain.exponentialRampToValueAtTime(0.001, t + 0.14); + + osc.connect(filter); + filter.connect(gain); + gain.connect(this.sfxGain); + + osc.start(t); + osc.stop(t + 0.15); + } else { + // High-tech warning clock tick + osc.type = 'sine'; + osc.frequency.setValueAtTime(560, t); + osc.frequency.exponentialRampToValueAtTime(420, t + 0.05); + + gain.gain.setValueAtTime(0.12, t); + gain.gain.exponentialRampToValueAtTime(0.001, t + 0.08); + + osc.connect(gain); + gain.connect(this.sfxGain); + + osc.start(t); + osc.stop(t + 0.09); + } + } + + /** + * Industrial Emergency Hazard Alarm Sound (Time Expired / Hazard Warning) + */ + playTimerHazardAlarm() { + this.ensureContext(); + if (!this.ctx || this.isMuted) return; + + const t = this.ctx.currentTime; + // Industrial emergency hazard klaxon / buzzer burst + [0, 0.18, 0.36].forEach(offset => { + const time = t + offset; + const osc = this.ctx.createOscillator(); + const gain = this.ctx.createGain(); + const filter = this.ctx.createBiquadFilter(); + + osc.type = 'sawtooth'; + osc.frequency.setValueAtTime(620, time); + osc.frequency.linearRampToValueAtTime(380, time + 0.14); + + filter.type = 'lowpass'; + filter.frequency.setValueAtTime(2000, time); + + gain.gain.setValueAtTime(0.26, time); + gain.gain.exponentialRampToValueAtTime(0.001, time + 0.15); + + osc.connect(filter); + filter.connect(gain); + gain.connect(this.sfxGain); + + osc.start(time); + osc.stop(time + 0.16); + }); + } + + /** + * Central Screen Start Countdown Beep (3, 2, 1) + */ + playCountdownBeep(num) { + this.ensureContext(); + if (!this.ctx || this.isMuted) return; + + const t = this.ctx.currentTime; + const osc = this.ctx.createOscillator(); + const osc2 = this.ctx.createOscillator(); + const gain = this.ctx.createGain(); + const filter = this.ctx.createBiquadFilter(); + + // Pitch rises: 3 (520Hz) -> 2 (680Hz) -> 1 (880Hz) + const baseFreq = num === 3 ? 520 : (num === 2 ? 680 : 880); + + osc.type = 'triangle'; + osc.frequency.setValueAtTime(baseFreq, t); + + osc2.type = 'sine'; + osc2.frequency.setValueAtTime(baseFreq * 2, t); + + filter.type = 'lowpass'; + filter.frequency.setValueAtTime(2400, t); + + gain.gain.setValueAtTime(0.24, t); + gain.gain.exponentialRampToValueAtTime(0.001, t + 0.28); + + osc.connect(filter); + osc2.connect(filter); + filter.connect(gain); + gain.connect(this.sfxGain); + + osc.start(t); + osc2.start(t); + osc.stop(t + 0.3); + osc2.stop(t + 0.3); + } + + /** + * Central Screen Countdown Launch / GO! Chime + */ + playCountdownGo() { + this.ensureContext(); + if (!this.ctx || this.isMuted) return; + + const t = this.ctx.currentTime; + // Energetic ascending triad (C5 -> E5 -> G5) + [523.25, 659.25, 783.99, 1046.50].forEach((freq, idx) => { + const time = t + (idx * 0.05); + const osc = this.ctx.createOscillator(); + const gain = this.ctx.createGain(); + + osc.type = 'sine'; + osc.frequency.setValueAtTime(freq, time); + + gain.gain.setValueAtTime(0.22, time); + gain.gain.exponentialRampToValueAtTime(0.001, time + 0.45); + + osc.connect(gain); + gain.connect(this.sfxGain); + + osc.start(time); + osc.stop(time + 0.5); + }); + } + playCriticalStopAlert() { this.ensureContext(); if (!this.ctx || this.isMuted) return; diff --git a/js/game.js b/js/game.js index af70dae..dc6fa3b 100644 --- a/js/game.js +++ b/js/game.js @@ -15,6 +15,10 @@ class SafetyGame { this.avatar = null; this.particles = null; + // 30-Second Countdown Timer State + this.timerSeconds = 30; + this.timerInterval = null; + // DOM Elements Cache this.dom = { screenStart: document.getElementById('screen-start'), @@ -34,10 +38,16 @@ class SafetyGame { // HUD hudScore: document.getElementById('hud-score'), hudStars: document.getElementById('hud-stars'), + hudTimerBox: document.getElementById('hud-timer-box'), + hudTimerVal: document.getElementById('hud-timer-val'), + countdownOverlay: document.getElementById('countdown-overlay'), + countdownDigit: document.getElementById('countdown-digit'), + countdownSubTip: document.getElementById('countdown-sub-tip'), stepPill1: document.getElementById('step-pill-1'), stepPill2: document.getElementById('step-pill-2'), btnSoundToggle: document.getElementById('btn-sound-toggle'), btnMusicToggle: document.getElementById('btn-music-toggle'), + btnFullscreenToggle: document.getElementById('btn-fullscreen-toggle'), btnAudioSettings: document.getElementById('btn-audio-settings'), // Scene Info & Hazards @@ -104,14 +114,15 @@ class SafetyGame { } bindEvents() { - // Start Game from Intro (Instantaneous Transition, Zero Delay) + // Start Game from Intro (Instantaneous Transition with 3,2,1,GO Central Countdown) if (this.dom.btnStartGame) { this.dom.btnStartGame.addEventListener('click', () => { window.soundEngine.ensureContext(); window.soundEngine.playStartIntroSting(); window.soundEngine.startBackgroundMusic(); - this.loadScenario(0); + this.loadScenario(0, false); this.switchScreen('game'); + this.triggerStartCountdown(() => this.startTimer()); }); } @@ -146,7 +157,8 @@ class SafetyGame { this.particles.stopMode(); if (this.currentScenarioIndex < SCENARIO_DATA.length - 1) { - this.loadScenario(this.currentScenarioIndex + 1); + this.loadScenario(this.currentScenarioIndex + 1, false); + this.triggerStartCountdown(() => this.startTimer()); } else { this.showFinalSummary(); } @@ -161,17 +173,31 @@ class SafetyGame { this.scenarioCompleted = [false, false]; this.switchScreen('game'); window.soundEngine.startBackgroundMusic(); - this.loadScenario(0); + this.loadScenario(0, false); + this.triggerStartCountdown(() => this.startTimer()); }); } - // Print Certificate + // Certificate Viewer Modal & Print Handlers 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.soundEngine.playButtonClick(); + const certModal = document.getElementById('modal-certificate'); + if (certModal) certModal.classList.add('active'); + }); + } + + const btnCloseCert = document.getElementById('btn-close-cert-modal'); + if (btnCloseCert) { + btnCloseCert.addEventListener('click', () => { + const certModal = document.getElementById('modal-certificate'); + if (certModal) certModal.classList.remove('active'); + }); + } + + const btnPrintAction = document.getElementById('btn-print-cert-action'); + if (btnPrintAction) { + btnPrintAction.addEventListener('click', () => { window.print(); }); } @@ -243,14 +269,31 @@ class SafetyGame { }); } - if (this.dom.toggleAmbientHum) { - this.dom.toggleAmbientHum.addEventListener('change', (e) => { - window.soundEngine.setAmbientEnabled(e.target.checked); + // Fullscreen Mode Toggle + if (this.dom.btnFullscreenToggle) { + this.dom.btnFullscreenToggle.addEventListener('click', () => { + this.toggleFullscreen(); }); } - // Keyboard Shortcuts (1-9 and 0 for 10 Locker items, Space for Start Task, Esc to close modals) + const onFullscreenChange = () => { + const isFull = !!(document.fullscreenElement || document.webkitFullscreenElement); + document.body.classList.toggle('fullscreen-mode', isFull); + document.documentElement.classList.toggle('fullscreen-mode', isFull); + this.updateFullscreenIcon(); + }; + document.addEventListener('fullscreenchange', onFullscreenChange); + document.addEventListener('webkitfullscreenchange', onFullscreenChange); + document.addEventListener('mozfullscreenchange', onFullscreenChange); + document.addEventListener('MSFullscreenChange', onFullscreenChange); + + // Keyboard Shortcuts (1-9 and 0 for 10 Locker items, Space for Start Task, F for Fullscreen, Esc to close modals) window.addEventListener('keydown', (e) => { + // Don't trigger game shortcuts if typing in an editable field + if (e.target && (e.target.tagName === 'INPUT' || e.target.isContentEditable)) { + return; + } + const num = parseInt(e.key); if (!isNaN(num)) { const cards = document.querySelectorAll('.ppe-item-card'); @@ -260,6 +303,12 @@ class SafetyGame { } } + if ((e.key === 'f' || e.key === 'F') && !e.ctrlKey && !e.altKey && !e.metaKey) { + e.preventDefault(); + this.toggleFullscreen(); + return; + } + if ((e.code === 'Space' || e.code === 'Enter') && this.dom.screenStart.classList.contains('active')) { e.preventDefault(); if (this.dom.btnStartGame) this.dom.btnStartGame.click(); @@ -276,6 +325,8 @@ class SafetyGame { if (e.key === 'Escape') { if (this.dom.modalAudio && this.dom.modalAudio.classList.contains('active')) { this.dom.modalAudio.classList.remove('active'); + } else if (document.getElementById('modal-certificate')?.classList.contains('active')) { + document.getElementById('modal-certificate').classList.remove('active'); } else if (this.dom.consequenceOverlay.classList.contains('active')) { this.dismissConsequenceModal(); } @@ -283,17 +334,206 @@ class SafetyGame { }); } + toggleFullscreen() { + window.soundEngine.playButtonClick(); + if (!document.fullscreenElement && !document.webkitFullscreenElement) { + const elem = document.documentElement; + if (elem.requestFullscreen) { + elem.requestFullscreen().catch(() => {}); + } else if (elem.webkitRequestFullscreen) { + elem.webkitRequestFullscreen(); + } + } else { + if (document.exitFullscreen) { + document.exitFullscreen().catch(() => {}); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } + } + } + + updateFullscreenIcon() { + const isFull = !!(document.fullscreenElement || document.webkitFullscreenElement); + const iconSpan = document.getElementById('fullscreen-icon'); + if (iconSpan) { + iconSpan.textContent = isFull ? '‑' : 'β›Ά'; + } + if (this.dom.btnFullscreenToggle) { + this.dom.btnFullscreenToggle.title = isFull ? 'Exit Fullscreen (F)' : 'Toggle Fullscreen (F)'; + if (isFull) { + this.dom.btnFullscreenToggle.classList.add('fullscreen-active'); + } else { + this.dom.btnFullscreenToggle.classList.remove('fullscreen-active'); + } + } + } + switchScreen(screenName) { this.dom.screenStart.classList.remove('active'); this.dom.screenGame.classList.remove('active'); this.dom.screenSummary.classList.remove('active'); - if (screenName === 'start') this.dom.screenStart.classList.add('active'); + if (screenName === 'start') { + this.stopTimer(); + this.dom.screenStart.classList.add('active'); + } if (screenName === 'game') this.dom.screenGame.classList.add('active'); - if (screenName === 'summary') this.dom.screenSummary.classList.add('active'); + if (screenName === 'summary') { + this.stopTimer(); + this.dom.screenSummary.classList.add('active'); + } } - loadScenario(index) { + // ========================================================================= + // 30-SECOND COUNTDOWN TIMER CONTROLLER + // ========================================================================= + + startTimer() { + this.stopTimer(); + this.timerSeconds = 30; + this.updateTimerDisplay(); + + this.timerInterval = setInterval(() => { + if (this.timerSeconds > 0) { + this.timerSeconds--; + this.updateTimerDisplay(); + + if (this.timerSeconds > 10) { + // Normal clock tick sound from 30s to 11s (first 20 seconds) + window.soundEngine.playTimerTick(); + } else if (this.timerSeconds === 10) { + // Trigger dedicated audio track for the final 10 seconds + window.soundEngine.playTimerLast10sAudio(); + } + } else { + this.stopTimer(); + // Time expired: execute safety check + if (this.dom.screenGame.classList.contains('active') && + !this.dom.consequenceOverlay.classList.contains('active') && + !this.dom.successOverlay.classList.contains('active')) { + this.executeSafetyTest(); + } + } + }, 1000); + } + + stopTimer() { + if (this.timerInterval) { + clearInterval(this.timerInterval); + this.timerInterval = null; + } + window.soundEngine.stopTimerLast10sAudio(); + } + + pauseTimer() { + this.stopTimer(); + } + + resumeTimer() { + if (this.timerSeconds > 0 && !this.timerInterval) { + this.timerInterval = setInterval(() => { + if (this.timerSeconds > 0) { + this.timerSeconds--; + this.updateTimerDisplay(); + + if (this.timerSeconds > 10) { + window.soundEngine.playTimerTick(); + } else if (this.timerSeconds === 10) { + window.soundEngine.playTimerLast10sAudio(); + } + } else { + this.stopTimer(); + if (this.dom.screenGame.classList.contains('active') && + !this.dom.consequenceOverlay.classList.contains('active') && + !this.dom.successOverlay.classList.contains('active')) { + this.executeSafetyTest(); + } + } + }, 1000); + } + } + + updateTimerDisplay() { + if (!this.dom.hudTimerVal) return; + const formatted = `00:${this.timerSeconds.toString().padStart(2, '0')}`; + this.dom.hudTimerVal.textContent = formatted; + + if (this.dom.hudTimerBox) { + this.dom.hudTimerBox.classList.remove('timer-warning', 'timer-danger'); + if (this.timerSeconds <= 5) { + this.dom.hudTimerBox.classList.add('timer-danger'); + } else if (this.timerSeconds <= 10) { + this.dom.hudTimerBox.classList.add('timer-warning'); + } + } + } + + // ========================================================================= + // CENTRAL SCREEN START COUNTDOWN (3, 2, 1, GO!) + // ========================================================================= + + triggerStartCountdown(onComplete) { + if (!this.dom.countdownOverlay) { + if (onComplete) onComplete(); + return; + } + + this.stopTimer(); + this.timerSeconds = 30; + this.updateTimerDisplay(); + + this.dom.countdownOverlay.classList.add('active'); + const digitEl = this.dom.countdownDigit; + const subTipEl = this.dom.countdownSubTip; + + const steps = [ + { text: '3', tip: 'PREPARE YOUR PPE', isGo: false, duration: 1000 }, + { text: '2', tip: 'IDENTIFY HAZARDS', isGo: false, duration: 1000 }, + { text: '1', tip: 'EQUIP WORKER', isGo: false, duration: 1000 }, + { text: 'GO!', tip: '30s TIMER STARTED', isGo: true, duration: 750 } + ]; + + let currentStep = 0; + + const playNextStep = () => { + if (currentStep < steps.length) { + const item = steps[currentStep]; + + if (digitEl) { + digitEl.textContent = item.text; + digitEl.className = 'countdown-digit'; + if (item.isGo) { + digitEl.classList.add('go-text'); + } + // Force CSS reflow to re-trigger pop animation + void digitEl.offsetWidth; + digitEl.classList.add('pop'); + } + + if (subTipEl) { + subTipEl.textContent = item.tip; + } + + // Play uploaded countdown tick audio for 3, 2, 1 every second + if (item.isGo) { + window.soundEngine.playCountdownGo(); + } else { + window.soundEngine.playCountdownTickAudio(); + } + + currentStep++; + setTimeout(playNextStep, item.duration); + } else { + // Complete countdown & fade out + this.dom.countdownOverlay.classList.remove('active'); + if (onComplete) onComplete(); + } + }; + + playNextStep(); + } + + loadScenario(index, autoStartTimer = true) { window.soundEngine.stopAllTaskSFX(); this.currentScenarioIndex = index; this.currentScenario = SCENARIO_DATA[index]; @@ -303,6 +543,14 @@ class SafetyGame { this.particles.clear(); this.particles.stopMode(); + if (autoStartTimer) { + this.startTimer(); + } else { + this.stopTimer(); + this.timerSeconds = 30; + this.updateTimerDisplay(); + } + // Update Stepper HUD if (this.dom.stepPill1 && this.dom.stepPill2) { this.dom.stepPill1.className = index === 0 ? 'step-pill active' : (this.scenarioCompleted[0] ? 'step-pill completed' : 'step-pill'); @@ -483,9 +731,6 @@ class SafetyGame { return `
-
- ${isEquipped ? 'βœ“' : ''} -
${iconContent}
${item.name}
@@ -610,91 +855,63 @@ class SafetyGame { } handleSafeSuccess() { - // Start Task Simulation SFX & Particles - if (this.currentScenario.id === 'grinding') { - window.soundEngine.startGrinderSFX(); - this.particles.startMode('sparks'); - } else { - window.soundEngine.startLiquidTransferSFX(); - this.particles.startMode('chemical'); + this.stopTimer(); + this.particles.clear(); + this.particles.stopMode(); + window.soundEngine.stopAllTaskSFX(); + + window.soundEngine.playSuccessChime(); + window.soundEngine.playSafetyStarSound(); + + // Award points (100 on first try, 80 on retry) + const points = this.scenarioFirstAttempt ? 100 : 80; + this.score += points; + this.scenarioCompleted[this.currentScenarioIndex] = true; + this.updateHUD(); + + // Show Success Modal Overlay + if (this.dom.successTitle) { + if (this.currentScenario.id === 'grinding') { + this.dom.successTitle.innerHTML = `SAFE TO START`; + } else { + this.dom.successTitle.innerHTML = `SAFE TO PROCEED`; + } + } + 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.dom.btnNextScenarioText) { + if (this.currentScenarioIndex === SCENARIO_DATA.length - 1) { + this.dom.btnNextScenarioText.textContent = 'VIEW FINAL RESULTS βž”'; + } else { + this.dom.btnNextScenarioText.textContent = 'NEXT SCENARIO βž”'; + } } - // Play Success Chime & Star sound after short operational run - setTimeout(() => { - window.soundEngine.stopAllTaskSFX(); - this.particles.stopMode(); - - window.soundEngine.playSuccessChime(); - window.soundEngine.playSafetyStarSound(); - - // Award points (100 on first try, 80 on retry) - const points = this.scenarioFirstAttempt ? 100 : 80; - this.score += points; - this.scenarioCompleted[this.currentScenarioIndex] = true; - this.updateHUD(); - - // Show Success Modal Overlay - 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.dom.btnNextScenarioText) { - if (this.currentScenarioIndex === SCENARIO_DATA.length - 1) { - this.dom.btnNextScenarioText.textContent = 'VIEW FINAL RESULTS βž”'; - } else { - this.dom.btnNextScenarioText.textContent = 'NEXT SCENARIO βž”'; - } - } - - if (this.dom.successOverlay) this.dom.successOverlay.classList.add('active'); - }, 1200); + if (this.dom.successOverlay) this.dom.successOverlay.classList.add('active'); } handleUnsafeConsequence(allMissingChecks, allEquippedDistractors) { + this.stopTimer(); this.scenarioFirstAttempt = false; + this.particles.clear(); + this.particles.stopMode(); - // Start task machine momentarily - if (this.currentScenario.id === 'grinding') { - window.soundEngine.startGrinderSFX(); - this.particles.startMode('sparks'); - } else { - window.soundEngine.startLiquidTransferSFX(); - this.particles.startMode('chemical'); - } - - // Determine target position for incoming projectile - const avatarCoords = this.avatar.getAvatarCenterCoordinates(); - 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; - - const hasEyeFaceIssue = allMissingChecks.some(chk => chk.slot === 'eyes' || chk.slot === 'head'); - const targetX = hasEyeFaceIssue ? avatarCoords.eyeX : avatarCoords.handX; - const targetY = hasEyeFaceIssue ? avatarCoords.eyeY : avatarCoords.handY; - - setTimeout(() => { - window.soundEngine.playWhooshImpact(); - this.particles.spawnHazardProjectile( - this.currentScenario.id === 'grinding' ? 'spark' : 'chemical_drop', - originX, originY, targetX, targetY - ); - }, 400); - - // Store consequence context for freeze overlay this.activeConsequenceData = { allMissingChecks, allEquippedDistractors }; + this.handleFreezeFrameAlert(); } handleFreezeFrameAlert(projectile) { - // 1. Freeze particle engine & stop machine motors - this.particles.setFrozen(true); + this.particles.clear(); + this.particles.setFrozen(false); + this.particles.stopMode(); window.soundEngine.stopGrinderSFX(); window.soundEngine.stopLiquidTransferSFX(); - // 2. Duck background music + // Duck background music window.soundEngine.duckMusic(true); - // 3. Play warning tones + // Play warning tones window.soundEngine.playWarningTone(); window.soundEngine.playCriticalStopAlert(); @@ -712,22 +929,25 @@ class SafetyGame { // 4. Update Header & Badges if (this.dom.hazardFlasherBadge) { - this.dom.hazardFlasherBadge.textContent = `⚠️ STOP! ${totalIssues} SAFETY ${totalIssues === 1 ? 'HAZARD' : 'HAZARDS'} DETECTED`; + this.dom.hazardFlasherBadge.innerHTML = ` + ⚠️ + 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]; - if (this.dom.consequenceTitle) this.dom.consequenceTitle.textContent = "Incorrect / Ineffective PPE Detected"; + if (this.dom.consequenceTitle) this.dom.consequenceTitle.innerHTML = `INCORRECT PPE DETECTED`; if (this.dom.consequenceDesc) this.dom.consequenceDesc.textContent = d.rejectReason; } else { const m = allMissingChecks[0]; - if (this.dom.consequenceTitle) this.dom.consequenceTitle.textContent = m.consequenceTitle; + if (this.dom.consequenceTitle) this.dom.consequenceTitle.innerHTML = `CRITICAL HAZARD DETECTED`; if (this.dom.consequenceDesc) this.dom.consequenceDesc.textContent = m.consequenceDesc; } } else { - if (this.dom.consequenceTitle) this.dom.consequenceTitle.textContent = "Critical Protection Gaps Detected"; + if (this.dom.consequenceTitle) this.dom.consequenceTitle.innerHTML = `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."; } @@ -735,31 +955,49 @@ class SafetyGame { let cardsHtml = ''; allMissingChecks.forEach(chk => { - const icon = slotIcons[chk.slot] || '⚠️'; + const reqItem = this.currentScenario.lockerItems.find(it => it.id === chk.requiredItem); + const iconImgHtml = reqItem && reqItem.iconImg + ? `${reqItem.name}` + : `${slotIcons[chk.slot] || '⚠️'}`; + + const slotCap = chk.slot ? chk.slot.charAt(0).toUpperCase() + chk.slot.slice(1) : 'Body'; + cardsHtml += ` -
-
${icon}
-
-
- ${chk.missingLabel} - ${chk.slot} - Missing +
+
+ ${iconImgHtml} +
+
+
+ ${chk.missingLabel || (reqItem ? reqItem.name : 'Missing PPE')} + + ! ${slotCap} - Missing +
- ${chk.consequenceDesc} +

${chk.consequenceDesc}

`; }); allEquippedDistractors.forEach(dist => { + const iconImgHtml = dist.iconImg + ? `${dist.name}` + : `🚫`; + cardsHtml += ` -
-
🚫
-
-
- REMOVE: ${dist.name} - Unsafe Gear +
+
+ ${iconImgHtml} +
+
+
+ REMOVE: ${dist.name} + + ! Unsafe Gear +
- ${dist.rejectReason} +

${dist.rejectReason}

`; @@ -801,6 +1039,13 @@ class SafetyGame { const rows = this.dom.coverageGrid.querySelectorAll('.status-row-item'); rows.forEach(row => row.classList.remove('alert-missing')); } + + if (this.timerSeconds <= 0) { + this.timerSeconds = 15; // Extra 15s to fix PPE + this.resumeTimer(); + } else { + this.resumeTimer(); + } } // ========================================================================= diff --git a/js/particles.js b/js/particles.js index fbe0fb7..ca8f125 100644 --- a/js/particles.js +++ b/js/particles.js @@ -52,62 +52,17 @@ class ParticleEngine { this.clear(); } - // Spawn near-miss hazardous particle / projectile flying at avatar + // Particle Spawning (Disabled for clean direct feedback) spawnHazardProjectile(type, startX, startY, targetX, targetY) { - const dx = targetX - startX; - const dy = targetY - startY; - const dist = Math.sqrt(dx * dx + dy * dy); - const speed = 14; - - this.projectiles.push({ - type: type, // 'spark' or 'chemical_drop' - x: startX, - y: startY, - vx: (dx / dist) * speed, - vy: (dy / dist) * speed, - targetX: targetX, - targetY: targetY, - size: type === 'spark' ? 7 : 12, - color: type === 'spark' ? '#ff3b30' : '#00f0ff', - trail: [], - reached: false - }); + // Disabled } spawnSparks(originX, originY, count = 12) { - for (let i = 0; i < count; i++) { - const angle = (Math.PI * 0.8) + (Math.random() * 0.9 - 0.45); // Directed left-upward arc - const speed = Math.random() * 12 + 6; - this.particles.push({ - type: 'spark', - x: originX, - y: originY, - vx: Math.cos(angle) * speed, - vy: Math.sin(angle) * speed - (Math.random() * 4), - gravity: 0.35, - life: 1.0, - decay: Math.random() * 0.03 + 0.02, - size: Math.random() * 3 + 1.5, - color: Math.random() > 0.3 ? '#ffdd55' : '#ff5511' - }); - } + // Disabled } spawnChemicalFlow(originX, originY) { - for (let i = 0; i < 4; i++) { - this.particles.push({ - type: 'chemical', - x: originX + (Math.random() * 8 - 4), - y: originY, - vx: -(Math.random() * 2 + 1), - vy: Math.random() * 4 + 2, - gravity: 0.25, - life: 1.0, - decay: 0.02, - size: Math.random() * 4 + 3, - color: '#10b981' - }); - } + // Disabled } spawnConfetti() {