munchboka-edutools 0.1.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of munchboka-edutools might be problematic. Click here for more details.

Files changed (149) hide show
  1. munchboka_edutools/__init__.py +184 -0
  2. munchboka_edutools/_plotmath_shim.py +126 -0
  3. munchboka_edutools/_version.py +2 -0
  4. munchboka_edutools/directives/__init__.py +1 -0
  5. munchboka_edutools/directives/admonitions.py +389 -0
  6. munchboka_edutools/directives/cas_popup.py +272 -0
  7. munchboka_edutools/directives/clear.py +103 -0
  8. munchboka_edutools/directives/dialogue.py +137 -0
  9. munchboka_edutools/directives/escape_room.py +296 -0
  10. munchboka_edutools/directives/factor_tree.py +549 -0
  11. munchboka_edutools/directives/ggb.py +209 -0
  12. munchboka_edutools/directives/ggb_icon.py +105 -0
  13. munchboka_edutools/directives/ggb_popup.py +165 -0
  14. munchboka_edutools/directives/horner.py +324 -0
  15. munchboka_edutools/directives/interactive_code.py +75 -0
  16. munchboka_edutools/directives/jeopardy.py +252 -0
  17. munchboka_edutools/directives/multi_plot.py +1126 -0
  18. munchboka_edutools/directives/pair_puzzle.py +191 -0
  19. munchboka_edutools/directives/parsons.py +109 -0
  20. munchboka_edutools/directives/plot.py +3105 -0
  21. munchboka_edutools/directives/poly_icon.py +111 -0
  22. munchboka_edutools/directives/polydiv.py +344 -0
  23. munchboka_edutools/directives/popup.py +245 -0
  24. munchboka_edutools/directives/quiz.py +291 -0
  25. munchboka_edutools/directives/signchart.py +516 -0
  26. munchboka_edutools/directives/timed_quiz.py +436 -0
  27. munchboka_edutools/directives/turtle.py +157 -0
  28. munchboka_edutools/static/css/admonitions.css +2012 -0
  29. munchboka_edutools/static/css/cas_popup.css +242 -0
  30. munchboka_edutools/static/css/code_mirror_themes/github_dark_cm.css +112 -0
  31. munchboka_edutools/static/css/code_mirror_themes/github_dark_default_cm.css +40 -0
  32. munchboka_edutools/static/css/code_mirror_themes/github_dark_high_contrast_cm.css +141 -0
  33. munchboka_edutools/static/css/code_mirror_themes/github_light_cm.css +120 -0
  34. munchboka_edutools/static/css/code_mirror_themes/github_light_default_cm.css +108 -0
  35. munchboka_edutools/static/css/code_mirror_themes/one_dark_cm.css +121 -0
  36. munchboka_edutools/static/css/dialogue.css +92 -0
  37. munchboka_edutools/static/css/escapeRoom/escape-room.css +223 -0
  38. munchboka_edutools/static/css/figures.css +274 -0
  39. munchboka_edutools/static/css/general_style.css +74 -0
  40. munchboka_edutools/static/css/github-dark-high-contrast.css +141 -0
  41. munchboka_edutools/static/css/github-dark.css +112 -0
  42. munchboka_edutools/static/css/github-light.css +120 -0
  43. munchboka_edutools/static/css/interactive_code/style.css +575 -0
  44. munchboka_edutools/static/css/interactive_code.css +582 -0
  45. munchboka_edutools/static/css/jeopardy.css +529 -0
  46. munchboka_edutools/static/css/pairPuzzle/style.css +177 -0
  47. munchboka_edutools/static/css/parsons/parsonsPuzzle.css +331 -0
  48. munchboka_edutools/static/css/popup.css +115 -0
  49. munchboka_edutools/static/css/quiz.css +312 -0
  50. munchboka_edutools/static/css/timedQuiz.css +375 -0
  51. munchboka_edutools/static/icons/ggb/mode_evaluate.svg +1 -0
  52. munchboka_edutools/static/icons/ggb/mode_extremum.svg +1 -0
  53. munchboka_edutools/static/icons/ggb/mode_intersect.svg +1 -0
  54. munchboka_edutools/static/icons/ggb/mode_nsolve.svg +1 -0
  55. munchboka_edutools/static/icons/ggb/mode_numeric.svg +1 -0
  56. munchboka_edutools/static/icons/ggb/mode_point.svg +1 -0
  57. munchboka_edutools/static/icons/ggb/mode_solve.svg +1 -0
  58. munchboka_edutools/static/icons/misc/windows-logo.svg +1 -0
  59. munchboka_edutools/static/icons/outline/dark_mode/academic.svg +3 -0
  60. munchboka_edutools/static/icons/outline/dark_mode/backward.svg +3 -0
  61. munchboka_edutools/static/icons/outline/dark_mode/book.svg +3 -0
  62. munchboka_edutools/static/icons/outline/dark_mode/chat_bubble.svg +3 -0
  63. munchboka_edutools/static/icons/outline/dark_mode/check.svg +3 -0
  64. munchboka_edutools/static/icons/outline/dark_mode/cmd_line.svg +3 -0
  65. munchboka_edutools/static/icons/outline/dark_mode/file.svg +1 -0
  66. munchboka_edutools/static/icons/outline/dark_mode/fire.svg +4 -0
  67. munchboka_edutools/static/icons/outline/dark_mode/key.svg +3 -0
  68. munchboka_edutools/static/icons/outline/dark_mode/magnifying.svg +3 -0
  69. munchboka_edutools/static/icons/outline/dark_mode/pencil_square.svg +3 -0
  70. munchboka_edutools/static/icons/outline/dark_mode/play.svg +3 -0
  71. munchboka_edutools/static/icons/outline/dark_mode/question.svg +3 -0
  72. munchboka_edutools/static/icons/outline/dark_mode/square_check.svg +1 -0
  73. munchboka_edutools/static/icons/outline/dark_mode/stop.svg +3 -0
  74. munchboka_edutools/static/icons/outline/dark_mode/summary.svg +3 -0
  75. munchboka_edutools/static/icons/outline/dark_mode/undo.svg +3 -0
  76. munchboka_edutools/static/icons/outline/dark_mode/unlock.svg +3 -0
  77. munchboka_edutools/static/icons/outline/light_mode/academic.svg +3 -0
  78. munchboka_edutools/static/icons/outline/light_mode/backward.svg +3 -0
  79. munchboka_edutools/static/icons/outline/light_mode/book.svg +3 -0
  80. munchboka_edutools/static/icons/outline/light_mode/chat_bubble.svg +3 -0
  81. munchboka_edutools/static/icons/outline/light_mode/check.svg +3 -0
  82. munchboka_edutools/static/icons/outline/light_mode/cmd_line.svg +3 -0
  83. munchboka_edutools/static/icons/outline/light_mode/file.svg +1 -0
  84. munchboka_edutools/static/icons/outline/light_mode/fire.svg +4 -0
  85. munchboka_edutools/static/icons/outline/light_mode/key.svg +3 -0
  86. munchboka_edutools/static/icons/outline/light_mode/magnifying.svg +3 -0
  87. munchboka_edutools/static/icons/outline/light_mode/pencil_square.svg +3 -0
  88. munchboka_edutools/static/icons/outline/light_mode/play.svg +3 -0
  89. munchboka_edutools/static/icons/outline/light_mode/question.svg +3 -0
  90. munchboka_edutools/static/icons/outline/light_mode/square_check.svg +1 -0
  91. munchboka_edutools/static/icons/outline/light_mode/stop.svg +3 -0
  92. munchboka_edutools/static/icons/outline/light_mode/summary.svg +3 -0
  93. munchboka_edutools/static/icons/outline/light_mode/undo.svg +3 -0
  94. munchboka_edutools/static/icons/outline/light_mode/unlock.svg +3 -0
  95. munchboka_edutools/static/icons/polyicons/cubicdown.svg +3 -0
  96. munchboka_edutools/static/icons/polyicons/cubicup.svg +3 -0
  97. munchboka_edutools/static/icons/polyicons/frown.svg +3 -0
  98. munchboka_edutools/static/icons/polyicons/smile.svg +3 -0
  99. munchboka_edutools/static/icons/solid/dark_mode/academic.svg +5 -0
  100. munchboka_edutools/static/icons/solid/dark_mode/backward.svg +3 -0
  101. munchboka_edutools/static/icons/solid/dark_mode/book.svg +3 -0
  102. munchboka_edutools/static/icons/solid/dark_mode/brain.svg +1 -0
  103. munchboka_edutools/static/icons/solid/dark_mode/file.svg +1 -0
  104. munchboka_edutools/static/icons/solid/dark_mode/fire.svg +3 -0
  105. munchboka_edutools/static/icons/solid/dark_mode/key.svg +3 -0
  106. munchboka_edutools/static/icons/solid/dark_mode/pencil_square.svg +4 -0
  107. munchboka_edutools/static/icons/solid/dark_mode/play.svg +3 -0
  108. munchboka_edutools/static/icons/solid/dark_mode/python.svg +1 -0
  109. munchboka_edutools/static/icons/solid/dark_mode/scroll.svg +1 -0
  110. munchboka_edutools/static/icons/solid/dark_mode/stop.svg +3 -0
  111. munchboka_edutools/static/icons/solid/light_mode/academic.svg +5 -0
  112. munchboka_edutools/static/icons/solid/light_mode/backward.svg +3 -0
  113. munchboka_edutools/static/icons/solid/light_mode/book.svg +3 -0
  114. munchboka_edutools/static/icons/solid/light_mode/brain.svg +1 -0
  115. munchboka_edutools/static/icons/solid/light_mode/file.svg +1 -0
  116. munchboka_edutools/static/icons/solid/light_mode/fire.svg +3 -0
  117. munchboka_edutools/static/icons/solid/light_mode/key.svg +3 -0
  118. munchboka_edutools/static/icons/solid/light_mode/pencil_square.svg +4 -0
  119. munchboka_edutools/static/icons/solid/light_mode/play.svg +3 -0
  120. munchboka_edutools/static/icons/solid/light_mode/python.svg +1 -0
  121. munchboka_edutools/static/icons/solid/light_mode/scroll.svg +1 -0
  122. munchboka_edutools/static/icons/solid/light_mode/stop.svg +3 -0
  123. munchboka_edutools/static/icons/stickers/edit.svg +1 -0
  124. munchboka_edutools/static/icons/stickers/pencil_square.svg +3 -0
  125. munchboka_edutools/static/js/casThemeManager.js +99 -0
  126. munchboka_edutools/static/js/escapeRoom/escape-room.js +219 -0
  127. munchboka_edutools/static/js/geogebra-setup.js +6 -0
  128. munchboka_edutools/static/js/highlight-init.js +6 -0
  129. munchboka_edutools/static/js/interactiveCode/codeEditor.js +632 -0
  130. munchboka_edutools/static/js/interactiveCode/interactiveCodeSetup.js +348 -0
  131. munchboka_edutools/static/js/interactiveCode/pythonRunner.js +336 -0
  132. munchboka_edutools/static/js/interactiveCode/turtleCode.js +203 -0
  133. munchboka_edutools/static/js/interactiveCode/workerManager.js +353 -0
  134. munchboka_edutools/static/js/jeopardy.js +523 -0
  135. munchboka_edutools/static/js/pairPuzzle/draggableItem.js +64 -0
  136. munchboka_edutools/static/js/pairPuzzle/dropZone.js +119 -0
  137. munchboka_edutools/static/js/pairPuzzle/game.js +160 -0
  138. munchboka_edutools/static/js/parsons/parsonsPuzzle.js +641 -0
  139. munchboka_edutools/static/js/popup.js +85 -0
  140. munchboka_edutools/static/js/quiz.js +422 -0
  141. munchboka_edutools/static/js/skulpt/skulpt.js +35721 -0
  142. munchboka_edutools/static/js/timedQuiz/multipleChoiceQuestion.js +184 -0
  143. munchboka_edutools/static/js/timedQuiz/timedMultipleChoiceQuiz.js +244 -0
  144. munchboka_edutools/static/js/timedQuiz/utils.js +6 -0
  145. munchboka_edutools/static/js/utils.js +3 -0
  146. munchboka_edutools-0.1.13.dist-info/METADATA +108 -0
  147. munchboka_edutools-0.1.13.dist-info/RECORD +149 -0
  148. munchboka_edutools-0.1.13.dist-info/WHEEL +4 -0
  149. munchboka_edutools-0.1.13.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,85 @@
1
+
2
+
3
+ window.addEventListener("DOMContentLoaded", () => {
4
+ document.querySelectorAll(".popup-wrapper").forEach(wrapper => {
5
+ const trigger = wrapper.querySelector(".popup-trigger");
6
+ const bubble = wrapper.querySelector(".popup-bubble");
7
+
8
+ if (!trigger || !bubble) return;
9
+
10
+ let hideTimeout;
11
+
12
+ // Move bubble to body
13
+ const bodyBubble = bubble.cloneNode(true);
14
+ bodyBubble.style.display = "none";
15
+ document.body.appendChild(bodyBubble);
16
+
17
+ const positionBubble = () => {
18
+ const rect = trigger.getBoundingClientRect();
19
+ bodyBubble.style.position = "absolute";
20
+ bodyBubble.style.left = `${rect.left + window.scrollX}px`;
21
+ bodyBubble.style.top = `${rect.bottom + 6 + window.scrollY}px`;
22
+ bodyBubble.style.zIndex = "9999";
23
+ };
24
+
25
+ const showBubble = () => {
26
+ clearTimeout(hideTimeout);
27
+ document.querySelectorAll(".popup-bubble").forEach(b => b.style.display = "none");
28
+ positionBubble();
29
+ bodyBubble.style.display = "block";
30
+
31
+ if (window.renderMathInElement) {
32
+ renderMathInElement(bodyBubble, {
33
+ delimiters: [
34
+ { left: "$$", right: "$$", display: true },
35
+ { left: "$", right: "$", display: false },
36
+ { left: "\\(", right: "\\)", display: false },
37
+ { left: "\\[", right: "\\]", display: true }
38
+ ],
39
+ throwOnError: false
40
+ });
41
+ }
42
+ };
43
+
44
+ const hideBubble = () => {
45
+ hideTimeout = setTimeout(() => {
46
+ bodyBubble.style.display = "none";
47
+ }, 200);
48
+ };
49
+
50
+ // Click toggle
51
+ trigger.addEventListener("click", e => {
52
+ e.stopPropagation();
53
+ const visible = bodyBubble.style.display === "block";
54
+ document.querySelectorAll(".popup-bubble").forEach(b => b.style.display = "none");
55
+ if (!visible) showBubble();
56
+ });
57
+
58
+ // Hover
59
+ trigger.addEventListener("mouseenter", showBubble);
60
+ trigger.addEventListener("mouseleave", hideBubble);
61
+ bodyBubble.addEventListener("mouseenter", () => clearTimeout(hideTimeout));
62
+ bodyBubble.addEventListener("mouseleave", hideBubble);
63
+
64
+ // Global close
65
+ document.addEventListener("click", () => bodyBubble.style.display = "none");
66
+ document.addEventListener("keydown", e => {
67
+ if (e.key === "Escape") {
68
+ bodyBubble.style.display = "none";
69
+ }
70
+ });
71
+
72
+ // Render math in the trigger
73
+ if (window.renderMathInElement) {
74
+ renderMathInElement(trigger, {
75
+ delimiters: [
76
+ { left: "$$", right: "$$", display: true },
77
+ { left: "$", right: "$", display: false },
78
+ { left: "\\(", right: "\\)", display: false },
79
+ { left: "\\[", right: "\\]", display: true }
80
+ ],
81
+ throwOnError: false
82
+ });
83
+ }
84
+ });
85
+ });
@@ -0,0 +1,422 @@
1
+ // munchboka-edutools quiz runtime
2
+ // Combines SequentialMultipleChoiceQuiz and MultipleChoiceQuestion with minimal deps.
3
+
4
+ // Generate UUID function
5
+ function generateUUID() {
6
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
7
+ const r = (Math.random() * 16) | 0,
8
+ v = c === 'x' ? r : (r & 0x3) | 0x8;
9
+ return v.toString(16);
10
+ });
11
+ }
12
+
13
+ function shuffleArray(array) {
14
+ for (let i = array.length - 1; i > 0; i--) {
15
+ const j = Math.floor(Math.random() * (i + 1));
16
+ [array[i], array[j]] = [array[j], array[i]];
17
+ }
18
+ }
19
+
20
+ class MultipleChoiceQuestion {
21
+ constructor({ id, content, answers }) {
22
+ this.id = id;
23
+ this.content = content;
24
+ this.answers = answers.map((answer) => {
25
+ if (!Object.prototype.hasOwnProperty.call(answer, 'id')) {
26
+ answer.id = generateUUID();
27
+ }
28
+ return answer;
29
+ });
30
+ this.selectedAnswers = new Set();
31
+ this.elements = {}; // Store elements for easy access
32
+ this.correctlyAnswered = false; // Track if the question is correctly answered
33
+ }
34
+
35
+ shuffleAnswers() {
36
+ shuffleArray(this.answers);
37
+ }
38
+
39
+ render(containerId) {
40
+ const container = document.getElementById(containerId);
41
+
42
+ // Create question element
43
+ const questionCard = document.createElement('div');
44
+ questionCard.classList.add('question-card');
45
+ questionCard.innerHTML = this.content;
46
+
47
+ // Append the question card to the container
48
+ container.appendChild(questionCard);
49
+
50
+ // Render LaTeX in the question
51
+ this.renderMathInElement(questionCard);
52
+
53
+ // Apply syntax highlighting to the question card
54
+ this.applySyntaxHighlighting(questionCard);
55
+
56
+ // Create answers container
57
+ const answersGrid = document.createElement('div');
58
+ answersGrid.classList.add('answers-grid');
59
+
60
+ // Append the answers grid to the container
61
+ container.appendChild(answersGrid);
62
+
63
+ // Create answer elements
64
+ this.answers.forEach((answer) => {
65
+ const answerCard = document.createElement('div');
66
+ answerCard.classList.add('answer-card');
67
+ answerCard.innerHTML = answer.content;
68
+ answerCard.dataset.answerId = answer.id;
69
+
70
+ // Append the answer card to the answers grid
71
+ answersGrid.appendChild(answerCard);
72
+
73
+ // Render LaTeX in the answer
74
+ this.renderMathInElement(answerCard);
75
+
76
+ // Apply syntax highlighting to the answer card
77
+ this.applySyntaxHighlighting(answerCard);
78
+
79
+ // Mark as selected if previously selected
80
+ if (this.selectedAnswers.has(answer.id)) {
81
+ answerCard.classList.add('selected');
82
+ }
83
+
84
+ // Disable interaction if question is correctly answered
85
+ if (this.correctlyAnswered) {
86
+ answerCard.classList.add('disabled');
87
+ } else {
88
+ // Add click event to handle selection
89
+ answerCard.addEventListener('click', () => this.toggleSelection(answerCard));
90
+ }
91
+ });
92
+
93
+ // Store elements for later access
94
+ this.elements.container = container;
95
+ this.elements.questionCard = questionCard;
96
+ this.elements.answersGrid = answersGrid;
97
+
98
+ // Apply correct class if previously answered correctly
99
+ if (this.correctlyAnswered) {
100
+ this.elements.questionCard.classList.add('correct');
101
+ }
102
+ }
103
+
104
+ toggleSelection(answerCard) {
105
+ const answerId = answerCard.dataset.answerId;
106
+ if (this.selectedAnswers.has(answerId)) {
107
+ this.selectedAnswers.delete(answerId);
108
+ answerCard.classList.remove('selected');
109
+ } else {
110
+ // If single-choice, deselect other answers
111
+ if (this.isSingleChoice()) {
112
+ this.selectedAnswers.clear();
113
+ const allAnswerCards = this.elements.answersGrid.querySelectorAll('.answer-card');
114
+ allAnswerCards.forEach((card) => card.classList.remove('selected'));
115
+ }
116
+ this.selectedAnswers.add(answerId);
117
+ answerCard.classList.add('selected');
118
+ }
119
+ }
120
+
121
+ checkAnswers(showAlert = true) {
122
+ // Get the correct answer IDs
123
+ const correctAnswerIds = this.answers.filter((answer) => answer.isCorrect).map((answer) => answer.id);
124
+
125
+ // Compare selectedAnswers with correctAnswerIds
126
+ const isCorrect = this.selectedAnswers.size === correctAnswerIds.length && [...this.selectedAnswers].every((id) => correctAnswerIds.includes(id));
127
+
128
+ // Provide visual feedback
129
+ if (isCorrect) {
130
+ this.elements.questionCard.classList.remove('incorrect'); // ✅ Remove incorrect class
131
+ this.elements.questionCard.classList.add('correct');
132
+ this.correctlyAnswered = true; // Mark question as correctly answered
133
+ // Disable further interaction
134
+ const allAnswerCards = this.elements.answersGrid.querySelectorAll('.answer-card');
135
+ allAnswerCards.forEach((card) => {
136
+ card.classList.add('disabled');
137
+
138
+ const newCard = card.cloneNode(true);
139
+ card.parentNode.replaceChild(newCard, card);
140
+ });
141
+ } else {
142
+ this.elements.questionCard.classList.add('incorrect');
143
+ }
144
+
145
+ // Optionally display feedback if showAlert is true
146
+ if (showAlert) {
147
+ // eslint-disable-next-line no-alert
148
+ alert(isCorrect ? 'Riktig!' : 'Feil. Prøv igjen.');
149
+ }
150
+
151
+ return isCorrect;
152
+ }
153
+
154
+ markAsCorrectlyAnswered() {
155
+ this.correctlyAnswered = true;
156
+ }
157
+
158
+ isSingleChoice() {
159
+ // Determine if the question is single-choice
160
+ const correctAnswersCount = this.answers.filter((answer) => answer.isCorrect).length;
161
+ return correctAnswersCount === 1;
162
+ }
163
+
164
+ renderMathInElement(element) {
165
+ // Ensure KaTeX renders LaTeX inside the element
166
+ if (typeof window !== 'undefined' && typeof window.renderMathInElement === 'function') {
167
+ window.renderMathInElement(element, {
168
+ delimiters: [
169
+ { left: '$$', right: '$$', display: true },
170
+ { left: '$', right: '$', display: false },
171
+ { left: '\\[', right: '\\]', display: true },
172
+ { left: '\\(', right: '\\)', display: false },
173
+ ],
174
+ });
175
+ }
176
+ }
177
+
178
+ applySyntaxHighlighting(element) {
179
+ // Apply syntax highlighting to code blocks if highlight.js is present
180
+ if (typeof window !== 'undefined' && window.hljs && typeof window.hljs.highlightElement === 'function') {
181
+ const codeBlocks = element.querySelectorAll('code');
182
+ codeBlocks.forEach((block) => {
183
+ window.hljs.highlightElement(block);
184
+ });
185
+ }
186
+ }
187
+ }
188
+
189
+ class SequentialMultipleChoiceQuiz {
190
+ constructor(containerId, questionsData) {
191
+ this.containerId = containerId;
192
+ this.container = document.getElementById(containerId);
193
+ if (!this.container) {
194
+ throw new Error('Container not found');
195
+ }
196
+ this.questionsData = questionsData;
197
+ this.totalQuestions = questionsData.length;
198
+ this.currentQuestionIndex = 0;
199
+ this.uniqueId = generateUUID();
200
+ this.correctlyAnsweredQuestions = new Set(); // Track correctly answered questions
201
+ this.questionInstances = {}; // Store instances of MultipleChoiceQuestion
202
+ this.isFinished = false; // Track completion without destroying UI
203
+ this.init();
204
+ }
205
+
206
+ init() {
207
+ this.generateHTML();
208
+ this.showQuestion();
209
+ }
210
+
211
+ generateHTML() {
212
+ // Set up the main structure with Previous and Next buttons
213
+ this.container.innerHTML = `
214
+ <div id="question-counter-${this.uniqueId}" class="question-counter"></div>
215
+ <div id="question-container-${this.uniqueId}" class="mcq-container"></div>
216
+ <div class="button-container">
217
+ <button id="prev-question-${this.uniqueId}" class="button button-prev">← Forrige</button>
218
+ <button id="submit-answer-${this.uniqueId}" class="button button-run">Sjekk svar</button>
219
+ <button id="next-question-${this.uniqueId}" class="button button-next">Neste →</button>
220
+ </div>
221
+ <div id="quiz-completion-${this.uniqueId}" class="quiz-completion-message" style="display: none;">
222
+ <p>Da var quizen ferdig! 🎉</p>
223
+ </div>
224
+ <!-- Toast Notifications -->
225
+ <div id="toast-success-${this.uniqueId}" class="toast toast-success" style="display: none;">
226
+ <p>Riktig! 🎉</p>
227
+ </div>
228
+ <div id="toast-error-${this.uniqueId}" class="toast toast-error" style="display: none;">
229
+ <p>Prøv igjen!</p>
230
+ </div>
231
+ `;
232
+
233
+ // Add event listeners for the buttons
234
+ document.getElementById(`submit-answer-${this.uniqueId}`).addEventListener('click', () => this.submitAnswer());
235
+ document.getElementById(`prev-question-${this.uniqueId}`).addEventListener('click', () => this.goToPreviousQuestion());
236
+ document.getElementById(`next-question-${this.uniqueId}`).addEventListener('click', () => this.goToNextQuestion());
237
+ }
238
+
239
+ showQuestion() {
240
+ // If we moved beyond the virtual completion card, clamp back
241
+ if (this.currentQuestionIndex > this.totalQuestions) {
242
+ this.currentQuestionIndex = this.totalQuestions;
243
+ }
244
+
245
+ // Update the question counter (hide count on completion card)
246
+ const counter = document.getElementById(`question-counter-${this.uniqueId}`);
247
+ if (this.currentQuestionIndex === this.totalQuestions) {
248
+ counter.textContent = '';
249
+ } else {
250
+ counter.textContent = `Spørsmål ${this.currentQuestionIndex + 1} / ${this.totalQuestions}`;
251
+ }
252
+
253
+ // Clear the question container before rendering the new question
254
+ const questionContainer = document.getElementById(`question-container-${this.uniqueId}`);
255
+ questionContainer.innerHTML = ''; // Clear previous question
256
+
257
+ // If we are on the virtual completion card, show the completion banner
258
+ // and do NOT render a question (buttons remain for navigation)
259
+ const banner = document.getElementById(`quiz-completion-${this.uniqueId}`);
260
+ if (this.currentQuestionIndex === this.totalQuestions) {
261
+ if (banner) banner.style.display = 'block';
262
+ this.updateNavigationButtons();
263
+ return;
264
+ } else if (banner) {
265
+ banner.style.display = 'none';
266
+ }
267
+
268
+ const questionData = this.questionsData[this.currentQuestionIndex];
269
+ if (!questionData) {
270
+ return; // Nothing to show
271
+ }
272
+
273
+ // Check if we already have an instance of the question
274
+ if (Object.prototype.hasOwnProperty.call(this.questionInstances, this.currentQuestionIndex)) {
275
+ // Retrieve the existing instance
276
+ this.currentQuestion = this.questionInstances[this.currentQuestionIndex];
277
+ } else {
278
+ // Create a new instance and store it
279
+ this.currentQuestion = new MultipleChoiceQuestion(questionData);
280
+ // Shuffle the answers on first creation
281
+ this.currentQuestion.shuffleAnswers();
282
+ this.questionInstances[this.currentQuestionIndex] = this.currentQuestion;
283
+ }
284
+
285
+ // Render the question
286
+ this.currentQuestion.render(`question-container-${this.uniqueId}`);
287
+
288
+ // Update navigation buttons and other UI elements
289
+ this.updateNavigationButtons();
290
+ }
291
+
292
+ submitAnswer() {
293
+ // Disable the submit button to prevent multiple clicks
294
+ const submitButton = document.getElementById(`submit-answer-${this.uniqueId}`);
295
+ submitButton.disabled = true;
296
+
297
+ const isCorrect = this.currentQuestion.checkAnswers(false); // Pass 'false' to suppress alerts
298
+
299
+ if (isCorrect) {
300
+ this.correctlyAnsweredQuestions.add(this.currentQuestionIndex); // Track correct answer
301
+ this.showToast('success');
302
+ // Mark the question as correctly answered
303
+ this.currentQuestion.markAsCorrectlyAnswered();
304
+ // Update navigation buttons after a short delay
305
+ setTimeout(() => {
306
+ this.updateNavigationButtons();
307
+ }, 800); // Delay to allow the user to see the feedback
308
+ } else {
309
+ this.showToast('error');
310
+ // Re-enable the submit button so the user can try again
311
+ setTimeout(() => {
312
+ submitButton.disabled = false;
313
+ }, 1500); // Match the toast display time
314
+ }
315
+ }
316
+
317
+ showToast(type) {
318
+ const toastId = type === 'success' ? `toast-success-${this.uniqueId}` : `toast-error-${this.uniqueId}`;
319
+ const toast = document.getElementById(toastId);
320
+
321
+ if (!toast) {
322
+ console.error(`Toast element with ID ${toastId} not found.`);
323
+ return;
324
+ }
325
+
326
+ // Ensure the container is positioned relatively
327
+ if (getComputedStyle(this.container).position === 'static') {
328
+ this.container.style.position = 'relative';
329
+ }
330
+
331
+ // Display the toast in the center of the container
332
+ toast.style.position = 'absolute';
333
+ toast.style.top = '50%';
334
+ toast.style.left = '50%';
335
+ toast.style.transform = 'translate(-50%, -50%)';
336
+ toast.style.display = 'block';
337
+
338
+ // Hide the toast after a delay
339
+ setTimeout(() => {
340
+ toast.style.display = 'none';
341
+ }, 1500); // Display for 1.5 seconds
342
+ }
343
+
344
+ finishQuiz() {
345
+ // Mark finished and show completion banner, keep UI for navigation
346
+ this.isFinished = true;
347
+ const banner = document.getElementById(`quiz-completion-${this.uniqueId}`);
348
+ if (banner) {
349
+ banner.style.display = 'block';
350
+ }
351
+ }
352
+
353
+ updateNavigationButtons() {
354
+ const prevButton = document.getElementById(`prev-question-${this.uniqueId}`);
355
+ const nextButton = document.getElementById(`next-question-${this.uniqueId}`);
356
+ const submitButton = document.getElementById(`submit-answer-${this.uniqueId}`);
357
+
358
+ const currentIndex = this.currentQuestionIndex;
359
+
360
+ // Show or hide the Previous button
361
+ if (currentIndex === 0 || !this.correctlyAnsweredQuestions.has(currentIndex - 1)) {
362
+ prevButton.style.display = 'none'; // Hide the button
363
+ } else {
364
+ prevButton.style.display = ''; // Show the button
365
+ }
366
+
367
+ // Show or hide the Next button
368
+ if (currentIndex === this.totalQuestions) {
369
+ nextButton.style.display = 'none'; // No next beyond completion card
370
+ } else if (this.correctlyAnsweredQuestions.has(currentIndex)) {
371
+ nextButton.style.display = ''; // Show the button (including on last real question)
372
+ } else {
373
+ nextButton.style.display = 'none'; // Hide the button
374
+ }
375
+
376
+ // Disable/hide the submit button on completion card; otherwise disable if already correct
377
+ if (currentIndex === this.totalQuestions) {
378
+ submitButton.style.display = 'none';
379
+ } else {
380
+ submitButton.style.display = '';
381
+ submitButton.disabled = this.correctlyAnsweredQuestions.has(currentIndex);
382
+ }
383
+ }
384
+
385
+ goToPreviousQuestion() {
386
+ if (this.currentQuestionIndex > 0 && this.correctlyAnsweredQuestions.has(this.currentQuestionIndex - 1)) {
387
+ this.currentQuestionIndex--;
388
+ this.showQuestion();
389
+ // this.scrollToQuizContainer(); // Scroll to the quiz container
390
+ }
391
+ }
392
+
393
+ goToNextQuestion() {
394
+ if (this.currentQuestionIndex === this.totalQuestions) {
395
+ return; // Already at completion card
396
+ }
397
+ if (this.correctlyAnsweredQuestions.has(this.currentQuestionIndex)) {
398
+ if (this.currentQuestionIndex < this.totalQuestions - 1) {
399
+ this.currentQuestionIndex++;
400
+ this.showQuestion();
401
+ } else if (this.currentQuestionIndex === this.totalQuestions - 1) {
402
+ // Move to virtual completion card
403
+ this.currentQuestionIndex = this.totalQuestions;
404
+ this.finishQuiz();
405
+ this.showQuestion();
406
+ }
407
+ // this.scrollToQuizContainer(); // Scroll to the quiz container
408
+ }
409
+ }
410
+
411
+ scrollToQuizContainer() {
412
+ this.container.scrollIntoView({
413
+ behavior: 'smooth',
414
+ block: 'center',
415
+ inline: 'nearest',
416
+ });
417
+ }
418
+ }
419
+
420
+ // Expose globally (for inline scripts)
421
+ window.SequentialMultipleChoiceQuiz = SequentialMultipleChoiceQuiz;
422
+ window.MultipleChoiceQuestion = MultipleChoiceQuestion;