1;(function (window) { 2 'use strict'; 3 4 const STAGES = (window.COHBG_STAGES = window.COHBG_STAGES || {}); 5 6 // Desktop: 8 rows x 14 columns = 112 cards 7 const GRID_ROWS_DESKTOP = 8; 8 const GRID_COLS_DESKTOP = 14; 9 10 // Mobile: 9 rows x 7 columns = 63 cards 11 const GRID_ROWS_MOBILE = 9; 12 const GRID_COLS_MOBILE = 7; 13 14 const TOTAL_SEC = 30; 15 16 // phases by time (seconds from start) 17 // 0â6 : normal mode 18 // 6â11 : blur + click block (5 c) 19 // 11â13 : black screen (2 c) 20 // 13â18 : normal (5 c) 21 // 18â23 : blur (5 c) 22 // 23â25 : black (2 c) 23 // 25â30 : normal (5 c) 24 // >=30 : timeout 25 26 const AUTO_SUCCESS_DELAY_MS = 1000; 27 const AUTO_TIMEOUT_DELAY_MS = 1000; 28 29 STAGES.renderStage4 = function renderStage4(wrapper, state, config) { 30 if (!wrapper) return; 31 32 const cfg = config || window.COHBG_CONFIG || {}; 33 const t = (key, fallback) => 34 (cfg.texts && cfg.texts[key]) || fallback || key; 35 36 const st = state || (window.COHBG_STATE = window.COHBG_STATE || {}); 37 if (!st.totalChallenges) st.totalChallenges = 4; 38 st.currentChallenge = 3; 39 40 const isMobile = 41 typeof window !== 'undefined' && 42 window.matchMedia && 43 window.matchMedia('(max-width: 767px)').matches; 44 45 const gridRows = isMobile ? GRID_ROWS_MOBILE : GRID_ROWS_DESKTOP; 46 const gridCols = isMobile ? GRID_COLS_MOBILE : GRID_COLS_DESKTOP; 47 const totalCards = gridRows * gridCols; 48 const catIndex = randomInt(0, totalCards - 1); 49 50 const game = { 51 catIndex, 52 finished: false, 53 success: false, 54 timerId: null, 55 elapsedSeconds: 0, 56 currentPhase: null, 57 interactive: true, 58 }; 59 60 const cardsHtml = renderCards(totalCards, catIndex, t); 61 62 wrapper.innerHTML = ` 63 <div class="cohbg-stage cohbg-stage4-game"> 64 65 <div class="cohbg-stage1-short-instruction cohbg-stage4-short-instruction"> 66 <img 67 src="/wp-content/plugins/coh-blackout-game/assets/img/short_instruction_icon.svg" 68 alt="" 69 class="cohbg-stage1-short-instruction__icon" 70 loading="lazy" 71 /> 72 <p class="cohbg-stage1-short-instruction__text"> 73 ${t('stage4_short_instruction')} 74 </p> 75 </div> 76 77 <div class="cohbg-stage4-game__inner"> 78 79 <div class="cohbg-stage4-game__board" data-role="stage4-board"> 80 <div class="cohbg-stage4-game__top"> 81 <div class="cohbg-stage4-game__label"> 82 ${t('stage4_game_title', 'Find the odd one out')} 83 </div> 84 <div class="cohbg-stage4-game__timer"> 85 <span class="cohbg-stage4-game__timer-label"> 86 ${t('stage4_game_timer_label', 'Time left')} 87 </span> 88 <span 89 class="cohbg-stage4-game__timer-value" 90 data-role="stage4-timer" 91 > 92 ${TOTAL_SEC} 93 </span> 94 <span class="cohbg-stage4-game__timer-suffix">s</span> 95 </div> 96 </div> 97 98 <div class="cohbg-stage4-game__subtitle"> 99 ${t( 100 'stage4_game_subtitle', 101 'Tap on the one cat hidden among all the bears.' 102 )} 103 </div> 104 105 <div 106 class="cohbg-stage4-game__grid" 107 data-role="stage4-grid" 108 > 109 ${cardsHtml} 110 </div> 111 </div> 112 113 <div 114 class="cohbg-stage4-game__overlay" 115 data-role="stage4-overlay" 116 ></div> 117 118 <div 119 class="cohbg-stage4-game__phase-message" 120 data-role="stage4-phase-message" 121 aria-live="polite" 122 ></div> 123 124 </div> 125 </div> 126 `; 127 128 const rootEl = wrapper.querySelector('.cohbg-stage4-game'); 129 const gridEl = wrapper.querySelector('[data-role="stage4-grid"]'); 130 const overlayEl = wrapper.querySelector('[data-role="stage4-overlay"]'); 131 const phaseMsgEl = wrapper.querySelector('[data-role="stage4-phase-message"]'); 132 const timerEl = wrapper.querySelector('[data-role="stage4-timer"]'); 133 134 if (!gridEl || !rootEl) { 135 console.warn('COHBG: Stage4 grid root not found'); 136 return; 137 } 138 139 gridEl.style.setProperty('--cohbg-stage4-cols', String(gridCols)); 140 141 gridEl.addEventListener('click', function (e) { 142 if (game.finished || !game.interactive) return; 143 144 const card = e.target.closest('.cohbg-stage4-game__card'); 145 if (!card) return; 146
147 const kind = card.getAttribute('data-kind'); 148 if (kind !== 'cat') return; 149 150 card.classList.add('cohbg-stage4-game__card--found'); 151 finishGame(true); 152 }); 153 154 const startTime = Date.now(); 155 156 function setPhaseMessage(phase) { 157 if (!phaseMsgEl) return; 158 159 if (phase === 'blur') { 160 phaseMsgEl.textContent = t('messages_when_the_screen_gets_blurred_1', ''); 161 phaseMsgEl.classList.add('is-visible'); 162 } else if (phase === 'dark') { 163 phaseMsgEl.textContent = t('messages_when_the_screen_gets_blurred_2', ''); 164 phaseMsgEl.classList.add('is-visible'); 165 } else { 166 phaseMsgEl.textContent = ''; 167 phaseMsgEl.classList.remove('is-visible'); 168 } 169 } 170 171 function updatePhaseByTime(elapsedSec) { 172 let phase; 173 174 if ( 175 elapsedSec < 6 || 176 (elapsedSec >= 13 && elapsedSec < 18) || 177 (elapsedSec >= 25 && elapsedSec < 30) 178 ) { 179 phase = 'normal'; 180 } else if ( 181 (elapsedSec >= 6 && elapsedSec < 11) || 182 (elapsedSec >= 18 && elapsedSec < 23) 183 ) { 184 phase = 'blur'; 185 } else if ( 186 (elapsedSec >= 11 && elapsedSec < 13) || 187 (elapsedSec >= 23 && elapsedSec < 25) 188 ) { 189 phase = 'dark'; 190 } else { 191 phase = 'timeout'; 192 } 193 194 if (phase === game.currentPhase) return; 195 game.currentPhase = phase; 196 197 const isBlocked = 198 phase === 'blur' || phase === 'dark' || phase === 'timeout'; 199 200 game.interactive = !isBlocked && !game.finished; 201 202 rootEl.classList.toggle('cohbg-stage4-game--blur', phase === 'blur'); 203 rootEl.classList.toggle('cohbg-stage4-game--dark', phase === 'dark'); 204 rootEl.classList.toggle( 205 'cohbg-stage4-game--finished', 206 phase === 'timeout' || game.finished 207 ); 208 209 if (overlayEl) { 210 if (phase === 'blur') { 211 overlayEl.style.opacity = '1'; 212 overlayEl.style.background = 'rgba(15, 23, 42, 0.4)'; 213 } else if (phase === 'dark') { 214 overlayEl.style.opacity = '1'; 215 overlayEl.style.background = '#000000'; 216 } else if (phase === 'timeout') { 217 overlayEl.style.opacity = '1'; 218 overlayEl.style.background = 'rgba(15, 23, 42, 0.75)'; 219 } else { 220 overlayEl.style.opacity = '0'; 221 overlayEl.style.background = 'transparent'; 222 } 223 } 224 225 // â message only for blur/dark 226 setPhaseMessage(phase); 227 } 228 229 game.timerId = window.setInterval(function () { 230 if (game.finished) return; 231 232 const elapsedSec = Math.floor((Date.now() - startTime) / 1000); 233 game.elapsedSeconds = elapsedSec; 234 235 const timeLeft = Math.max(0, TOTAL_SEC - elapsedSec); 236 if (timerEl) { 237 timerEl.textContent = String(timeLeft); 238 } 239 240 updatePhaseByTime(elapsedSec); 241 242 if (elapsedSec >= TOTAL_SEC) { 243 finishGame(false); 244 } 245 }, 250); 246 247 function finishGame(success) { 248 if (game.finished) return; 249 game.finished = true; 250 game.success = !!success; 251 252 if (game.timerId) { 253 window.clearInterval(game.timerId); 254 game.timerId = null; 255 } 256 257 game.interactive = false; 258 updatePhaseByTime(TOTAL_SEC + 1); 259 260 const delay = success ? AUTO_SUCCESS_DELAY_MS : AUTO_TIMEOUT_DELAY_MS; 261 262 window.setTimeout(function () { 263 goToResult(game.success); 264 }, delay); 265 } 266 267 function goToResult(success) { 268 const resultPayload = { 269 success: !!success, 270 elapsedSeconds: game.elapsedSeconds, 271 }; 272 273 if ( 274 window.COHBG_STAGES && 275 typeof window.COHBG_STAGES.onStage4Finished === 'function' 276 ) { 277 window.COHBG_STAGES.onStage4Finished( 278 wrapper, 279 st, 280 cfg, 281 resultPayload 282 ); 283 } else if (typeof STAGES.renderStage4Result === 'function') { 284 STAGES.renderStage4Result(wrapper, st, cfg, resultPayload); 285 } else { 286 console.log('COHBG: Stage 4 finished', resultPayload); 287 } 288 } 289 }; 290
291 STAGES.renderStage4Result = function renderStage4Result( 292 wrapper, 293 state, 294 config, 295 result 296 ) { 297 if (!wrapper) return; 298 299 const cfg = config || window.COHBG_CONFIG || {}; 300 const t = (key, fallback) => 301 (cfg.texts && cfg.texts[key]) || fallback || key; 302 303 const st = state || (window.COHBG_STATE = window.COHBG_STATE || {}); 304 if (!st.totalChallenges) st.totalChallenges = 4; 305 st.currentChallenge = 3; 306 307 const success = !!(result && result.success); 308 309 const copy = success 310 ? { 311 title: t('stage4_result_found_title'), 312 text: t('stage4_result_found_text'), 313 image: 314 '/wp-content/plugins/coh-blackout-game/assets/img/stage_4/found_cat.png', 315 donateLabel: t('stage4_result_donate_label'), 316 } 317 : { 318 title: t('stage4_result_not_found_title'), 319 text: t('stage4_result_not_found_text'), 320 image: 321 '/wp-content/plugins/coh-blackout-game/assets/img/stage_4/hid_cat.png', 322 donateLabel: t('stage4_result_support_label'), 323 }; 324 325 wrapper.innerHTML = ` 326 <div class="cohbg-stage cohbg-stage4-result"> 327 <div class="cohbg-stage4-result__inner"> 328 <div class="cohbg-stage4-result__card"> 329 <h2 class="cohbg-stage4-result__title"> 330 ${copy.title} 331 </h2> 332 <p class="cohbg-stage4-result__text"> 333 ${copy.text} 334 </p> 335 <div class="cohbg-stage4-result__image"> 336 <img src="${copy.image}" alt="${copy.title}" /> 337 </div> 338 <div class="cohbg-stage4-result__actions"> 339 <button 340 type="button" 341 class="cohbg-btn cohbg-btn--finish cohbg-stage4-result__btn" 342 data-role="stage4-result-finish" 343 > 344 <span class="cohbg-btn-icon cohbg-btn-icon--lightbulb"></span> 345 <span class="cohbg-btn-label"> 346 ${t('stage4_result_finish_label')} 347 </span> 348 </button> 349 <button 350 id="donate-monthly" 351 data-fundraiseup 352 data-currency="USD" 353 data-amount="80" 354 data-recurring="monthly" 355 data-campaign="FUNBLLJTYFW" 356 type="button" 357 class="cohbg-btn cohbg-btn--donate cohbg-stage4-result__btn" 358 data-role="stage4-result-donate" 359 style="" 360 > 361 <span class="cohbg-btn-icon cohbg-btn-icon--heart"></span> 362 <span class="cohbg-btn-label"> 363 ${copy.donateLabel} 364 </span> 365 </button> 366 </div> 367 </div> 368 </div> 369 </div> 370 `; 371 372 const finishBtn = wrapper.querySelector('[data-role="stage4-result-finish"]'); 373 const donateBtn = wrapper.querySelector('[data-role="stage4-result-donate"]'); 374 375 if (finishBtn) { 376 finishBtn.addEventListener('click', function () { 377 if ( 378 window.COHBG_STAGES && 379 typeof window.COHBG_STAGES.onStage4ResultNext === 'function' 380 ) { 381 window.COHBG_STAGES.onStage4ResultNext( 382 wrapper, 383 st, 384 cfg, 385 result || {} 386 ); 387 } else { 388 console.log('COHBG: Stage 4 result finished (TODO: go to final screen)'); 389 } 390 }); 391 } 392 393 if (donateBtn) { 394 donateBtn.addEventListener('click', function () { 395 if ( 396 window.COHBG_STAGES && 397 typeof window.COHBG_STAGES.onStage4Donate === 'function' 398 ) { 399 window.COHBG_STAGES.onStage4Donate(wrapper, st, cfg); 400 } else { 401 console.log('COHBG: Donate action not implemented'); 402 } 403 }); 404 } 405 }; 406 407 if (!window.COHBG_STAGES.onStage4Finished) { 408 window.COHBG_STAGES.onStage4Finished = function ( 409 wrapper, 410 state, 411 config, 412 result 413 ) { 414 STAGES.renderStage4Result( 415 wrapper, 416 state || (window.COHBG_STATE = window.COHBG_STATE || {}), 417 config || window.COHBG_CONFIG || {}, 418 result || {} 419 ); 420 }; 421 } 422 423 // --------- helpers --------- 424 425 function renderCards(total, catIndex, t) { 426 let html = ''; 427 for (let i = 0; i < total; i++) { 428 const isCat = i === catIndex;
429 const kind = isCat ? 'cat' : 'bear'; 430 431 html += ` 432 <button 433 type="button" 434 class="cohbg-stage4-game__card cohbg-stage4-game__card--${kind}" 435 data-index="${i}" 436 data-kind="${kind}" 437 aria-label="${ 438 isCat 439 ? t('stage4_game_card_cat', 'Cat') 440 : t('stage4_game_card_bear', 'Bear') 441 }" 442 > 443 <span class="cohbg-stage4-game__card-inner"></span> 444 </button> 445 `; 446 } 447 return html; 448 } 449 450 function randomInt(min, max) { 451 return min + Math.floor(Math.random() * (max - min + 1)); 452 } 453})(window);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.