PageSourceSearch

https://speakworldlanguages.github.io/js_reusables/js_for_info_boxes_in_lessons.js

js speakworldlanguages.github.io collected 2026-10-03 09:59:02 UTC 37,831 bytes, 557 lines download raw bytes

1"use strict";
2// Code written by Manheart Earthman=B. A. Bilgekılınç Topraksoy=土本 智一勇夫剛志
3// This file MAY NOT BE MODIFIED WITHOUT CONSENT i.e. OFFICIAL AUTHORIZATION
4
5/*-----*/
6// TYPE1: Generic info box » Watch how 1 of 2 possible texts is injected into the button
7
8// Function that creates a div for [special case/unique feature/untranslatable thing] info NOTIFICATIONS
9/* VARIABLES AND CONSTANTS*/
10const putNotificationTxtIntoThisP1 = document.createElement("P");
11const putNotificationTxtIntoThisP2 = document.createElement("P");
12const putNotificationTxtIntoThisP3 = document.createElement("P");
13const okButtonToCloseInfoBoxType1 = document.createElement("DIV");
14const okButtonToCloseInfoBoxType1AmidLesson = document.createElement("DIV");
15const okButtonToCloseInfoBoxType1BeforeSpeechRecognition = document.createElement("DIV");
16// Put something like [OK], [Got it], [I see], [Oh really?], [Wow], [That's interesting] etc into the button.
17let okTexts = "✔|✔"; // Default content of the OK box is a "tick ✔" mark to be shown in case fetch fails
18okButtonToCloseInfoBoxType1.innerHTML = okTexts.split("|")[0];
19okButtonToCloseInfoBoxType1AmidLesson.innerHTML = okTexts.split("|")[0];
20okButtonToCloseInfoBoxType1BeforeSpeechRecognition.innerHTML = okTexts.split("|")[0];
21const pathOfOkCloseTheBox = "/user_interface/text/"+userInterfaceLanguage+"/0lesson-ok_i_understand.txt";
22fetch(pathOfOkCloseTheBox,myHeaders).then(function(response){return response.text();}).then(function(contentOfTheTxtFile){ okTexts=contentOfTheTxtFile; assignOKButtonText(); });
23function assignOKButtonText() {
24  if(Math.random()<0.5) { // Heads or tails
25    okButtonToCloseInfoBoxType1.innerHTML = okTexts.split("|")[0];
26    okButtonToCloseInfoBoxType1AmidLesson.innerHTML = okTexts.split("|")[1];
27    okButtonToCloseInfoBoxType1BeforeSpeechRecognition.innerHTML = okTexts.split("|")[0];
28  } else {
29    okButtonToCloseInfoBoxType1.innerHTML = okTexts.split("|")[1];
30    okButtonToCloseInfoBoxType1AmidLesson.innerHTML = okTexts.split("|")[0];
31    okButtonToCloseInfoBoxType1BeforeSpeechRecognition.innerHTML = okTexts.split("|")[0];
32  }
33}
34let popUpNotificationType1Sound;
35let dismissNotificationType1Sound;
36window.addEventListener("DOMContentLoaded",loadNotificationType1Sounds,{once:true});
37function loadNotificationType1Sounds() {
38  // soundFileFormat exists in js_for_all_iframed_lesson_htmls where it is copied from the parent in js_for_different_browsers_and_devices
39  popUpNotificationType1Sound = new parent.Howl({  src: ["/user_interface/sounds/notification1_appear."+soundFileFormat]  });
40  dismissNotificationType1Sound = new parent.Howl({  src: ["/user_interface/sounds/notification1_close."+soundFileFormat]  });
41  // NO BIG DEAL IF: These sounds are loaded even if they are not used in the lesson
42}
43window.addEventListener("beforeunload",unloadNotificationType1Sounds,{once:true});
44function unloadNotificationType1Sounds() {
45  popUpNotificationType1Sound.unload();
46  dismissNotificationType1Sound.unload();
47}
48
49/*Info box with only one [OK] button to show at the beginning of the lesson*/
50function createAndHandleInfoBoxType1BeforeLessonStarts() {
51  popUpNotificationType1Sound.play();
52  const notificationBoxContainer1 = document.createElement("DIV");
53  notificationBoxContainer1.classList.add("notificationBG"); // See css_for_info_boxes_in_lessons
54  document.body.appendChild(notificationBoxContainer1);
55  const notificationBoxItself = document.createElement("DIV");
56  notificationBoxItself.classList.add("notificationRoundedBox"); // See css_for_info_boxes_in_lessons
57  notificationBoxContainer1.appendChild(notificationBoxItself);
58
59  notificationBoxItself.appendChild(putNotificationTxtIntoThisP1);
60
61  okButtonToCloseInfoBoxType1.classList.add("okButtonUnderNotification"); // See css_for_info_boxes_in_lessons
62  if (needLatinFonts) {
63    okButtonToCloseInfoBoxType1.style.fontFamily = '"Oxanium SemiBold", sans-serif';
64    notificationBoxItself.classList.add("textAlignJustifyLTR","latinLineHeightAndLetterSpacing"); // See css_for_every_single_html
65    putNotificationTxtIntoThisP1.classList.add("latinLineHeightAndLetterSpacing"); // See css_for_every_single_html
66  }
67  if (needHitoicJapaneseFonts) {
68    notificationBoxItself.classList.add("textAlignLeft","cjkLineHeightAndLetterSpacing"); // See css_for_every_single_html
69    putNotificationTxtIntoThisP1.classList.add("toUseWBR_withCJK","cjkLineHeightAndLetterSpacing"); // See css_for_every_single_html
70  }
71  notificationBoxItself.appendChild(okButtonToCloseInfoBoxType1);
72
73
74  if (deviceDetector.isMobile) { okButtonToCloseInfoBoxType1.addEventListener("touchstart",okButtonIsClickedToStartLesson); }
75  else { okButtonToCloseInfoBoxType1.addEventListener("mousedown",okButtonIsClickedToStartLesson); }
76  function okButtonIsClickedToStartLesson(event) { event.preventDefault(); event.stopPropagation();
77    dismissNotificationType1Sound.play();
78    notificationBoxContainer1.classList.add("addThisToAButtonForPlayStationStyleClick"); // See css_for_every_single_html_css
79    setTimeout(function () {     notificationBoxContainer1.parentNode.removeChild(notificationBoxContainer1);     },1000); // The animation completes in 600ms
80    if (typeof startTheLesson === "function") {
81      setTimeout(function () {     startTheLesson();     }, 1500);
82    } else { parent.console.error("Error: createAndHandleInfoBoxType1BeforeLessonStarts() needs startTheLesson() function which doesn't exist???"); }
83  }
84}
85
86/*Info box with only one [OK] button to show in the middle or at the end of the lesson*/
87function createAndHandleInfoBoxType1AmidLesson() {
88  popUpNotificationType1Sound.play();
89  const notificationBoxContainer2 = document.createElement("DIV");
90  notificationBoxContainer2.classList.add("notificationBG"); // See css_for_info_boxes_in_lessons
91  document.body.appendChild(notificationBoxContainer2);
92  const notificationBoxItself2 = document.createElement("DIV");
93  notificationBoxItself2.classList.add("notificationRoundedBox"); // See css_for_info_boxes_in_lessons
94  notificationBoxContainer2.appendChild(notificationBoxItself2);
95
96  notificationBoxItself2.appendChild(putNotificationTxtIntoThisP2);
97
98  okButtonToCloseInfoBoxType1AmidLesson.classList.add("okButtonUnderNotification"); // See css_for_info_boxes_in_lessons
99  if (needLatinFonts) {
100    okButtonToCloseInfoBoxType1AmidLesson.style.fontFamily = '"Oxanium SemiBold", sans-serif';
101    notificationBoxItself2.classList.add("textAlignJustifyLTR","latinLineHeightAndLetterSpacing"); // See css_for_every_single_html
102    putNotificationTxtIntoThisP2.classList.add("latinLineHeightAndLetterSpacing"); // See css_for_every_single_html
103  }
104  if (needHitoicJapaneseFonts) {
105    notificationBoxItself2.classList.add("textAlignLeft","cjkLineHeightAndLetterSpacing"); // See css_for_every_single_html
106    putNotificationTxtIntoThisP2.classList.add("toUseWBR_withCJK","cjkLineHeightAndLetterSpacing"); // See css_for_every_single_html
107  }
108  notificationBoxItself2.appendChild(okButtonToCloseInfoBoxType1AmidLesson);
109
110  if (deviceDetector.isMobile) { okButtonToCloseInfoBoxType1AmidLesson.addEventListener("touchstart",okButtonIsClickedToContinueLesson); }
111  else { okButtonToCloseInfoBoxType1AmidLesson.addEventListener("mousedown",okButtonIsClickedToContinueLesson); }
112  function okButtonIsClickedToContinueLesson(event) { event.preventDefault(); event.stopPropagation();
113    dismissNotificationType1Sound.play();
114    notificationBoxContainer2.classList.add("addThisToAButtonForPlayStationStyleClick"); // See css_for_every_single_html_css
115    setTimeout(function () {     notificationBoxContainer2.parentNode.removeChild(notificationBoxContainer2);  },1000); // The animation completes in 600ms
116    if (typeof continueLesson === "function") {
117      setTimeout(function () {     continueLesson();     }, 1500);
118    } else { parent.console.error("Error: createAndHandleInfoBoxType1AmidLesson() needs continueLesson() function which doesn't exist???"); }
119  }
120}
121
122/*Info box with only one [OK] button to show just before speech recognition*/
123function createAndHandleInfoBoxType1JustBeforeSpeechRecognition() {
124  popUpNotificationType1Sound.play();
125  const notificationBoxContainer3 = document.createElement("DIV");
126  notificationBoxContainer3.classList.add("notificationBG"); // See css_for_info_boxes_in_lessons
127  document.body.appendChild(notificationBoxContainer3);
128  const notificationBoxItself3 = document.createElement("DIV");
129  notificationBoxItself3.classList.add("notificationRoundedBox"); // See css_for_info_boxes_in_lessons
130  notificationBoxContainer3.appendChild(notificationBoxItself3);
131
132  notificationBoxItself3.appendChild(putNotificationTxtIntoThisP3);
133
134  okButtonToCloseInfoBoxType1BeforeSpeechRecognition.classList.add("okButtonUnderNotification"); // See css_for_info_boxes_in_lessons
135  if (needLatinFonts) {
136    okButtonToCloseInfoBoxType1BeforeSpeechRecognition.style.fontFamily = '"Oxanium SemiBold", sans-serif';
137    notificationBoxItself3.classList.add("textAlignJustifyLTR","latinLineHeightAndLetterSpacing"); // See css_for_every_single_html
138    putNotificationTxtIntoThisP3.classList.add("latinLineHeightAndLetterSpacing"); // See css_for_every_single_html
139  }
140  if (needHitoicJapaneseFonts) {
141    notificationBoxItself3.classList.add("textAlignLeft","cjkLineHeightAndLetterSpacing"); // See css_for_every_single_html
142    putNotificationTxtIntoThisP3.classList.add("toUseWBR_withCJK","cjkLineHeightAndLetterSpacing"); // See css_for_every_single_html
143  }
144  notificationBoxItself3.appendChild(okButtonToCloseInfoBoxType1BeforeSpeechRecognition);
145
146  if (deviceDetector.isMobile) { okButtonToCloseInfoBoxType1BeforeSpeechRecognition.addEventListener("touchstart",okButtonIsClickedToContinueLesson); }
147  else { okButtonToCloseInfoBoxType1BeforeSpeechRecognition.addEventListener("mousedown",okButtonIsClickedToContinueLesson); }
148  function okButtonIsClickedToContinueLesson(event) { event.preventDefault(); event.stopPropagation();
149    dismissNotificationType1Sound.play();
150    notificationBoxContainer3.classList.add("addThisToAButtonForPlayStationStyleClick"); // See css_for_every_single_html_css
151    setTimeout(function () {     notificationBoxContainer3.parentNode.removeChild(notificationBoxContainer3);  },1000); // The animation completes in 600ms
152    if (typeof display_nowItsYourTurn_animation === "function") {
153      setTimeout(function () {     display_nowItsYourTurn_animation();     }, 1500);
154    } else { parent.console.error("Error: createAndHandleInfoBoxType1JustBeforeSpeechRecognition() needs display_nowItsYourTurn_animation() function which doesn't exist???"); }
155  }
156}
157
158/*-----*/
159// TYPE2 used to be the deprecated [choose input device] thingy.
160/*-----*/
161
162/*-----*/
163// TYPE3 LISTEN-MANY-TIMES-BOX.
164// Function that creates a div box for pronunciation (Hitonokaochan; formerly wavesurfer) NOTIFICATIONS
165/* VARIABLES AND CONSTANTS*/
166const putVocabularyTxtIntoThisP1 = document.createElement("P"); const putVocabularyTxtIntoThisP1OUTRO = document.createElement("P");
167const putVocabularyTxtIntoThisP2 = document.createElement("P"); const putVocabularyTxtIntoThisP2OUTRO = document.createElement("P");
168let txtStringForP1 = null; let txtStringForP1OUTRO = null;
169let txtStringForP2 = null; let txtStringForP2OUTRO = null;
170function handleP1P2ActualText(receivedTxt) { // Called when fetch gets the file » See the lesson's own js
171  txtStringForP1=receivedTxt.split("|")[0]; putVocabularyTxtIntoThisP1.innerHTML= txtStringForP1;
172  txtStringForP2=receivedTxt.split("|")[1]; putVocabularyTxtIntoThisP2.innerHTML= txtStringForP2;
173}
174function handleP1P2ActualTextOUTRO(receivedTxt) { // Called when fetch gets the file » See the lesson's own js
175  txtStringForP1OUTRO=receivedTxt.split("|")[0]; putVocabularyTxtIntoThisP1OUTRO.innerHTML= txtStringForP1OUTRO;
176  txtStringForP2OUTRO=receivedTxt.split("|")[1]; putVocabularyTxtIntoThisP2OUTRO.innerHTML= txtStringForP2OUTRO;
177}
178const listenButtonOfTheVocabulary = document.createElement("DIV");
179const startButtonToCloseTheVocabulary = document.createElement("DIV");
180let listenButtonTxt, listenAgainButtonTxt, startButtonTxt, nextButtonTxt; // [Next] button replaces [Start] button if outro is enabled by the second parameter
181const listenBoxButton1Button2Path = "/user_interface/text/"+userInterfaceLanguage+"/0lesson-vocabulary_button1_button2.txt";
182let primaryFetchIsStillTryingToGetTheFile = true;
183fetch(listenBoxButton1Button2Path,myHeaders).then(function(response){return response.text();}).then(function(contentOfTheTxtFile){
184  listenButtonTxt = contentOfTheTxtFile.split("|")[0];
185  listenAgainButtonTxt = contentOfTheTxtFile.split("|")[1];
186  startButtonTxt = contentOfTheTxtFile.split("|")[2];
187  nextButtonTxt = contentOfTheTxtFile.split("|")[3];
188}).finally(  ()=>{ primaryFetchIsStillTryingToGetTheFile = false; }  );
189// -
190let popUpVocabularySound;
191let dismissVocabularySound;
192window.addEventListener("DOMContentLoaded",loadVocabularyBoxButtonSounds,{once:true});
193function loadVocabularyBoxButtonSounds() {
194  // soundFileFormat exists in js_for_all_iframed_lesson_htmls where it is copied from the parent in js_for_different_browsers_and_devices
195  popUpVocabularySound = new parent.Howl({  src: ["/user_interface/sounds/notification3_appear."+soundFileFormat]  });
196  dismissVocabularySound = new parent.Howl({  src: ["/user_interface/sounds/notification3_close."+soundFileFormat]  });
197  // NO BIG DEAL IF: These sounds are loaded even if they are not used in the lesson
198}
199window.addEventListener("beforeunload",unloadVocabularyBoxButtonSounds,{once:true});
200function unloadVocabularyBoxButtonSounds() {
201  popUpVocabularySound.unload();
202  dismissVocabularySound.unload();
203}
204
205/*FUNCTION DECLARATION*/
206// IN THE PAST WE SAID: IN THE FUTURE WE WILL HOPEFULLY USE RHUBARB LIP-SYNC TO PLAY AN ANIMATED MOUTH ILLUSTRATION ALONG WITH WAVESURFER
207// AND NOW THIS IS APRIL 2024: We have deprecated wavesurfer entirely and have replaced it with Hitonokaochan
208// See » https://github.com/DanielSWolf/rhubarb-lip-sync
209
210function createAndHandleListenManyTimesBox(pathToTheAudio1,pathToJSON1,pathToTheAudio2,pathToJSON2,pathToTheAudio3,pathToJSON3,isLessonOutro) {
211  popUpVocabularySound.play();
212  const vocabularyBoxContainer = document.createElement("DIV"); // Maybe a dark theme will look nice
213  vocabularyBoxContainer.classList.add("vocabularyBG"); // See css_for_info_boxes_in_lessons
214  document.body.appendChild(vocabularyBoxContainer);
215  const vocabularyBoxItself = document.createElement("DIV");
216  vocabularyBoxItself.classList.add("vocabularyRoundedBox"); // See css_for_info_boxes_in_lessons
217  if (needLatinFonts) {
218    vocabularyBoxItself.classList.add("textAlignJustifyLTR","latinLineHeightAndLetterSpacing"); // See css_for_every_single_html
219    putVocabularyTxtIntoThisP1.classList.add("latinLineHeightAndLetterSpacing"); // See css_for_every_single_html
220    putVocabularyTxtIntoThisP2.classList.add("latinLineHeightAndLetterSpacing"); // See css_for_every_single_html
221    putVocabularyTxtIntoThisP1OUTRO.classList.add("latinLineHeightAndLetterSpacing");
222    putVocabularyTxtIntoThisP2OUTRO.classList.add("latinLineHeightAndLetterSpacing");
223  }
224  if (needHitoicJapaneseFonts) {
225    vocabularyBoxItself.classList.add("textAlignLeft","cjkLineHeightAndLetterSpacing"); // See css_for_every_single_html
226    putVocabularyTxtIntoThisP1.classList.add("toUseWBR_withCJK","cjkLineHeightAndLetterSpacing"); // See css_for_every_single_html
227    putVocabularyTxtIntoThisP2.classList.add("toUseWBR_withCJK","cjkLineHeightAndLetterSpacing"); // See css_for_every_single_html
228    putVocabularyTxtIntoThisP1OUTRO.classList.add("toUseWBR_withCJK","cjkLineHeightAndLetterSpacing");
229    putVocabularyTxtIntoThisP2OUTRO.classList.add("toUseWBR_withCJK","cjkLineHeightAndLetterSpacing");
230  }
231  vocabularyBoxContainer.appendChild(vocabularyBoxItself);
232  //
233  if (!txtStringForP1) { // If download has failed or not finished yet
234    putVocabularyTxtIntoThisP1.innerHTML="📄"; //📄
235    putVocabularyTxtIntoThisP2.innerHTML="⏳"; //⏳
236  }
237  if (!txtStringForP1OUTRO) { // If download has failed or not finished yet
238    putVocabularyTxtIntoThisP1OUTRO.innerHTML="📄"; //📄
239    putVocabularyTxtIntoThisP2OUTRO.innerHTML="⏳"; //⏳
240  }
241
242  // APPEND txt1
243  if (!isLessonOutro) {    vocabularyBoxItself.appendChild(putVocabularyTxtIntoThisP1);  }
244  else {    vocabularyBoxItself.appendChild(putVocabularyTxtIntoThisP1OUTRO);  }
245
246  // -
247  let introVocabulary1,introVocabulary2,introVocabulary3; let introSound1IsReady = false; let introSound2IsReady = false; let introSound3IsReady = false; let introSoundIsPlaying = false;
248  let outroVocabulary1,outroVocabulary2,outroVocabulary3; let outroSound1IsReady = false; let outroSound2IsReady = false; let outroSound3IsReady = false; let outroSoundIsPlaying = false;
249  if (!isLessonOutro) {
250    introVocabulary1 = new parent.Howl({  src: [pathToTheAudio1]  });
251    introVocabulary1.once('load', function(){  introSound1IsReady = true; getJSON(pathToJSON1,1);  });
252    introVocabulary1.on('end', function(){  introSoundIsPlaying = false; pseudoReactivationOfPlayButton();  });
253
254    introVocabulary2 = new parent.Howl({  src: [pathToTheAudio2]  });
255    introVocabulary2.once('load', function(){  introSound2IsReady = true; getJSON(pathToJSON2,2);  });
256    introVocabulary2.on('end', function(){  introSoundIsPlaying = false; pseudoReactivationOfPlayButton();  });
257
258    introVocabulary3 = new parent.Howl({  src: [pathToTheAudio3]  });
259    introVocabulary3.once('load', function(){  introSound3IsReady = true; getJSON(pathToJSON3,3);  });
260    introVocabulary3.on('end', function(){  introSoundIsPlaying = false; pseudoReactivationOfPlayButton();  });
261  }
262  else {
263    outroVocabulary1 = new parent.Howl({  src: [pathToTheAudio1]  });
264    outroVocabulary1.once('load', function(){  outroSound1IsReady = true; getJSON(pathToJSON1,1);  });
265    outroVocabulary1.on('end', function(){  outroSoundIsPlaying = false; pseudoReactivationOfPlayButton();  });
266
267    outroVocabulary2 = new parent.Howl({  src: [pathToTheAudio2]  });
268    outroVocabulary2.once('load', function(){  outroSound2IsReady = true; getJSON(pathToJSON2,2);  });
269    outroVocabulary2.on('end', function(){  outroSoundIsPlaying = false; pseudoReactivationOfPlayButton();  });
270
271    outroVocabulary3 = new parent.Howl({  src: [pathToTheAudio3]  });
272    outroVocabulary3.once('load', function(){  outroSound3IsReady = true; getJSON(pathToJSON3,3);  });
273    outroVocabulary3.on('end', function(){  outroSoundIsPlaying = false; pseudoReactivationOfPlayButton();  });
274  }
275  // -
276  window.addEventListener("beforeunload",unloadListenBoxVocabularySounds,{once:true});
277  function unloadListenBoxVocabularySounds() {
278    if (introSound1IsReady) { introVocabulary1.unload(); } if (introSound2IsReady) { introVocabulary2.unload(); } if (introSound3IsReady) { introVocabulary3.unload(); }
279    if (outroSound1IsReady) { outroVocabulary1.unload(); } if (outroSound2IsReady) { outroVocabulary2.unload(); } if (outroSound3IsReady) { outroVocabulary3.unload(); }
280  }
281  // DEPRECATE: Get json file that is supposed to be named exactly the same as the audio file
282  // Deprecate: const theFileExtensionIsRemoved_1 = pathToTheAudio1.split(".")[0];
283  // Deprecate: const jsonFilePath_1 = theFileExtensionIsRemoved_1 + ".json"; // Cached by 112.js 114.js etc in js_for_cache_handling
284  let lipSyncJSON_1 = null; let lipSyncJSON_2 = null; let lipSyncJSON_3 = null;
285  //INSTEAD OF STARTING HIDDEN let's show an animated «loading» text
286  pseudoDeactivationOfPlayButton(); //Same as » listenButtonOfTheVocabulary.style.opacity = "0.4"; // Low opacity until fetch gets json // Here used to be listenButtonOfTheVocabulary.style.visibility = "hidden"; // Its class is added down below
287  // NOTE THAT: Nothing will happen if user clicks|touches the button before the sounds are loaded because the playintroVocabulary functions will check if sound is loaded before trying to play
288  let to1,to2,to3,to4,to5,to6;
289  function getJSON(jsonFilePath,whichOne) { // Will fire only if the sound has successfully loaded
290    fetch(jsonFilePath).then(response => {  if (!response.ok) { throw new Error('Network response was not ok'); }  return response.json();  })
291    .then(data => {
292      switch (whichOne) {
293        case 1: lipSyncJSON_1 = data; break;
294        case 2: lipSyncJSON_2 = data; break;
295        case 3: lipSyncJSON_3 = data; break;
296        default:
297      }
298    }).catch(error => { parent.console.error('Fetch error:', error); parent.console.warn("Was trying to get:\n" + jsonFilePath); })
299    .finally(()=>{ // Even if json fails the app will proceed and play sounds without lip sync animation
300      if (introSound1IsReady || outroSound1IsReady) {
301        //stop the loading animation by clearing all timeouts
302        clearTimeout(to1); clearTimeout(to2); clearTimeout(to3); clearTimeout(to4); clearTimeout(to5); clearTimeout(to6);
303        // Put "Listen" text into the button by overwriting the last state of the loading animation
304        listenButtonOfTheVocabulary.innerHTML = listenButtonTxt;
305        // Before the implementation of animated text, here used to be » listenButtonOfTheVocabulary.style.visibility = "visible";
306        pseudoReactivationOfPlayButton(); // Same as » listenButtonOfTheVocabulary.style.opacity = "1";
307      } else {
308        // THIS IS IMPOSSIBLE since get getJSON will never fire without sound being fully loaded by Howler first
309      }
310    });
311  }
312
313  // --
314  const hitonokaochanContainer = document.createElement("DIV");
315  hitonokaochanContainer.classList.add("listenBoxHitonokaochan");
316  const hitonokaochanA = document.createElement("IMG"); hitonokaochanA.src = "/user_interface/images/rhubarb_lip_sync/a.webp"; hitonokaochanA.style.display = "none"; hitonokaochanContainer.appendChild(hitonokaochanA);
317  const hitonokaochanB = document.createElement("IMG"); hitonokaochanB.src = "/user_interface/images/rhubarb_lip_sync/b.webp"; hitonokaochanB.style.display = "none"; hitonokaochanContainer.appendChild(hitonokaochanB);
318  const hitonokaochanC = document.createElement("IMG"); hitonokaochanC.src = "/user_interface/images/rhubarb_lip_sync/c.webp"; hitonokaochanC.style.display = "none"; hitonokaochanContainer.appendChild(hitonokaochanC);
319  const hitonokaochanD = document.createElement("IMG"); hitonokaochanD.src = "/user_interface/images/rhubarb_lip_sync/d.webp"; hitonokaochanD.style.display = "none"; hitonokaochanContainer.appendChild(hitonokaochanD);
320  const hitonokaochanE = document.createElement("IMG"); hitonokaochanE.src = "/user_interface/images/rhubarb_lip_sync/e.webp"; hitonokaochanE.style.display = "none"; hitonokaochanContainer.appendChild(hitonokaochanE);
321  const hitonokaochanF = document.createElement("IMG"); hitonokaochanF.src = "/user_interface/images/rhubarb_lip_sync/f.webp"; hitonokaochanF.style.display = "none"; hitonokaochanContainer.appendChild(hitonokaochanF);
322  const hitonokaochanG = document.createElement("IMG"); hitonokaochanG.src = "/user_interface/images/rhubarb_lip_sync/g.webp"; hitonokaochanG.style.display = "none"; hitonokaochanContainer.appendChild(hitonokaochanG);
323  const hitonokaochanH = document.createElement("IMG"); hitonokaochanH.src = "/user_interface/images/rhubarb_lip_sync/h.webp"; hitonokaochanH.style.display = "none"; hitonokaochanContainer.appendChild(hitonokaochanH);
324  const hitonokaochanN = document.createElement("IMG"); hitonokaochanN.src = "/user_interface/images/rhubarb_lip_sync/n.webp"; hitonokaochanN.style.display = "none"; hitonokaochanContainer.appendChild(hitonokaochanN); // Added on April 21st 2024
325  const hitonokaochanX = document.createElement("IMG"); hitonokaochanX.src = "/user_interface/images/rhubarb_lip_sync/x.webp"; hitonokaochanX.style.display = "block";hitonokaochanContainer.appendChild(hitonokaochanX);
326  const allMouthStates = [hitonokaochanA,hitonokaochanB,hitonokaochanC,hitonokaochanD,hitonokaochanE,hitonokaochanF,hitonokaochanG,hitonokaochanH,hitonokaochanN,hitonokaochanX];
327  // QUESTION: Within startButtonF Hitonokaochan img container must be removed OR NOT? ANSWER: NOT IF we check whether it already exists before adding it
328  if (vocabularyBoxItself.contains(hitonokaochanContainer)) {  } // This way we don't have to remove it inside startButtonF
329  else {  vocabularyBoxItself.appendChild(hitonokaochanContainer);  } // Will add it only when called as intro
330  // -
331  function animateHitonokachan(jsonData) {
332    const mouthCues = jsonData.mouthCues;
333    let counter = 0;
334    mouthCues.forEach(cue => {
335      if (counter>0) { // Skip setting the very first timeout at 0000ms
336        setTimeout(() => {
337          allMouthStates.forEach(frame => { frame.style.display = "none"; }); // Same for all
338          switch (cue.value) {
339            // Rhubarb standard alphabetic reference PLUS the addition of "N" for «nnn» sound
340            case "A":  hitonokaochanA.style.display = "block"; break;
341            case "B":  hitonokaochanB.style.display = "block"; break;
342            case "C":  hitonokaochanC.style.display = "block"; break;
343            case "D":  hitonokaochanD.style.display = "block"; break;
344            case "E":  hitonokaochanE.style.display = "block"; break;
345            case "F":  hitonokaochanF.style.display = "block"; break;
346            case "G":  hitonokaochanG.style.display = "block"; break;
347            case "H":  hitonokaochanH.style.display = "block"; break;
348            case "N":  hitonokaochanN.style.display = "block"; break;
349            case "X":  hitonokaochanX.style.display = "block"; break;
350            // Leavidence Design reference
351            case "MM": hitonokaochanA.style.display = "block"; break;
352            case "SS": hitonokaochanB.style.display = "block"; break;
353            case "EE": hitonokaochanC.style.display = "block"; break;
354            case "AA": hitonokaochanD.style.display = "block"; break;
355            case "OO": hitonokaochanE.style.display = "block"; break;
356            case "UU": hitonokaochanF.style.display = "block"; break;
357            case "FF": hitonokaochanG.style.display = "block"; break;
358            case "LL": hitonokaochanH.style.display = "block"; break;
359            case "NN": hitonokaochanN.style.display = "block"; break;
360            case "XX": hitonokaochanX.style.display = "block"; break;
361
362            default:
363          }
364          // It works!
365        }, cue.start * 1000); // Convert seconds to milliseconds
366      }
367      // -
368      counter++;
369    });
370  }
371
372  function playintroVocabulary1() {
373    if (introSound1IsReady && !introSoundIsPlaying) {
374      introVocabulary1.play(); introSoundIsPlaying = true; pseudoDeactivationOfPlayButton();
375      if (lipSyncJSON_1) { animateHitonokachan(lipSyncJSON_1); } // Handle lip-sync
376    }
377  }
378  function playintroVocabulary2() {
379    if (introSound2IsReady && !introSoundIsPlaying) {
380      introVocabulary2.play(); introSoundIsPlaying = true; pseudoDeactivationOfPlayButton();
381      if (lipSyncJSON_2) { animateHitonokachan(lipSyncJSON_2); } // Handle lip-sync
382    }
383  }
384  function playintroVocabulary3() {
385    if (introSound3IsReady && !introSoundIsPlaying) {
386      introVocabulary3.play(); introSoundIsPlaying = true; pseudoDeactivationOfPlayButton();
387      if (lipSyncJSON_3) { animateHitonokachan(lipSyncJSON_3); } // Handle lip-sync
388    }
389  }
390  function playoutroVocabulary1() {
391    if (outroSound1IsReady && !outroSoundIsPlaying) {
392      outroVocabulary1.play(); outroSoundIsPlaying = true; pseudoDeactivationOfPlayButton();
393      if (lipSyncJSON_1) { animateHitonokachan(lipSyncJSON_1); } // Handle lip-sync
394    }
395  }
396  function playoutroVocabulary2() {
397    if (outroSound2IsReady && !outroSoundIsPlaying) {
398      outroVocabulary2.play(); outroSoundIsPlaying = true; pseudoDeactivationOfPlayButton();
399      if (lipSyncJSON_2) { animateHitonokachan(lipSyncJSON_2); } // Handle lip-sync
400    }
401  }
402  function playoutroVocabulary3() {
403    if (outroSound3IsReady && !outroSoundIsPlaying) {
404      outroVocabulary3.play(); outroSoundIsPlaying = true; pseudoDeactivationOfPlayButton();
405      if (lipSyncJSON_3) { animateHitonokachan(lipSyncJSON_3); } // Handle lip-sync
406    }
407  }
408
409  function pseudoDeactivationOfPlayButton() { listenButtonOfTheVocabulary.style.opacity = "0.4"; } // Event listeners will stay » callback functions will decide if the button will do something or nothing
410  function pseudoReactivationOfPlayButton() { listenButtonOfTheVocabulary.style.opacity = "1"; } // Event listeners will stay » callback functions will decide if the button will do something or nothing
411
412  // APPEND txt2
413  if (!isLessonOutro) {    vocabularyBoxItself.appendChild(putVocabularyTxtIntoThisP2);  }
414  else {    vocabularyBoxItself.appendChild(putVocabularyTxtIntoThisP2OUTRO);  }
415  // Try to fix Safari and Firefox as they ignore text align justify although the container div has it in its class
416  // putVocabularyTxtIntoThisP1.style.textAlign = "justify"; putVocabularyTxtIntoThisP2.style.textAlign = "justify"; // DIDN'T WORK!
417
418  listenButtonOfTheVocabulary.classList.add("buttonsAtTheBottomOfThePronunciationBox"); loadingAnimation(); // &#128259; is a "refresh 🔃" mark
419
420  function loadingAnimation() {
421    listenButtonOfTheVocabulary.innerHTML = "...::...";
422    to1 = setTimeout(function () { listenButtonOfTheVocabulary.innerHTML = "..:··:..";  }, 100);
423    to2 = setTimeout(function () { listenButtonOfTheVocabulary.innerHTML = ".:····:.";  }, 200);
424    to3 = setTimeout(function () { listenButtonOfTheVocabulary.innerHTML = ":······:";  }, 300);
425    to4 = setTimeout(function () { listenButtonOfTheVocabulary.innerHTML = ".:····:.";  }, 400);
426    to5 = setTimeout(function () { listenButtonOfTheVocabulary.innerHTML = "..:··:..";  }, 500);
427    to6 = setTimeout(loadingAnimation, 600);
428  }
429
430  startButtonToCloseTheVocabulary.classList.add("buttonsAtTheBottomOfThePronunciationBox");  startButtonToCloseTheVocabulary.innerHTML = "&#9658;"; // &#127918; is a "gamepad 🎮" mark // &#9658; is a "play ►" mark
431  if (deviceDetector.isMobile) {
432    listenButtonOfTheVocabulary.classList.add("buttonsAtTheBottomOfThePronunciationBoxMOBILE");
433    startButtonToCloseTheVocabulary.classList.add("buttonsAtTheBottomOfThePronunciationBoxMOBILE");
434  } else {
435    listenButtonOfTheVocabulary.classList.add("buttonsAtTheBottomOfThePronunciationBoxDESKTOP");
436    startButtonToCloseTheVocabulary.classList.add("buttonsAtTheBottomOfThePronunciationBoxDESKTOP");
437  }
438  // ---
439  if (listenButtonTxt) { // If the primary fetch (the one that fires at global level even before window load) has already finished downloading the txt file
440    // LET listenButtonTxt STAY READY AND WAIT UNTIL THE SOUNDS AND JSONS LOAD » Before the implementation of animated text here used to be » listenButtonOfTheVocabulary.innerHTML = listenButtonTxt;
441    if (!isLessonOutro) { startButtonToCloseTheVocabulary.innerHTML = startButtonTxt; }
442    else { startButtonToCloseTheVocabulary.innerHTML = nextButtonTxt; }
443  } else { // Restart fetch but this time update buttonTxts as soon as the file is ready
444    if (primaryFetchIsStillTryingToGetTheFile) {
445      // Let it keep working
446    } else {
447      // Retry getting the file one single more time i.e. this is the second and the last try
448      fetch(listenBoxButton1Button2Path,myHeaders).then(function(response){return response.text();}).then(function(contentOfTheTxtFile){
449        listenButtonTxt = contentOfTheTxtFile.split("|")[0]; listenButtonOfTheVocabulary.innerHTML = listenButtonTxt;
450        listenAgainButtonTxt = contentOfTheTxtFile.split("|")[1];
451        if (!isLessonOutro) { startButtonTxt = contentOfTheTxtFile.split("|")[2]; startButtonToCloseTheVocabulary.innerHTML = startButtonTxt; }
452        else { nextButtonTxt = contentOfTheTxtFile.split("|")[3]; startButtonToCloseTheVocabulary.innerHTML = nextButtonTxt; }
453      });
454    }
455  }
456  const twoButtonsContainer = document.createElement("DIV"); twoButtonsContainer.classList.add("vocabularyButtonsContainer");
457  twoButtonsContainer.appendChild(listenButtonOfTheVocabulary);
458  vocabularyBoxItself.appendChild(twoButtonsContainer);
459  // Listen button
460  if (deviceDetector.isMobile) {   listenButtonOfTheVocabulary.addEventListener("touchstart",playButtonF);   }
461  else {   listenButtonOfTheVocabulary.addEventListener("mousedown",playButtonF);
462    parent.window.addEventListener("keydown",checkIfSpaceKeyWasPressed); window.addEventListener("keydown",checkIfSpaceKeyWasPressed);
463    parent.window.addEventListener("keyup",checkIfSpaceKeyWasReleased);  window.addEventListener("keyup",checkIfSpaceKeyWasReleased);
464  }
465  let clickOrTouchCount=0;
466  function playButtonF(event) { event.preventDefault(); event.stopPropagation();
467    if (introSoundIsPlaying || outroSoundIsPlaying) {  return;  } // Prevent multiple instances of the sound overlap and be heard at the same time
468    // Use stopPropagation instead of parent.preventTouchConflictWithTheSlidingNavMenu(listenButtonOfTheVocabulary); // Exists in js_for_the_sliding_navigation_menu
469    clickOrTouchCount++;
470    // -
471    checkClickTouchModulus(clickOrTouchCount);
472  }
473  function checkClickTouchModulus(nthPlay) {
474    // -
475    if (nthPlay==1) {
476      if (listenAgainButtonTxt) { listenButtonOfTheVocabulary.innerHTML = listenAgainButtonTxt; } // Change button innerHTML from [Listen] to [Listen again]
477    } else if (nthPlay==2) {
478      // No change
479    } else if (nthPlay==3) {
480      startButtonToCloseTheVocabulary.classList.add("startButtonUnderListenBox"); twoButtonsContainer.appendChild(startButtonToCloseTheVocabulary); // Reveal the [Start] button
481    }
482    if (nthPlay%3 == 1) {    if (!isLessonOutro) {  playintroVocabulary1();  } else {  playoutroVocabulary1();  }    }
483    if (nthPlay%3 == 2) {    if (!isLessonOutro) {  playintroVocabulary2();  } else {  playoutroVocabulary2();  }    }
484    if (nthPlay%3 == 0) {    if (!isLessonOutro) {  playintroVocabulary3();  } else {  playoutroVocabulary3();  }    }
485  }
486  let spaceKeyIsBeingHeldDown = false;
487  function checkIfSpaceKeyWasPressed(event) { event.preventDefault(); // Can't be too safe
488    if (!spaceKeyIsBeingHeldDown) {
489      if (event.key == " " || event.code == "Space") {
490        spaceKeyIsBeingHeldDown = true;
491        // -
492        if (introSoundIsPlaying || outroSoundIsPlaying) {  return;  } // Prevent multiple instances of the sound overlap and be heard at the same time
493        // -
494        clickOrTouchCount++;
495        // -
496        checkClickTouchModulus(clickOrTouchCount);
497      }
498    }
499  }
500  function checkIfSpaceKeyWasReleased(event) { event.preventDefault(); // Can't be too safe
501    if (spaceKeyIsBeingHeldDown) {
502      if (event.key == " " || event.code == "Space") {
503        spaceKeyIsBeingHeldDown = false;
504      }
505    }
506  }
507  // Start button
508  if (deviceDetector.isMobile) {   startButtonToCloseTheVocabulary.addEventListener("touchstart",startButtonF);   }
509  else {   startButtonToCloseTheVocabulary.addEventListener("mousedown",startButtonF);   }
510  let lastPointerX, lastPointerY;
511  function startButtonF(event) { event.preventDefault(); event.stopPropagation();
512    // Use stopPropagation instead of parent.preventTouchConflictWithTheSlidingNavMenu(startButtonToCloseTheVocabulary); // Exists in js_for_the_sliding_navigation_menu
513    dismissVocabularySound.play();
514    vocabularyBoxItself.style.animationName = "vocabularyListenBoxDisappears";
515    vocabularyBoxContainer.style.animationName = "vocabularyPronunciationDisappearsBG";
516    setTimeout(function(){
517      // DEPRECATED document.body.removeChild(vocabularyBoxContainer);
518      startButtonToCloseTheVocabulary.remove();
519      listenButtonOfTheVocabulary.remove();
520      twoButtonsContainer.remove();
521      if (vocabularyBoxItself.contains(putVocabularyTxtIntoThisP2)) { putVocabularyTxtIntoThisP2.remove(); }
522      if (vocabularyBoxItself.contains(putVocabularyTxtIntoThisP2OUTRO)) { putVocabularyTxtIntoThisP2OUTRO.remove(); }
523      // No need to remove hitonokaochanContainer when using «contains()»
524      if (vocabularyBoxItself.contains(putVocabularyTxtIntoThisP1)) { putVocabularyTxtIntoThisP1.remove(); }
525      if (vocabularyBoxItself.contains(putVocabularyTxtIntoThisP1OUTRO)) { putVocabularyTxtIntoThisP1OUTRO.remove(); }
526      vocabularyBoxItself.remove();
527      vocabularyBoxContainer.remove();
528    },501);
529
530    if (deviceDetector.device == "desktop") {
531      lastPointerX = event.clientX;
532      lastPointerY = event.clientY;
533    } else {
534      lastPointerX = event.touches[0].clientX;
535      lastPointerY = event.touches[0].clientY;
536    }
537    // ---
538    if (!isLessonOutro) { // CASE: Lesson intro
539      if (typeof vocabularyBoxIsClosed === "function") {
540        setTimeout(function () {     vocabularyBoxIsClosed(lastPointerX,lastPointerY);     }, 500);
541      } else { parent.console.error("Error: vocabularyBoxIsClosed() function doesn't exist???"); }
542    } else { // CASE: Lesson outro
543      if (typeof vocabularyBoxIsClosed_LESSON_OUTRO === "function") {
544        setTimeout(function () {     vocabularyBoxIsClosed_LESSON_OUTRO(lastPointerX,lastPointerY);     }, 500);
545      } else { parent.console.error("Error: vocabularyBoxIsClosed_LESSON_OUTRO() function doesn't exist???"); }
546    }
547
548    // -
549    listenButtonOfTheVocabulary.removeEventListener("touchstart",playButtonF);
550    listenButtonOfTheVocabulary.removeEventListener("mousedown",playButtonF);
551    startButtonToCloseTheVocabulary.removeEventListener("touchstart",startButtonF);
552    startButtonToCloseTheVocabulary.removeEventListener("mousedown",startButtonF);
553    parent.window.removeEventListener("keydown",checkIfSpaceKeyWasPressed); window.removeEventListener("keydown",checkIfSpaceKeyWasPressed);
554    parent.window.removeEventListener("keyup",checkIfSpaceKeyWasReleased);  window.removeEventListener("keyup",checkIfSpaceKeyWasReleased);
555  } // END OF startButtonF
556}
557// END OF createAndHandleListenManyTimesBox DEFINITION

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.