PageSourceSearch

https://www.sporcle.com/js/sgf.js?v=130266

js sporcle.com collected 2026-10-01 08:51:25 UTC 27,012 bytes, 1,020 lines download raw bytes

1var stop = false;
2var mweb = mweb || 0;
3var giveUpDelay = 0;
4var gameTimer;
5var startTime;
6var origSecs;
7var origQuizTime;
8var timerSecs;
9var pausedTime = 0;
10var pausedStartTime;
11var overrideSecs = -1;
12var timerCorrection;
13var inProgress = false;
14var didGiveUp = false;
15var stopwatch = false;
16var scoreAsPercentage = false;
17var numRight = 0;
18var currDisplayScore = 0;
19var oldTime = null;
20var delayCount = 0;
21var isTournamentQuiz = false;
22const JUST_GO_PLAY_THRESHOLD = 15;
23
24$j(function () {
25	oldTime = $j('#time').html().trim();
26});
27
28function selectTimerSetting(selection, isShowdown) {
29	const isSporcleAppView = (window._payload || {}).is_flagship_app_view;
30
31	if (selection == 'stopwatch' && !userID) {
32		window.SporcleLib.Modal.openRegisterModal({
33			regPath        : 'quiz_page_practice_mode',
34		});
35		return;
36	}
37	
38	let timeEl = mweb ? $j('#reckoning-time') : $j('#time');
39
40	if (inProgress) {
41		return;
42	}
43
44	if (typeof isShowdown === 'undefined') {
45		isShowdown = false;
46	}
47
48	$j('#time-box-dropdown-menu button').each(function(index, element) {
49		const $el = $j(element);
50		if ($el.data('value') === selection) {
51			$el.addClass('active');
52		} else {
53			$el.removeClass('active');
54		}
55	});
56
57	// Only do the things if the mode actually changes
58	if (selection === 'timer' && stopwatch) {
59		stopwatch = false;
60		timeEl.html(oldTime);
61
62		if (!mweb) {
63			$j('#timeBox .dropdown-trigger').html('Timer');
64			$j('#timeBox #practice-mode-container').addClass('disabled');
65		}
66
67		$j('#timeBox, #scoreBox, #upTime').removeClass('timePast');
68		
69		if (userID) {
70			if (isShowdown) {
71				$j('#share-game-challenge').addClass('disabled');
72				window.Sporcle.scoreboards.showdown.params.mode = 'showdownFriends';
73				showCollapsedQuizScoreboard($j('#showdown-scoreboard'), window.Sporcle.scoreboards.showdown);
74			} else {
75				window.Sporcle.scoreboards.quiz.params.mode = 'quizFriends';
76				showCollapsedQuizScoreboard($j('#quiz-scoreboard'), window.Sporcle.scoreboards.quiz);
77			}
78		}
79
80		if (isSporcleAppView && Sporcle.gameData.quizHelpers) {
81			if (!Sporcle.gameData.quizHelpers.filter(qh => qh.id !== 'the-extender').length) {
82				$j('#quiz-helpers-open').show();
83			}
84		}
85	} else if (selection === 'stopwatch' && !stopwatch) {
86		stopwatch = true;
87		timeEl.html('00:00');
88
89		if (!mweb) {
90			$j('#timeBox .dropdown-trigger').html('Practice Mode');
91			$j('#timeBox #practice-mode-container').removeClass('disabled');
92		}
93
94		$j('#share-game-challenge').addClass('disabled');
95		$j('#timeBox, #scoreBox, #upTime').addClass('timePast');
96		
97		if (userID) {
98			window.Sporcle.scoreboards.quiz.params.mode = 'quizStopwatch';
99			showCollapsedQuizScoreboard($j('#quiz-scoreboard'), window.Sporcle.scoreboards.quiz);
100		}
101
102		if (isSporcleAppView && Sporcle.gameData.quizHelpers) {
103			if (!Sporcle.gameData.quizHelpers.filter(qh => qh.id !== 'the-extender').length) {
104				$j('#quiz-helpers-open').hide();
105			}
106		}
107	}
108}
109
110function selectScoreSetting(selection) {
111	if (!window.userID) {
112		window.SporcleLib.Modal.openRegisterModal({
113			regPath        : 'quiz_page_score_settings',
114		});
115		return;
116	}
117	
118	$j('#score-box-dropdown-menu button').each(function(index, element) {
119		const $el = $j(element);
120		if ($el.data('value') === selection) {
121			$el.addClass('active');
122		} else {
123			$el.removeClass('active');
124		}
125	});
126
127	scoreAsPercentage = (selection === 'percentage');
128
129	displayScore();
130}
131
132function startTimer() {
133	timerSecs = stopwatch ? 0 : origSecs;
134	startTime = new Date().getTime();
135	gameTimer = setTimeout(redo, 1000);
136
137	$j('#time-progress').css({
138		'animation-duration' : `${origSecs * 1000}ms`,
139		'animation-play-state' : stopwatch ? 'paused' : 'running'
140	});
141}
142
143function displayScore(numCorrect, showMax = true) {
144	const isSporcleAppView = (window._payload || {}).is_flagship_app_view;
145
146	// if (isSporcleAppView) {
147	// 	window.SporcleApp.api.playHaptic('soft');
148	// }
149
150	if (typeof numCorrect === 'undefined') {
151		numCorrect = currDisplayScore;
152	} else {
153		currDisplayScore = numCorrect;
154	}
155
156	let maxScore = answers.length;
157
158	if (window.pinpoint) {
159		maxScore = maxScore * 100;
160	}
161
162	var score;
163	if (scoreAsPercentage) {
164        var userPct = _.round((numCorrect/maxScore), 2);
165		score = _.round(100 * userPct) + '%';
166	} else {
167		if (showMax) {
168			score = numCorrect + "/" + maxScore;
169
170			if (mweb) {
171				score = numCorrect + " / " + maxScore;
172			}
173		} else {
174			score = numCorrect;
175		}
176	}
177
178	$j('.correctnum').text(numCorrect);
179	$j('#scoreBox .currentScore').text(score);
180}
181
182function displayTime(totalSecs) {
183	var min = Math.floor(totalSecs/60);
184	var sec = totalSecs%60;
185	return (min<10?"0":"")+min+":"+(sec<10?"0":"")+sec;
186}
187
188function endGame()  {
189	if (!inProgress) return;
190
191	inProgress = false;
192
193	clearTimeout(gameTimer);
194	
195	// Defer this block to reduce INP
196	window.setTimeout(async function() {
197		showMissed();
198		
199		if (window.Sporcle.postQuizAnimation) {
200			await window.Sporcle.postQuizAnimation;
201		}
202		
203		$j(window).trigger('endgame');
204		$j('body').removeClass('active').addClass('over');
205		
206		callSPHooks('end');
207		
208	}, 0);
209}
210
211function giveUp() {
212	didGiveUp = true;
213	logPlay();
214	endGame();
215
216	if (typeof gameID !== 'undefined') {
217		$j.get('/games/includes/event.php', {
218			g: gameID,
219			mw: mweb,
220			sw: stopwatch ? 1 : 0,
221			a: 'gu'
222		});
223	}
224}
225
226function addGiveUp() {
227	const $giveUp = $j('#giveUp');
228	$giveUp.prop('disabled', false)
229	$giveUp.one('click', giveUp);
230	$giveUp.show();
231}
232
233async function redo() {
234	if (stop || didGiveUp) {
235		return;
236	}
237
238	timerSecs += stopwatch ? 1 : -1;
239
240	if (stopwatch) {
241		if (timerSecs == giveUpDelay) {
242			addGiveUp();
243		}
244	} else {
245		if ((origSecs - timerSecs) == giveUpDelay) {
246			addGiveUp();
247		}
248
249		if (timerSecs == 10) {
250			$j('#timeBox').addClass('timeTicking');
251		}
252	}
253
254	// Pinpoint quizzes have a delay to show the scoring radius. Crossover quizzes also have a delay. We don't want these checks to cut this short
255	if (!window.pinpoint && !window.crossover) {
256		if (guessesRemaining == 0) {
257			/* Process the end of a click box or picture click game where they ran out of guesses (regardless if they got them all right) */
258			logPlay();
259			endGame();
260	
261			return;
262		}
263	
264		if (inProgress && endOnAllCorrect && found.length == answers.length) {
265			/* Process the end of normal game where they got them all right before the time ran out */
266			logPlay();
267			endGame();
268			return;
269		}
270	}
271
272	$j('#time').html(displayTime(timerSecs));
273	
274	restoreFocus();
275
276	if (timerSecs == 5999) {
277		// End the game if the timer ever hits 99:59. This is for stopwatch
278		logPlay();
279		endGame();
280		return;
281	}
282
283	if (stopwatch) {
284		if (timerSecs == origSecs) {
285			/* They ran out of time, log a timer play but keep counting up */
286			logTimerPlay();
287
288			$j.get('/games/includes/event.php', {g:gameID, mw:mweb, sw:stopwatch?1:0, a:'av'});
289		}
290		timerCorrection = (new Date().getTime() - (startTime+pausedTime)) - (timerSecs*1000);
291		gameTimer = setTimeout(redo, 1000-timerCorrection);
292	} else {
293		if (timerSecs == 0) {
294			$j('#time-progress').css({
295				'animation-play-state' : 'paused'
296			});
297
298			let showAddTime = true;
299			let timeToAdd;
300
301			if (origQuizTime < 60) {
302				timeToAdd = 15; // 15 seconds
303			} else if (origQuizTime < 240) {
304				timeToAdd = 30; // 30 seconds
305			} else {
306				timeToAdd = 60; // 60 seconds
307			}
308
309			if (window.orderup && window.orderup.choices.every((choice, index) => choice.data === window.orderup.correct[index].data)) {
310				showAddTime = false; // Don't show add time modal if they got everything right in order up
311			}
312
313			if (showAddTime && window.addTime && await window.addTime(timeToAdd)) {
314				doAddTime(timeToAdd);
315
316				pausedTime = (new Date().getTime() - startTime) - ((origSecs-timerSecs)*1000);
317				timerCorrection = 0;
318
319				return;
320			}
321
322			/* Process the end of any game where they ran out of time */
323			logPlay();
324			endGame();
325			return;
326		} else {
327			/* Restart the timer */
328			timerCorrection = (new Date().getTime() - (startTime+pausedTime)) - ((origSecs-timerSecs)*1000);
329			gameTimer = setTimeout(redo, (1000 - timerCorrection));
330		}
331	}
332}
333
334function doAddTime(timeToAdd = 60) {
335	timerSecs += timeToAdd;
336	origSecs += timeToAdd;
337
338	let animationProgress = (origSecs - timerSecs) / origSecs;
339
340	$j('#time').html(displayTime(timerSecs));
341	$j('#timeBox').removeClass('timeTicking');
342
343	// All this just resets the animation to prevent it being buggy
344	$j('#time-progress').css('animation', 'none').height();
345
346	$j('#time-progress').css({
347		'animation-name': 'time-progress-bar',
348		'animation-timing-function': 'linear',
349		'animation-fill-mode': 'forwards',
350		'animation-play-state': 'paused'
351	});
352
353	$j('#time-progress').css({
354		'animation-duration' : `${origSecs * 1000}ms`,
355		'animation-play-state' : 'running',
356		'animation-delay' : `-${origSecs * animationProgress}s`
357	});
358}
359
360function getLogData() {
361	var ls = getLogStrings();
362	ls.logString = ls.logString || '';
363	ls.logBonusString = ls.logBonusString || '';
364
365	var logTime = stopwatch ? timerSecs : origSecs-timerSecs;
366
367	// Get homepage tracking data. Then clear it, we only want to log this on he very first play coming from the homepage
368	let homepageTrackingPayload = {};
369	if (window.homepageTracking) { // This should be getting set, but don't want to break if it's not
370		homepageTrackingPayload = window.homepageTracking.getTrackingDataLogResultsPayload();
371		window.homepageTracking.clearTrackingData();
372	} else {
373		console.error('Homepage tracking not found');
374	}
375	
376	var logData = {
377		g       : gameID,
378		a       : ls.logString,
379		t       : logTime,
380		b       : ls.logBonusString,
381		tzo     : (new Date()).getTimezoneOffset(),
382		es      : Sporcle.embedSiteID,
383		ep      : Sporcle.embedPartnerID,
384		pl      : window.sessionStorage.getItem('playlistID'),
385		ho      : typeof hasOrange !== 'undefined' ? hasOrange : 0,
386		hpm     : typeof hasMembership !== 'undefined' ? hasMembership : 0,
387		ldtm    : loadTime,
388		avg_pct : typeof averageScore !== 'undefined' ? averageScore : 0,
389		qs      : typeof qScores !== 'undefined' ? qScores : {},
390		ssr     : window.Sporcle.gameData.subsetRows.join(','),
391
392		// Tracking keys for the homepage
393		...homepageTrackingPayload,
394	};
395
396	if (isEmbed) {
397		if (typeof spuid != 'undefined' && spuid) logData.spuid = spuid;
398	}
399
400	return logData;
401}
402
403function logPlay() {
404	trackJustGo();
405
406	if (showdownActive) {
407		return;
408	}
409
410	if (stopwatch) {
411		logTimerPlay();
412		logStopwatchPlay();
413	} else {
414		logTimerPlay();
415	}
416
417	return;
418}
419
420
421// Helper functions for "JustGo" mtc variable
422function trackJustGo() {
423	// Only for logged out users
424	if (typeof userKey !== 'undefined') {
425		return;
426	}
427	
428	let playCount = getJustGoCount();
429	playCount++;
430	window.localStorage.setItem('justGoPlayCount', playCount);
431
432	const params = new URLSearchParams(window.location.search);
433
434	if ((playCount > 0 && playCount % JUST_GO_PLAY_THRESHOLD === 0) || params.has('justgo')) {
435		triggerJustGo();
436	}
437}
438
439function getJustGoCount() {
440	let playCount = 0;
441	
442	// Only fetch the value if the user is not logged in
443	if (window.localStorage && typeof userKey === 'undefined') {
444		playCount = parseInt(window.localStorage.getItem('justGoPlayCount')) || 0;
445	}
446	
447	return playCount;
448}
449
450// Check the user's JustGo play count and trigger JustGo if it's 20
451function checkJustGo() {
452	const count = getJustGoCount();
453	if (count > 0 && count % JUST_GO_PLAY_THRESHOLD === 0) {
454		triggerJustGo();
455	}
456}
457
458// Set the window variable and post an event for MTC to pick up
459function triggerJustGo() {
460	window.JustGo = true;
461	window.top.postMessage(
462		{ source: "JustGo", message: true},
463		"*"
464	);
465}
466
467
468function logTimerPlay() {
469	// This is so postLog waits for us to get a response back from log_results before attempting to do postLog stuff
470	var deferredData = $j.Deferred();
471	postLog = postLog.bind(null, deferredData);
472
473	var logData = getLogData();
474	logData.swm = stopwatch ? 1 : 0;
475	logData.nur = typeof newUserRecommended !== 'undefined' && newUserRecommended ? 1 : 0;
476
477	$j.get("/games/log_results.php", logData, function(data) {
478		deferredData.resolve(data);
479		
480		if (data.tracked_tournaments && data.tracked_tournaments.length) {
481			let message = data.tracked_tournaments.length > 1
482			? `Your score has been recorded for all the tournaments this quiz is in. See how you stack up in <a href='/tournaments/'>your tournaments</a>.`
483			: `Your play is locked in — you’re #${data.tracked_tournaments[0].current_rank} out of ${data.tracked_tournaments[0].player_count} so far today. Check your progress on the <a href='/tournaments/${data.tracked_tournaments[0].encoded_tournament_id}'>Tournament Leaderboard</a>.`;
484			
485			// Modal disabled for now
486			// window.SporcleLib.Modal.openTournamentPostQuizModal();
487			window.addGameMessage(message, 'alertGood', 'tournament_message');
488		}
489		
490        if (data.play_is_valid && typeof userKey !== 'undefined' && !data.anonymized_play) {
491            window.updatePlayStreak();
492        }
493
494        if (data.play_is_valid && typeof gameID !== 'undefined') {
495            var user = typeof userKey !== 'undefined' && userKey ? userKey : (typeof spuid !== 'undefined' && spuid ? spuid : false);
496
497            if (typeof window.sbData !== 'undefined') {
498
499                if (
500                    typeof sbData[2] !== 'undefined' &&
501                    typeof sbData[2]['playlist_game_ids'] !== 'undefined' &&
502                    sbData[2]['playlist_game_ids'].indexOf(gameID) >= 0
503                ) {
504                    updateSuggestionsBar(2, 1);
505                }
506
507                if (typeof gameCategoryID !== 'undefined' && typeof updateSamplerPlatter === 'function') {
508                    updateSamplerPlatter(gameCategoryID);
509                }
510
511                if (
512                    typeof window.sbData['update_baby_steps_on_play'] !== 'undefined' &&
513                    window.sbData['update_baby_steps_on_play'] &&
514                    typeof updateBabySteps === 'function'
515                ) {
516                    updateBabySteps();
517                }
518            }
519        }
520	});
521
522	// We only log a timer play once, so short circuit any future calls
523	logTimerPlay = function(){};
524	
525	if (window.Sporcle.sendEmbedQuizEvents) {
526		// Basing the correct answer count on the logString because we don't update numRight on some quiz types until the showMissed() function
527		const correct = (logData.a.match(/1/g)||[]).length;
528		const answers = window.answers.length;
529		const time = window.origSecs-window.timerSecs;
530		
531		let cause = 'completed_quiz';
532		if (timerSecs === 0) {
533			cause = 'timed_out';
534		} else if (didGiveUp) {
535			cause = 'gave_up';
536		} else if (_spks && correct !== answers) {
537			cause = 'hit_mine';
538		}
539		
540		const msg = {
541			source         : 'sporcle',
542			event          : 'end',
543			end_cause      : cause,
544			correct_count  : correct,
545			question_count : answers,
546			time           : time,
547		};
548		window.parent.postMessage(msg, '*');
549	}
550}
551
552function logStopwatchPlay() {
553	var logData = getLogData();
554
555	logData.sw = 1;
556	logData.swm = stopwatch ? 1 : 0;
557
558	$j.get("/games/log_results.php", logData, function (data) {
559        if (data.play_is_valid && typeof userKey !== 'undefined' && !data.anonymized_play) {
560            window.updatePlayStreak();
561        }
562    });
563
564	// We only log a stopwatch play once, so short circuit any future calls
565	logStopwatchPlay = function(){};
566}
567
568function runFinished() {
569	$j('#giveUp:not(.button-primary)').hide();
570	$j('#pauseBox').css({visibility:'hidden'});
571	postLog();
572}
573
574function postLog(deferredData) {
575	if (typeof deferredData === 'object') {
576		// This means it is a normal play, and we are waiting for the call to log_results to finish
577		$j.when(deferredData).done(function(data) {
578			$j(window).trigger('postLog');
579
580			var completedChallengeCount = 0;
581			var height = 270;
582			var userPct = 0;
583
584			if (answers.length >= 1) {
585				userPct = _.round(window.numRight/answers.length, 2);
586
587				if (window.pinpoint && window.qScores) {
588					let score = Object.values(qScores).reduce((a, cv) => {
589						return a + cv;
590					}, 0);
591					userPct = _.round(score/(answers.length * 100), 2);
592				}
593
594				if (userPct === 0 && window.numRight > 0 ) {
595					userPct = 0.01;
596				} else if (userPct > 1) {
597					userPct = 1;
598				}
599			}
600
601			if (data.hasOwnProperty('challenge_status') && data.challenge_status.logged_in) {
602				challenge = data.challenge_status;
603
604				completedChallengeCount = challenge.record.wins + challenge.record.losses + challenge.record.ties;
605
606				if (!challenge.rollup) {
607					height += 50 * completedChallengeCount;
608				}
609
610				if (challenge.met > 0 && completedChallengeCount === 0) {
611					$j('#game-message').html('<a href="/challenge/pending/">Challenge met!</a>');
612					$j('#game-message-box').attr('class', 'alertNeutral').fadeIn({duration: 0.25});
613				}
614			}
615
616			if (completedChallengeCount > 0) {
617				$j('#game-message-box').hide();
618
619				if (!window._payload.is_flagship_app_view) {
620					window.SporcleLib.Modal.openPostChallengeModal({
621						results : data.challenge_status,
622						gid     : gameID
623					});
624				} else {
625					let userPctCopy = userPct; // a copy is needed to prevent the value from changing during the 50ms timeout
626
627					window.SporcleLib.Modal.openPostChallengeModal({
628						results : data.challenge_status,
629						gid     : gameID,
630						onClose : () => {
631							setTimeout(() => {
632								openFlagshipPostGameModal(userPctCopy);
633							}, 50);
634						}
635					});
636				}
637			} else if (window._payload?.is_flagship_app_view) {
638				openFlagshipPostGameModal(userPct);
639			}
640
641			if (!isEmbed) {
642				if (window.userID && window.numRight > 0) {
643					let score = window.numRight;
644					let maxScore = answers.length;
645					
646					if (window.pinpoint && window.qScores) {
647						score = Object.values(qScores).reduce((a, cv) => {
648							return a + cv;
649						}, 0);
650						maxScore = answers.length * 100;
651						userPct = _.round(100*score/maxScore, 2);
652					} else {
653						userPct = _.round(userPct*100, 0);
654					}
655					if (!stopwatch) {
656						// Update the played tag
657
658						if (typeof window.bestPercent === 'undefined' || window.bestPercent < userPct) {
659	                        $j('#played-tag')
660								.html('Best: '+score+'/'+maxScore+' ('+userPct+'%)')
661								.show();
662						}
663					}
664					updateLastPlayedDate();
665
666					// Update the friend scores
667					var mode = stopwatch ? 'quizStopwatch' : 'quizFriends';
668					var playTime = stopwatch ? timerSecs : origSecs-timerSecs;
669					addPlayToFriendScoresCache(mode, score, playTime);
670					window.Sporcle.scoreboards.quiz.params.mode = mode;
671					showCollapsedQuizScoreboard($j('#quiz-scoreboard'), window.Sporcle.scoreboards.quiz);
672				}
673			}
674		});
675	} else {
676		// This means it is a showdown play, nothing was logged.
677		// openFlagshipPostGameModal happens in getReckoning if it's a flagship app showdown play
678	}
679}
680
681
682function showAnswerBoxes() {
683	$j('.answer').css({visibility:'visible'});
684}
685
686async function startGame(secsStart)
687{
688	window.Sporcle.gameData.startTime = Math.floor(Date.now() / 1000);
689	inProgress = true;
690
691	callSPHooks('start');
692
693	if (overrideSecs >= 0) {
694		origSecs = overrideSecs;
695		giveUpDelay -= (secsStart - overrideSecs);
696	} else {
697		origSecs = secsStart;
698	}
699	origSecs = (overrideSecs >=0) ? overrideSecs : secsStart;
700	origQuizTime = origSecs; // If time is added with the app addTime modal origSecs will be updated, keep original value
701
702	$j('#button-showdown-lower').remove();
703	$j('#button-showdown-upper').remove();
704	
705	$j('#preHide').hide();
706	$j('#preHideContent').show();
707
708	$j('#playPadding, .embeddable-start-button').hide();
709
710	$j('#answer-wrapper').show();
711
712	$j('#skip').css({visibility:'visible'});
713
714	$j('#gameinput').prop('disabled',false);
715
716	if (!showdownActive) {
717		$j('#pauseBox').css({visibility:'visible'});
718	}
719	disableStopwatchPick();
720
721	if (typeof gameID !== 'undefined' && !showdownActive) {
722		var eventData = {
723			g: gameID,
724			mw: mweb,
725			sw: stopwatch ? 1 : 0,
726			a: 'st',
727			ldtm : loadTime
728		};
729		$j.get('/games/includes/event.php', eventData);
730	}
731
732	$j('body').addClass('play active');
733	
734	// The `startgame` event kicks off a bunch of stuff on mobile web, so we want to yield execution before and after this
735	await window.yieldToMain();
736	$j(window).trigger('startgame');
737	await window.yieldToMain();
738	
739	
740	$j('#gameinput').trigger('focus');
741	
742	if (window.fakeInput) {
743		window.fakeInput.remove();
744	}
745
746	startTimer();
747
748	if (mweb) {
749		$j('#pauseButton').on('click', function(e) {
750			pauseGame();
751		});
752
753		const qhOpenEl = document.querySelector('#quiz-helpers-open');
754
755		if (qhOpenEl) {
756			$j('#quiz-helpers-open').on('click', () => {
757				window.SporcleLib.Modal.openQuizHelpersModal();
758			});
759
760			let idleTimeout = null
761
762			function resetIdleTimer() {
763			    clearTimeout(idleTimeout);
764
765			    idleTimeout = setTimeout(() => {
766			        qhOpenEl.classList.remove('glisten-active');
767			        void qhOpenEl.offsetWidth;
768				    qhOpenEl.classList.add('glisten-active');
769				    resetIdleTimer();
770			    }, 10000);
771			}
772
773			window.addEventListener('touchstart', resetIdleTimer)
774			resetIdleTimer();
775		}
776	}
777
778	if (giveUpDelay <= 0) { addGiveUp(); }
779	
780	if (window.Sporcle.sendEmbedQuizEvents) {
781		const msg = {source: 'sporcle', event: 'start'};
782		window.parent.postMessage(msg, '*');
783	}
784}
785
786function pauseGame(fromGiveUp)
787{
788	/* Separated out to account for instances where we want to stop game play without displaying the pause screen */
789	delayGame();
790
791	if (typeof gameID != 'undefined') {
792		$j.get('/games/includes/event.php', {
793			g  : gameID,
794			mw : mweb,
795			a  : fromGiveUp ? 'gup' : 'p'
796		});
797	}
798
799	showPause(fromGiveUp);
800}
801
802function delayGame() {
803	delayCount += 1;
804
805	if (stop) {
806		return;
807	}
808
809	$j('#time-progress').css({
810		'animation-play-state' : 'paused'
811	});
812
813	pausedStartTime = new Date().getTime();
814	stop = true;
815}
816
817// Grid quizzes for MW will automatically pause on being rotated horizontally if the view is too small.
818// We don't want those to be counted towards pauses or resumes in gamereport_mobile.php so break this
819// out into two separate parts
820function unPauseGame() {
821	resumeGame()
822
823	if (typeof gameID != 'undefined') {
824		$j.get('/games/includes/event.php', {g:gameID, mw:mweb, a:'r'});
825	}
826}
827
828function resumeGame() {
829	delayCount -= 1;
830
831	if (!stop) {
832		return;
833	}
834
835	if (delayCount > 0) {
836		return;
837	}
838
839	$j('#time-progress').css({
840		'animation-play-state' : stopwatch ? 'paused' : 'running'
841	});
842
843	stop = false;
844	pausedTime += new Date().getTime() - pausedStartTime;
845
846	if (stopwatch) {
847		timerCorrection = (new Date().getTime() - (startTime+pausedTime)) - (timerSecs*1000);
848	} else {
849		timerCorrection = (new Date().getTime() - (startTime+pausedTime)) - ((origSecs-timerSecs)*1000);
850	}
851
852	gameTimer = setTimeout(redo, 1000-timerCorrection);
853
854	if (!mweb) {
855		$j('.instructions object, .instructions embed').show();
856		$j('#gameinput').trigger('focus');
857		$j('#pause-ad').html("");
858	}
859}
860
861function showPause(fromGiveUp) {
862	let pauseData = {
863		time             : displayTime(timerSecs),
864		quiz_length      : answers.length,
865		correct          : currDisplayScore,
866		quiz_name        : Sporcle.gameData.name,
867		quiz_description : Sporcle.gameData.description,
868		give_up          : fromGiveUp,
869		show_score       : (Sporcle.gameData.isGrid ? 0 : 1),
870		hideQuizHelp     : (Sporcle.gameData.quizHelpers.length == 0 ? 1 : 0),
871	};
872
873	if (mweb) {
874		$j(window).trigger('showPause', pauseData);
875	} else {
876		$j('.tynt-close-btn').trigger('click'); // does not behave well with pause screen
877		$j('.instructions object, .instructions embed').hide();
878		SporcleLib.Modal.openPauseModal(pauseData);
879	}
880}
881
882function decodeFlag(f) {
883	return (parseInt(f, 36) % 2 == 0);
884}
885
886function getAnswer(slotNum, tryShort) {
887	if (slotNum >= answers.length) {
888		thisSlot = hidden[slotNum-answers.length];
889	} else {
890		thisSlot = answers[slotNum];
891	}
892
893	// Don't split at closing tags "</"
894	var garbledGator = (asta['<'] == ']' ? "\\" : "") + asta['<'];
895	var slashNoGator = thisSlot.search("[^"+garbledGator+"]\/");
896	slashNoGator = slashNoGator == -1 ? -1 : slashNoGator + 1;	// Position of first slash that has no "<" before it
897	slash = thisSlot.search("^\/") == 0 ? 0 : slashNoGator;		// If string starts with slash, go with that, otherwise use first slash not preceded by "<"
898
899	if (slash != -1 && !tryShort) {
900		thisSlot = thisSlot.slice(0, slash);
901	}
902
903	slotinfo = thisSlot;
904	var e='';
905	for(var i=0;i < slotinfo.length;i++){var c=slotinfo.slice(i,i+1);var d=false;for(var k in asta)if(asta[k]==c){	e+=k;d=true;}if(!d)	e+=c;}
906
907	if (tryShort) {
908		var parts = e.split('/');
909
910		if ((parts.length > 2) && (parts[1].length >= 39)) {
911			return parts[2];
912		} else {
913			return parts[0];
914		}
915	}
916
917	return e;
918}
919
920function escapeHTML(unsafe) {
921	return $j('<div>').text(unsafe).html();
922}
923
924function unescapeHTML(safe) {
925	return $j('<div>').html(safe).text();
926}
927
928function disableStopwatch() {
929	// Disable switching the timer settings
930	$j('#timeBox .timer-controls-btn').attr('disabled', true).off('click keyup');
931
932	// Hide "Practice Mode" button
933	$j('#button-play.button-play-stopwatch').hide();
934	
935	// Seems most instances we disable stopwatch is a good time to disable quiz helpers
936	$j('#quiz-helpers-open').hide();
937	stopwatch = false;
938}
939
940function enableStopwatchPick() {
941	$j('#timeBox .timer-controls-btn').attr('disabled', false).on({
942		click: function(e) {
943			selectTimerSetting($j(e.currentTarget).data('value'));
944		},
945		keyup: function(e) {
946			if (e.keycode === 13) {
947				selectTimerSetting($j(e.currentTarget).data('value'));
948			}
949		},
950	});
951}
952
953function disableStopwatchPick() {
954	$j('#timeBox .timer-controls-btn').attr('disabled', true).off('click keyup');
955}
956
957function shuffle(array) {
958    var tmp, current, top = array.length;
959
960    if(top) while(--top) {
961        current = Math.floor(Math.random() * (top + 1));
962        tmp = array[current];
963        array[current] = array[top];
964        array[top] = tmp;
965    }
966
967    return array;
968}
969
970function colorLum(hex, lum) {
971
972	hex = String(hex).replace(/[^0-9a-f]/gi, '');
973	if (hex.length < 6) {
974		hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
975	}
976	lum = lum || 0;
977	var rgb = "#", c, i;
978	for (i = 0; i < 3; i++) {
979		c = parseInt(hex.substr(i*2,2), 16);
980		c = Math.round(Math.min(Math.max(0, c + (c * lum)), 255)).toString(16);
981		rgb += ("00"+c).substr(c.length);
982	}
983	return rgb;
984}
985
986function getDarkerColor(color)
987{
988	return colorLum(color, -0.2);
989}
990
991function restoreFocus() {
992	const isSporcleAppView = (window._payload || {}).is_flagship_app_view;
993
994	if (isSporcleAppView) {
995		// No in-page ads in the app so don't run. Does this chunk of code interact with the code I added
996		// to sporcle/build/includes/sporcle-ui/game/play/common.js that pulls focus away quickly
997		// sometimes causing an infinite loop? What's causing the infinite loop?
998		//
999		// Regardless, the app doesn't have in-page ads so we don't ever need to run restoreFocus for it
1000		return;
1001	}
1002
1003	// If the active element is inside an iframe (that is not inside #embedMedia) then return focus to the game input
1004
1005	if (document.activeElement.getAttribute('id') === 'gameinput') {
1006		return;
1007	}
1008
1009	var iframeParent = document.activeElement.closest('iframe');
1010
1011	if (!iframeParent) {
1012		return;
1013	}
1014
1015	if (iframeParent.closest('#embedMedia')) {
1016		return;
1017	}
1018
1019	$j('#gameinput').trigger('focus');
1020}

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.