PageSourceSearch

https://childrenheroes.nl/wp-content/plugins/coh-blackout-game/ass…/blackout-stage4-quiz.js?ver=1769423438

js childrenheroes.nl collected 2026-10-08 07:52:55 UTC 13,516 bytes, 453 lines download raw bytes

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.