munchboka-edutools 0.2.3__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.
- munchboka_edutools/__init__.py +184 -0
- munchboka_edutools/_plotmath_shim.py +126 -0
- munchboka_edutools/_version.py +2 -0
- munchboka_edutools/directives/__init__.py +1 -0
- munchboka_edutools/directives/admonitions.py +389 -0
- munchboka_edutools/directives/cas_popup.py +428 -0
- munchboka_edutools/directives/clear.py +103 -0
- munchboka_edutools/directives/dialogue.py +137 -0
- munchboka_edutools/directives/escape_room.py +296 -0
- munchboka_edutools/directives/escape_room2.py +318 -0
- munchboka_edutools/directives/factor_tree.py +552 -0
- munchboka_edutools/directives/flashcards.py +233 -0
- munchboka_edutools/directives/ggb.py +209 -0
- munchboka_edutools/directives/ggb_icon.py +105 -0
- munchboka_edutools/directives/ggb_popup.py +308 -0
- munchboka_edutools/directives/horner.py +326 -0
- munchboka_edutools/directives/interactive_code.py +75 -0
- munchboka_edutools/directives/jeopardy.py +252 -0
- munchboka_edutools/directives/jeopardy2.py +636 -0
- munchboka_edutools/directives/multi_plot.py +2524 -0
- munchboka_edutools/directives/multi_plot2.py +252 -0
- munchboka_edutools/directives/pair_puzzle.py +191 -0
- munchboka_edutools/directives/parsons.py +109 -0
- munchboka_edutools/directives/plot.py +3758 -0
- munchboka_edutools/directives/poly_icon.py +111 -0
- munchboka_edutools/directives/polydiv.py +346 -0
- munchboka_edutools/directives/popup.py +245 -0
- munchboka_edutools/directives/quiz.py +291 -0
- munchboka_edutools/directives/quiz2.py +453 -0
- munchboka_edutools/directives/signchart.py +519 -0
- munchboka_edutools/directives/signchart2.py +1545 -0
- munchboka_edutools/directives/timed_quiz.py +436 -0
- munchboka_edutools/directives/turtle.py +157 -0
- munchboka_edutools/static/css/admonitions.css +2012 -0
- munchboka_edutools/static/css/cas_popup.css +242 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_cm.css +112 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_default_cm.css +40 -0
- munchboka_edutools/static/css/code_mirror_themes/github_dark_high_contrast_cm.css +141 -0
- munchboka_edutools/static/css/code_mirror_themes/github_light_cm.css +120 -0
- munchboka_edutools/static/css/code_mirror_themes/github_light_default_cm.css +108 -0
- munchboka_edutools/static/css/code_mirror_themes/one_dark_cm.css +121 -0
- munchboka_edutools/static/css/dialogue.css +92 -0
- munchboka_edutools/static/css/escapeRoom/escape-room.css +223 -0
- munchboka_edutools/static/css/figures.css +321 -0
- munchboka_edutools/static/css/flashcards.css +219 -0
- munchboka_edutools/static/css/general_style.css +74 -0
- munchboka_edutools/static/css/github-dark-high-contrast.css +141 -0
- munchboka_edutools/static/css/github-dark.css +147 -0
- munchboka_edutools/static/css/github-light.css +155 -0
- munchboka_edutools/static/css/interactive_code/style.css +575 -0
- munchboka_edutools/static/css/interactive_code.css +582 -0
- munchboka_edutools/static/css/jeopardy.css +553 -0
- munchboka_edutools/static/css/pairPuzzle/style.css +177 -0
- munchboka_edutools/static/css/parsons/parsonsPuzzle.css +331 -0
- munchboka_edutools/static/css/popup.css +115 -0
- munchboka_edutools/static/css/quiz.css +377 -0
- munchboka_edutools/static/css/timedQuiz.css +375 -0
- munchboka_edutools/static/icons/ggb/mode_evaluate.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_extremum.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_intersect.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_nsolve.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_numeric.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_point.svg +1 -0
- munchboka_edutools/static/icons/ggb/mode_solve.svg +1 -0
- munchboka_edutools/static/icons/misc/windows-logo.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/academic.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/book.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/chat_bubble.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/check.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/cmd_line.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/file.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/fire.svg +4 -0
- munchboka_edutools/static/icons/outline/dark_mode/key.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/magnifying.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/pencil_square.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/play.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/question.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/square_check.svg +1 -0
- munchboka_edutools/static/icons/outline/dark_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/summary.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/undo.svg +3 -0
- munchboka_edutools/static/icons/outline/dark_mode/unlock.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/academic.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/book.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/chat_bubble.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/check.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/cmd_line.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/file.svg +1 -0
- munchboka_edutools/static/icons/outline/light_mode/fire.svg +4 -0
- munchboka_edutools/static/icons/outline/light_mode/key.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/magnifying.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/pencil_square.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/play.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/question.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/square_check.svg +1 -0
- munchboka_edutools/static/icons/outline/light_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/summary.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/undo.svg +3 -0
- munchboka_edutools/static/icons/outline/light_mode/unlock.svg +3 -0
- munchboka_edutools/static/icons/polyicons/cubicdown.svg +3 -0
- munchboka_edutools/static/icons/polyicons/cubicup.svg +3 -0
- munchboka_edutools/static/icons/polyicons/frown.svg +3 -0
- munchboka_edutools/static/icons/polyicons/smile.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/academic.svg +5 -0
- munchboka_edutools/static/icons/solid/dark_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/book.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/brain.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/file.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/fire.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/key.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/pencil_square.svg +4 -0
- munchboka_edutools/static/icons/solid/dark_mode/play.svg +3 -0
- munchboka_edutools/static/icons/solid/dark_mode/python.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/scroll.svg +1 -0
- munchboka_edutools/static/icons/solid/dark_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/academic.svg +5 -0
- munchboka_edutools/static/icons/solid/light_mode/backward.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/book.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/brain.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/file.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/fire.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/key.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/pencil_square.svg +4 -0
- munchboka_edutools/static/icons/solid/light_mode/play.svg +3 -0
- munchboka_edutools/static/icons/solid/light_mode/python.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/scroll.svg +1 -0
- munchboka_edutools/static/icons/solid/light_mode/stop.svg +3 -0
- munchboka_edutools/static/icons/stickers/edit.svg +1 -0
- munchboka_edutools/static/icons/stickers/pencil_square.svg +3 -0
- munchboka_edutools/static/js/casThemeManager.js +99 -0
- munchboka_edutools/static/js/escapeRoom/escape-room.js +219 -0
- munchboka_edutools/static/js/flashcards.js +199 -0
- munchboka_edutools/static/js/geogebra-setup.js +6 -0
- munchboka_edutools/static/js/highlight-init.js +6 -0
- munchboka_edutools/static/js/interactiveCode/codeEditor.js +648 -0
- munchboka_edutools/static/js/interactiveCode/interactiveCodeSetup.js +441 -0
- munchboka_edutools/static/js/interactiveCode/pythonRunner.js +336 -0
- munchboka_edutools/static/js/interactiveCode/turtleCode.js +203 -0
- munchboka_edutools/static/js/interactiveCode/workerManager.js +353 -0
- munchboka_edutools/static/js/jeopardy.js +560 -0
- munchboka_edutools/static/js/pairPuzzle/draggableItem.js +64 -0
- munchboka_edutools/static/js/pairPuzzle/dropZone.js +119 -0
- munchboka_edutools/static/js/pairPuzzle/game.js +160 -0
- munchboka_edutools/static/js/parsons/parsonsPuzzle.js +641 -0
- munchboka_edutools/static/js/popup.js +85 -0
- munchboka_edutools/static/js/quiz.js +566 -0
- munchboka_edutools/static/js/skulpt/skulpt.js +35721 -0
- munchboka_edutools/static/js/timedQuiz/multipleChoiceQuestion.js +184 -0
- munchboka_edutools/static/js/timedQuiz/timedMultipleChoiceQuiz.js +244 -0
- munchboka_edutools/static/js/timedQuiz/utils.js +6 -0
- munchboka_edutools/static/js/utils.js +3 -0
- munchboka_edutools-0.2.3.dist-info/METADATA +109 -0
- munchboka_edutools-0.2.3.dist-info/RECORD +157 -0
- munchboka_edutools-0.2.3.dist-info/WHEEL +4 -0
- munchboka_edutools-0.2.3.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
/* Faithful Jeopardy runtime from the legacy project with math/code rendering, teams, turns, and scoring. */
|
|
2
|
+
(function(){
|
|
3
|
+
function renderMathIfAvailable(root){
|
|
4
|
+
if (typeof renderMathInElement === 'function') {
|
|
5
|
+
try { renderMathInElement(root, {delimiters: [
|
|
6
|
+
{left: '$$', right: '$$', display: true},
|
|
7
|
+
{left: '$', right: '$', display: false},
|
|
8
|
+
{left: '\\(', right: '\\)', display: false},
|
|
9
|
+
{left: '\\[', right: '\\]', display: true}
|
|
10
|
+
]}); } catch(e){}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function highlightCodeIfAvailable(root){
|
|
15
|
+
if (typeof hljs !== 'undefined' && root) {
|
|
16
|
+
try {
|
|
17
|
+
root.querySelectorAll('pre code').forEach(function(block){
|
|
18
|
+
hljs.highlightElement(block);
|
|
19
|
+
});
|
|
20
|
+
} catch(e){}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function executeScripts(root){
|
|
25
|
+
// When content is inserted via innerHTML, script tags don't execute
|
|
26
|
+
// This function finds and re-executes them
|
|
27
|
+
if (!root) return;
|
|
28
|
+
const scripts = root.querySelectorAll('script');
|
|
29
|
+
scripts.forEach(function(oldScript){
|
|
30
|
+
const newScript = document.createElement('script');
|
|
31
|
+
if (oldScript.src) {
|
|
32
|
+
newScript.src = oldScript.src;
|
|
33
|
+
} else {
|
|
34
|
+
newScript.textContent = oldScript.textContent;
|
|
35
|
+
}
|
|
36
|
+
// Copy attributes
|
|
37
|
+
Array.from(oldScript.attributes).forEach(function(attr){
|
|
38
|
+
newScript.setAttribute(attr.name, attr.value);
|
|
39
|
+
});
|
|
40
|
+
oldScript.parentNode.replaceChild(newScript, oldScript);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function initJeopardy(container){
|
|
45
|
+
let cfg = null;
|
|
46
|
+
try {
|
|
47
|
+
const dataNode = container.querySelector('script.jeopardy-data[type="application/json"]');
|
|
48
|
+
let raw = dataNode ? (dataNode.textContent || dataNode.innerText || '') : '';
|
|
49
|
+
if (!raw || !raw.trim()) {
|
|
50
|
+
raw = container.getAttribute('data-config') || '{}';
|
|
51
|
+
}
|
|
52
|
+
if (raw && raw.indexOf('&') !== -1 && raw.indexOf('{') === -1) {
|
|
53
|
+
const ta = document.createElement('textarea'); ta.innerHTML = raw; raw = ta.value;
|
|
54
|
+
}
|
|
55
|
+
cfg = JSON.parse(raw);
|
|
56
|
+
} catch(e){ cfg = {}; }
|
|
57
|
+
const nTeams = Math.max(1, parseInt(cfg.teams||2,10));
|
|
58
|
+
const categories = cfg.categories||[];
|
|
59
|
+
const values = (cfg.values||[]).slice().sort(function(a,b){return a-b;});
|
|
60
|
+
|
|
61
|
+
const tileStates = Object.create(null);
|
|
62
|
+
const categoryStats = categories.map(()=>({correct:0, wrong:0}));
|
|
63
|
+
let totalPlayableTiles = 0;
|
|
64
|
+
let scoreboardShown = false;
|
|
65
|
+
const storageKey = 'jeopardy:' + (container && container.id ? container.id : 'default');
|
|
66
|
+
function buildState(){
|
|
67
|
+
return {
|
|
68
|
+
started,
|
|
69
|
+
gameMode,
|
|
70
|
+
timerMs,
|
|
71
|
+
currentTurn,
|
|
72
|
+
scoreboardShown,
|
|
73
|
+
teams: teams.map(t=>({name:t.name, score:t.score})),
|
|
74
|
+
teamCategoryPoints: teamCategoryPoints.map(row=> row.slice()),
|
|
75
|
+
categoryStats: categoryStats.map(s=>({correct:s.correct, wrong:s.wrong})),
|
|
76
|
+
tileStates: Object.fromEntries(Object.entries(tileStates).map(([k,v])=>[k,{locked:!!(v&&v.locked)}]))
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function saveState(){ try { localStorage.setItem(storageKey, JSON.stringify(buildState())); } catch(e){} }
|
|
80
|
+
function loadState(){
|
|
81
|
+
try { const raw = localStorage.getItem(storageKey); if(!raw) return null; const obj = JSON.parse(raw); if (!obj || typeof obj !== 'object') return null; return obj; } catch(e){ return null; }
|
|
82
|
+
}
|
|
83
|
+
function clearState(){ try { localStorage.removeItem(storageKey); } catch(e){} }
|
|
84
|
+
let gameMode = 'duel';
|
|
85
|
+
let timerMs = 0;
|
|
86
|
+
let currentTurn = 0;
|
|
87
|
+
let started = false;
|
|
88
|
+
|
|
89
|
+
const scorebar = document.createElement('div');
|
|
90
|
+
scorebar.className = 'jeopardy-scorebar';
|
|
91
|
+
scorebar.style.display = 'none';
|
|
92
|
+
const turnIndicator = document.createElement('div');
|
|
93
|
+
turnIndicator.className = 'jeopardy-turn-indicator';
|
|
94
|
+
turnIndicator.style.display = 'none';
|
|
95
|
+
const topbar = document.createElement('div');
|
|
96
|
+
topbar.className = 'jeopardy-topbar';
|
|
97
|
+
const scoreWrap = document.createElement('div');
|
|
98
|
+
scoreWrap.className = 'jeopardy-scorebar-wrap';
|
|
99
|
+
scoreWrap.appendChild(scorebar);
|
|
100
|
+
const topbarRight = document.createElement('div');
|
|
101
|
+
topbarRight.className = 'jeopardy-topbar-right';
|
|
102
|
+
const resetBtn = document.createElement('button');
|
|
103
|
+
resetBtn.className = 'j-btn accent jeopardy-reset-button';
|
|
104
|
+
resetBtn.textContent = 'Reset spill';
|
|
105
|
+
resetBtn.style.display = 'none';
|
|
106
|
+
topbarRight.appendChild(resetBtn);
|
|
107
|
+
topbar.appendChild(scoreWrap);
|
|
108
|
+
topbar.appendChild(topbarRight);
|
|
109
|
+
|
|
110
|
+
let teams = [];
|
|
111
|
+
let teamCategoryPoints = Array.from({length: nTeams}, () => Array.from({length: categories.length}, () => 0));
|
|
112
|
+
function updateActiveTeamHighlight(){
|
|
113
|
+
teams.forEach((t,i)=>{
|
|
114
|
+
if (!t._el) return;
|
|
115
|
+
if (gameMode==='turn' && i===currentTurn) t._el.classList.add('active');
|
|
116
|
+
else t._el.classList.remove('active');
|
|
117
|
+
});
|
|
118
|
+
if (turnIndicator) {
|
|
119
|
+
if (gameMode==='turn' && started && teams.length>0) {
|
|
120
|
+
turnIndicator.style.display = '';
|
|
121
|
+
turnIndicator.textContent = `Tur: ${teams[currentTurn].name}`;
|
|
122
|
+
} else {
|
|
123
|
+
turnIndicator.style.display = 'none';
|
|
124
|
+
turnIndicator.textContent = '';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function rebuildTeams(newN, names){
|
|
129
|
+
teams = [];
|
|
130
|
+
scorebar.innerHTML = '';
|
|
131
|
+
teamCategoryPoints = Array.from({length: newN}, () => Array.from({length: categories.length}, () => 0));
|
|
132
|
+
for(let i=0;i<newN;i++){
|
|
133
|
+
const team = { name: names && names[i] ? names[i] : `Lag ${i+1}`, score: 0 };
|
|
134
|
+
teams.push(team);
|
|
135
|
+
const el = document.createElement('div');
|
|
136
|
+
el.className = 'jeopardy-team';
|
|
137
|
+
const nameSpan = document.createElement('span');
|
|
138
|
+
nameSpan.className = 'team-name';
|
|
139
|
+
nameSpan.textContent = team.name;
|
|
140
|
+
const scoreSpan = document.createElement('span');
|
|
141
|
+
scoreSpan.className = 'score';
|
|
142
|
+
scoreSpan.textContent = '0';
|
|
143
|
+
el.appendChild(nameSpan);
|
|
144
|
+
el.appendChild(scoreSpan);
|
|
145
|
+
scorebar.appendChild(el);
|
|
146
|
+
team._elScore = scoreSpan;
|
|
147
|
+
team._el = el;
|
|
148
|
+
}
|
|
149
|
+
updateActiveTeamHighlight();
|
|
150
|
+
}
|
|
151
|
+
function applySavedState(state){
|
|
152
|
+
try {
|
|
153
|
+
started = !!state.started;
|
|
154
|
+
gameMode = state.gameMode || gameMode;
|
|
155
|
+
timerMs = typeof state.timerMs === 'number' ? state.timerMs : timerMs;
|
|
156
|
+
currentTurn = typeof state.currentTurn === 'number' ? state.currentTurn : 0;
|
|
157
|
+
scoreboardShown = !!state.scoreboardShown;
|
|
158
|
+
const savedTeams = Array.isArray(state.teams) ? state.teams : [];
|
|
159
|
+
const names = savedTeams.length ? savedTeams.map(t=> t && t.name ? String(t.name) : '') : null;
|
|
160
|
+
const newN = Math.max(1, names ? names.length : nTeams);
|
|
161
|
+
rebuildTeams(newN, names || undefined);
|
|
162
|
+
if (savedTeams.length){
|
|
163
|
+
savedTeams.forEach((t,i)=>{
|
|
164
|
+
if (teams[i]){ teams[i].score = Number(t.score)||0; if (teams[i]._elScore) teams[i]._elScore.textContent = String(teams[i].score); }
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (Array.isArray(state.teamCategoryPoints)){
|
|
168
|
+
teamCategoryPoints = state.teamCategoryPoints.map(row=> Array.isArray(row)? row.slice(): []);
|
|
169
|
+
}
|
|
170
|
+
if (Array.isArray(state.categoryStats)){
|
|
171
|
+
state.categoryStats.forEach((s,i)=>{ if (categoryStats[i]){ categoryStats[i].correct = Number(s.correct)||0; categoryStats[i].wrong = Number(s.wrong)||0; }});
|
|
172
|
+
}
|
|
173
|
+
if (state.tileStates && typeof state.tileStates === 'object'){
|
|
174
|
+
Object.keys(state.tileStates).forEach(k=>{ const v = state.tileStates[k]; if (v && v.locked){ tileStates[k] = {locked:true}; }});
|
|
175
|
+
try {
|
|
176
|
+
container.querySelectorAll('.jeopardy-tile').forEach(btn=>{
|
|
177
|
+
const k = btn && btn.dataset ? btn.dataset.key : null; if (!k) return;
|
|
178
|
+
if (tileStates[k] && tileStates[k].locked){ btn.disabled = true; btn.classList.add('used'); }
|
|
179
|
+
});
|
|
180
|
+
} catch(e){}
|
|
181
|
+
}
|
|
182
|
+
try { scorebar.style.display = ''; } catch(e){}
|
|
183
|
+
try { resetBtn.style.display = ''; } catch(e){}
|
|
184
|
+
try { setup.style.display = 'none'; } catch(e){}
|
|
185
|
+
updateActiveTeamHighlight();
|
|
186
|
+
} catch(e){}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const table = document.createElement('table');
|
|
190
|
+
table.className = 'jeopardy-grid';
|
|
191
|
+
|
|
192
|
+
const thead = document.createElement('thead');
|
|
193
|
+
const thr = document.createElement('tr');
|
|
194
|
+
categories.forEach(cat=>{
|
|
195
|
+
const th = document.createElement('th');
|
|
196
|
+
th.textContent = cat.name||'';
|
|
197
|
+
thr.appendChild(th);
|
|
198
|
+
});
|
|
199
|
+
thead.appendChild(thr);
|
|
200
|
+
|
|
201
|
+
const tbody = document.createElement('tbody');
|
|
202
|
+
|
|
203
|
+
const lookup = {};
|
|
204
|
+
categories.forEach((cat,ci)=>{
|
|
205
|
+
(cat.tiles||[]).forEach(t=>{
|
|
206
|
+
const key = ci+'|'+t.value;
|
|
207
|
+
lookup[key] = t;
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
totalPlayableTiles = Object.keys(lookup).length;
|
|
211
|
+
|
|
212
|
+
values.forEach(val=>{
|
|
213
|
+
const tr = document.createElement('tr');
|
|
214
|
+
categories.forEach((cat,ci)=>{
|
|
215
|
+
const td = document.createElement('td');
|
|
216
|
+
const tile = document.createElement('button');
|
|
217
|
+
tile.className = 'jeopardy-tile';
|
|
218
|
+
tile.textContent = val;
|
|
219
|
+
const key = ci+'|'+val;
|
|
220
|
+
tile.dataset.key = key;
|
|
221
|
+
const data = lookup[key] || null;
|
|
222
|
+
if(!data){ tile.disabled = true; tile.classList.add('used'); }
|
|
223
|
+
if (tileStates[key] && tileStates[key].locked) { tile.disabled = true; tile.classList.add('used'); }
|
|
224
|
+
tile.addEventListener('click', ()=>{
|
|
225
|
+
if(tile.classList.contains('used')||tile.disabled) return;
|
|
226
|
+
if (!started) return;
|
|
227
|
+
openModal(cat.name||'', val, data, tile, key);
|
|
228
|
+
});
|
|
229
|
+
td.appendChild(tile);
|
|
230
|
+
tr.appendChild(td);
|
|
231
|
+
});
|
|
232
|
+
tbody.appendChild(tr);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
table.appendChild(thead); table.appendChild(tbody);
|
|
236
|
+
|
|
237
|
+
const backdrop = document.createElement('div');
|
|
238
|
+
backdrop.className = 'jeopardy-modal-backdrop';
|
|
239
|
+
const modal = document.createElement('div');
|
|
240
|
+
modal.className = 'jeopardy-modal';
|
|
241
|
+
const header = document.createElement('div'); header.className='jeopardy-modal-header';
|
|
242
|
+
const title = document.createElement('div');
|
|
243
|
+
const timerBox = document.createElement('div'); timerBox.className='jeopardy-timer'; timerBox.style.marginLeft='auto';
|
|
244
|
+
const closeBtn = document.createElement('button'); closeBtn.className='j-btn warn'; closeBtn.textContent='Lukk';
|
|
245
|
+
const body = document.createElement('div'); body.className='jeopardy-modal-body';
|
|
246
|
+
const footer = document.createElement('div'); footer.className='jeopardy-modal-footer';
|
|
247
|
+
|
|
248
|
+
header.appendChild(title); header.appendChild(timerBox); header.appendChild(closeBtn);
|
|
249
|
+
modal.appendChild(header); modal.appendChild(body); modal.appendChild(footer);
|
|
250
|
+
backdrop.appendChild(modal);
|
|
251
|
+
|
|
252
|
+
function setScore(i, delta){ teams[i].score += delta; teams[i]._elScore.textContent = String(teams[i].score); }
|
|
253
|
+
let escHandler = null;
|
|
254
|
+
const hideModal = ()=>{
|
|
255
|
+
backdrop.style.display = 'none';
|
|
256
|
+
try { if (typeof stopTimer === 'function') stopTimer(); } catch(e){}
|
|
257
|
+
if (escHandler) {
|
|
258
|
+
try { document.removeEventListener('keydown', escHandler); } catch(e){}
|
|
259
|
+
escHandler = null;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
function enableEscClose(){
|
|
263
|
+
if (escHandler) {
|
|
264
|
+
try { document.removeEventListener('keydown', escHandler); } catch(e){}
|
|
265
|
+
escHandler = null;
|
|
266
|
+
}
|
|
267
|
+
escHandler = function(e){
|
|
268
|
+
const key = e.key || e.code;
|
|
269
|
+
if (key === 'Escape' || key === 'Esc') {
|
|
270
|
+
try { e.preventDefault(); } catch(_){ }
|
|
271
|
+
hideModal();
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
try { document.addEventListener('keydown', escHandler); } catch(e){}
|
|
275
|
+
}
|
|
276
|
+
function checkCompletionAndShowWinner(){
|
|
277
|
+
if (scoreboardShown) return;
|
|
278
|
+
if (totalPlayableTiles <= 0) return;
|
|
279
|
+
let lockedCount = 0;
|
|
280
|
+
for (const k in tileStates) { if (tileStates[k] && tileStates[k].locked) lockedCount++; }
|
|
281
|
+
if (lockedCount >= totalPlayableTiles) {
|
|
282
|
+
scoreboardShown = true;
|
|
283
|
+
openWinner();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function openWinner(){
|
|
287
|
+
const sorted = teams.map((t,i)=>({name:t.name, score:t.score, idx:i}))
|
|
288
|
+
.sort((a,b)=> b.score - a.score);
|
|
289
|
+
const max = sorted.length ? sorted[0].score : 0;
|
|
290
|
+
const winners = sorted.filter(x=> x.score === max);
|
|
291
|
+
title.textContent = winners.length > 1 ? 'Scoreboard' : 'Scoreboard';
|
|
292
|
+
body.innerHTML = '';
|
|
293
|
+
footer.innerHTML = '';
|
|
294
|
+
const grid = document.createElement('div');
|
|
295
|
+
const cols = 2 + categories.length;
|
|
296
|
+
grid.style.display = 'grid';
|
|
297
|
+
grid.style.gridTemplateColumns = `1.5fr ${'auto '.repeat(categories.length)} auto`;
|
|
298
|
+
grid.style.gap = '0.5rem 1rem';
|
|
299
|
+
const hTeam = document.createElement('div'); hTeam.style.fontWeight = '700'; hTeam.textContent = 'Lag';
|
|
300
|
+
grid.appendChild(hTeam);
|
|
301
|
+
categories.forEach(cat => { const h = document.createElement('div'); h.style.fontWeight='700'; h.textContent = cat.name||''; grid.appendChild(h); });
|
|
302
|
+
const hScore = document.createElement('div'); hScore.style.fontWeight='700'; hScore.textContent = 'Score';
|
|
303
|
+
grid.appendChild(hScore);
|
|
304
|
+
sorted.forEach(t => {
|
|
305
|
+
const rowBold = (t.score === max);
|
|
306
|
+
const name = document.createElement('div'); name.textContent = t.name; if (rowBold) name.style.fontWeight = '700'; grid.appendChild(name);
|
|
307
|
+
const pointsRow = teamCategoryPoints[t.idx] || [];
|
|
308
|
+
categories.forEach((_, ci) => { const cell = document.createElement('div'); const val = pointsRow[ci] || 0; cell.textContent = String(val); if (rowBold) cell.style.fontWeight = '700'; grid.appendChild(cell); });
|
|
309
|
+
const sc = document.createElement('div'); sc.textContent = String(t.score); if (rowBold) sc.style.fontWeight = '700'; grid.appendChild(sc);
|
|
310
|
+
});
|
|
311
|
+
body.appendChild(grid);
|
|
312
|
+
backdrop.style.display = 'flex';
|
|
313
|
+
enableEscClose();
|
|
314
|
+
closeBtn.onclick = hideModal; backdrop.onclick = (e)=>{ if(e.target===backdrop) hideModal(); };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function resetGame(){
|
|
318
|
+
try { hideModal(); } catch(e){}
|
|
319
|
+
try { stopTimer(); } catch(e){}
|
|
320
|
+
started = false;
|
|
321
|
+
scoreboardShown = false;
|
|
322
|
+
currentTurn = 0;
|
|
323
|
+
for (const k in tileStates) { try { delete tileStates[k]; } catch(e){} }
|
|
324
|
+
for (let i=0;i<categoryStats.length;i++){ categoryStats[i].correct = 0; categoryStats[i].wrong = 0; }
|
|
325
|
+
teams = [];
|
|
326
|
+
teamCategoryPoints = Array.from({length: nTeams}, () => Array.from({length: categories.length}, () => 0));
|
|
327
|
+
scorebar.innerHTML = '';
|
|
328
|
+
try {
|
|
329
|
+
container.querySelectorAll('.jeopardy-tile').forEach(b=>{ b.disabled = false; b.classList.remove('used'); });
|
|
330
|
+
} catch(e){}
|
|
331
|
+
try { scorebar.style.display = 'none'; } catch(e){}
|
|
332
|
+
try { turnIndicator.style.display = 'none'; } catch(e){}
|
|
333
|
+
try { resetBtn.style.display = 'none'; } catch(e){}
|
|
334
|
+
try { setup.style.display = ''; } catch(e){}
|
|
335
|
+
clearState();
|
|
336
|
+
}
|
|
337
|
+
resetBtn.addEventListener('click', resetGame);
|
|
338
|
+
|
|
339
|
+
let countdownId = null;
|
|
340
|
+
function stopTimer(){ if (countdownId) { try { clearInterval(countdownId); } catch(e){} countdownId = null; } timerBox.textContent=''; }
|
|
341
|
+
function startTimer(onTimeout){
|
|
342
|
+
stopTimer();
|
|
343
|
+
if (!timerMs || timerMs <= 0) return;
|
|
344
|
+
let remaining = Math.floor(timerMs/1000);
|
|
345
|
+
const render = () => { timerBox.textContent = `${Math.floor(remaining/60)}:${String(remaining%60).padStart(2,'0')}`; };
|
|
346
|
+
render();
|
|
347
|
+
countdownId = setInterval(()=>{
|
|
348
|
+
remaining -= 1;
|
|
349
|
+
if (remaining <= 0){ stopTimer(); try { onTimeout && onTimeout(); } catch(e){} }
|
|
350
|
+
else render();
|
|
351
|
+
}, 1000);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function openModal(category, value, data, tile, key){
|
|
355
|
+
title.textContent = `${category} – ${value}`;
|
|
356
|
+
body.innerHTML = '';
|
|
357
|
+
footer.innerHTML = '';
|
|
358
|
+
|
|
359
|
+
const q = document.createElement('div'); q.className='jeopardy-q'; q.innerHTML = data && data.question ? data.question : '';
|
|
360
|
+
const a = document.createElement('div'); a.className='jeopardy-a'; a.innerHTML = data && data.answer ? data.answer : '';
|
|
361
|
+
|
|
362
|
+
const revealBtn = document.createElement('button'); revealBtn.className='j-btn success'; revealBtn.textContent='Fasit';
|
|
363
|
+
revealBtn.addEventListener('click', ()=>{
|
|
364
|
+
const showing = a.style.display !== 'block';
|
|
365
|
+
a.style.display = showing ? 'block' : 'none';
|
|
366
|
+
try { setTimeout(()=>{ if (typeof body.scrollTo === 'function') body.scrollTo({ top: body.scrollHeight, behavior: 'smooth' }); else body.scrollTop = body.scrollHeight; }, 0); } catch(e){ try { body.scrollTop = body.scrollHeight; } catch(_e){} }
|
|
367
|
+
});
|
|
368
|
+
body.appendChild(q); body.appendChild(a);
|
|
369
|
+
|
|
370
|
+
// Execute any scripts in the question and answer content
|
|
371
|
+
executeScripts(q);
|
|
372
|
+
executeScripts(a);
|
|
373
|
+
|
|
374
|
+
// Tracking for duel vs turn-based scoring
|
|
375
|
+
let scored = false;
|
|
376
|
+
let duelPendingTeams = new Set();
|
|
377
|
+
const teamActions = document.createElement('div'); teamActions.className='jeopardy-team-actions';
|
|
378
|
+
const disableTeamButtons = () => { teamActions.querySelectorAll('button').forEach(b=>{ b.disabled = true; }); };
|
|
379
|
+
const onTimeout = ()=>{
|
|
380
|
+
disableTeamButtons();
|
|
381
|
+
if (gameMode==='turn' && teams.length>0){ currentTurn = (currentTurn+1)%teams.length; updateActiveTeamHighlight(); }
|
|
382
|
+
try { saveState(); } catch(e){}
|
|
383
|
+
try { setTimeout(()=>{ hideModal(); }, 300); } catch(e){}
|
|
384
|
+
};
|
|
385
|
+
teams.forEach((t, i)=>{
|
|
386
|
+
// In turn-based mode, only the active team can be scored on
|
|
387
|
+
if (gameMode==='turn' && i!==currentTurn) return;
|
|
388
|
+
// Create a per-team column with label and buttons
|
|
389
|
+
const teamCol = document.createElement('div');
|
|
390
|
+
teamCol.className = 'jeopardy-team-column';
|
|
391
|
+
const teamLabel = document.createElement('div');
|
|
392
|
+
teamLabel.className = 'jeopardy-team-label';
|
|
393
|
+
teamLabel.textContent = t.name;
|
|
394
|
+
teamCol.appendChild(teamLabel);
|
|
395
|
+
|
|
396
|
+
const add = document.createElement('button');
|
|
397
|
+
add.className='j-btn primary';
|
|
398
|
+
add.textContent = `+${value} poeng`;
|
|
399
|
+
|
|
400
|
+
// Explicit 0-points option per team in both modes
|
|
401
|
+
let zeroBtn = document.createElement('button');
|
|
402
|
+
zeroBtn.className = 'j-btn secondary';
|
|
403
|
+
zeroBtn.textContent = '0 poeng';
|
|
404
|
+
|
|
405
|
+
// Register this team as pending in duel mode
|
|
406
|
+
if (gameMode === 'duel') {
|
|
407
|
+
duelPendingTeams.add(i);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const registerScore = (delta)=>{
|
|
411
|
+
if (tileStates[key] && tileStates[key].locked) return;
|
|
412
|
+
setScore(i, delta);
|
|
413
|
+
try {
|
|
414
|
+
const ci = parseInt(String(key).split('|')[0], 10);
|
|
415
|
+
if (!isNaN(ci) && categoryStats[ci]) {
|
|
416
|
+
if (delta > 0) categoryStats[ci].correct++;
|
|
417
|
+
}
|
|
418
|
+
if (!isNaN(ci) && teamCategoryPoints[i]) {
|
|
419
|
+
if (typeof teamCategoryPoints[i][ci] !== 'number') teamCategoryPoints[i][ci] = 0;
|
|
420
|
+
teamCategoryPoints[i][ci] += delta;
|
|
421
|
+
}
|
|
422
|
+
} catch(e){}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const finalizeIfNeeded = ()=>{
|
|
426
|
+
if (gameMode === 'duel') {
|
|
427
|
+
if (duelPendingTeams.size > 0) return;
|
|
428
|
+
} else {
|
|
429
|
+
// turn-based: single decision ends the question
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
scored = true;
|
|
433
|
+
tileStates[key] = { locked: true };
|
|
434
|
+
if (tile) { tile.classList.add('used'); tile.disabled = true; }
|
|
435
|
+
if (gameMode==='turn' && teams.length>0){ currentTurn = (currentTurn+1)%teams.length; updateActiveTeamHighlight(); }
|
|
436
|
+
try { saveState(); setTimeout(()=>{ hideModal(); checkCompletionAndShowWinner(); }, 300); } catch(e){}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
const handleAdd = ()=>{
|
|
440
|
+
if (gameMode === 'turn') {
|
|
441
|
+
if (scored) return;
|
|
442
|
+
registerScore(value);
|
|
443
|
+
scored = true;
|
|
444
|
+
if (add) { add.disabled = true; add.classList.add('used-choice'); }
|
|
445
|
+
if (zeroBtn) { zeroBtn.disabled = true; zeroBtn.classList.add('used-choice'); }
|
|
446
|
+
finalizeIfNeeded();
|
|
447
|
+
} else {
|
|
448
|
+
if (!duelPendingTeams.has(i)) return;
|
|
449
|
+
registerScore(value);
|
|
450
|
+
duelPendingTeams.delete(i);
|
|
451
|
+
if (add) { add.disabled = true; add.classList.add('used-choice'); }
|
|
452
|
+
if (zeroBtn) { zeroBtn.disabled = true; zeroBtn.classList.add('used-choice'); }
|
|
453
|
+
finalizeIfNeeded();
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const handleZero = ()=>{
|
|
458
|
+
if (gameMode === 'turn') {
|
|
459
|
+
if (scored) return;
|
|
460
|
+
// Explicitly choose 0 points: no score change, but consume the question
|
|
461
|
+
scored = true;
|
|
462
|
+
if (add) { add.disabled = true; add.classList.add('used-choice'); }
|
|
463
|
+
if (zeroBtn) { zeroBtn.disabled = true; zeroBtn.classList.add('used-choice'); }
|
|
464
|
+
finalizeIfNeeded();
|
|
465
|
+
} else if (gameMode === 'duel') {
|
|
466
|
+
if (!duelPendingTeams.has(i)) return;
|
|
467
|
+
// Zero points: just clear pending state for this team
|
|
468
|
+
duelPendingTeams.delete(i);
|
|
469
|
+
if (add) { add.disabled = true; add.classList.add('used-choice'); }
|
|
470
|
+
if (zeroBtn) { zeroBtn.disabled = true; zeroBtn.classList.add('used-choice'); }
|
|
471
|
+
finalizeIfNeeded();
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
add.addEventListener('click', handleAdd);
|
|
476
|
+
if (zeroBtn) zeroBtn.addEventListener('click', handleZero);
|
|
477
|
+
|
|
478
|
+
if (tileStates[key] && tileStates[key].locked) {
|
|
479
|
+
add.disabled = true;
|
|
480
|
+
if (zeroBtn) zeroBtn.disabled = true;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
teamCol.appendChild(add);
|
|
484
|
+
if (zeroBtn) teamCol.appendChild(zeroBtn);
|
|
485
|
+
teamActions.appendChild(teamCol);
|
|
486
|
+
});
|
|
487
|
+
const footerRight = document.createElement('div');
|
|
488
|
+
footerRight.className = 'jeopardy-footer-right';
|
|
489
|
+
footerRight.appendChild(revealBtn);
|
|
490
|
+
footer.appendChild(teamActions);
|
|
491
|
+
footer.appendChild(footerRight);
|
|
492
|
+
|
|
493
|
+
renderMathIfAvailable(q); renderMathIfAvailable(a);
|
|
494
|
+
highlightCodeIfAvailable(q); highlightCodeIfAvailable(a);
|
|
495
|
+
|
|
496
|
+
backdrop.style.display = 'flex';
|
|
497
|
+
enableEscClose();
|
|
498
|
+
startTimer(onTimeout);
|
|
499
|
+
closeBtn.onclick = hideModal; backdrop.onclick = (e)=>{ if(e.target===backdrop) hideModal(); };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const setup = document.createElement('div'); setup.className='jeopardy-setup';
|
|
503
|
+
const fTeams = document.createElement('div'); fTeams.className='jp-field';
|
|
504
|
+
const lTeams = document.createElement('label'); lTeams.textContent='Antall lag:'; const sTeams=document.createElement('select');
|
|
505
|
+
[1,2,3,4,5,6].forEach(n=>{ const opt=document.createElement('option'); opt.value=String(n); opt.textContent=String(n); if(n===nTeams) opt.selected=true; sTeams.appendChild(opt); });
|
|
506
|
+
fTeams.appendChild(lTeams); fTeams.appendChild(sTeams);
|
|
507
|
+
const namesWrap = document.createElement('div'); namesWrap.className='jp-names';
|
|
508
|
+
function renderNames(){ namesWrap.innerHTML=''; const n=parseInt(sTeams.value,10)||1; for(let i=0;i<n;i++){ const row=document.createElement('div'); row.className='jp-name-row'; const lbl=document.createElement('label'); lbl.textContent=`Lagnavn ${i+1}`; const inp=document.createElement('input'); inp.type='text'; inp.value=`Lag ${i+1}`; row.appendChild(lbl); row.appendChild(inp); namesWrap.appendChild(row);} }
|
|
509
|
+
sTeams.addEventListener('change', renderNames); renderNames();
|
|
510
|
+
const fTimer=document.createElement('div'); fTimer.className='jp-field'; const lTimer=document.createElement('label'); lTimer.textContent='Timer:'; const sTimer=document.createElement('select'); [{label:'∞',ms:0},{label:'30s',ms:30000},{label:'1 min',ms:60000},{label:'2 min',ms:120000}].forEach((t,i)=>{ const opt=document.createElement('option'); opt.value=String(t.ms); opt.textContent=t.label; if(i===0) opt.selected=true; sTimer.appendChild(opt);}); fTimer.appendChild(lTimer); fTimer.appendChild(sTimer);
|
|
511
|
+
const fMode=document.createElement('div'); fMode.className='jp-field'; const lMode=document.createElement('label'); lMode.textContent='Modus:'; const sMode=document.createElement('select'); [{v:'turn',t:'Turn-based'},{v:'duel',t:'Duell'}].forEach(m=>{ const opt=document.createElement('option'); opt.value=m.v; opt.textContent=m.t; if(m.v==='duel') opt.selected=true; sMode.appendChild(opt);}); fMode.appendChild(lMode); fMode.appendChild(sMode);
|
|
512
|
+
const startBtn=document.createElement('button'); startBtn.className='j-btn primary'; startBtn.textContent='Start spill';
|
|
513
|
+
startBtn.addEventListener('click', ()=>{
|
|
514
|
+
const newN = parseInt(sTeams.value,10)||1;
|
|
515
|
+
const names = Array.from(namesWrap.querySelectorAll('input')).map((inp,i)=> inp.value && inp.value.trim() ? inp.value.trim() : `Lag ${i+1}`);
|
|
516
|
+
gameMode = sMode.value==='turn' ? 'turn' : 'duel';
|
|
517
|
+
timerMs = parseInt(sTimer.value,10)||0;
|
|
518
|
+
currentTurn = Math.floor(Math.random()*Math.max(1,newN));
|
|
519
|
+
started = true;
|
|
520
|
+
try { if (typeof resumePrompt !== 'undefined' && resumePrompt && resumePrompt.parentNode) resumePrompt.remove(); } catch(e){}
|
|
521
|
+
rebuildTeams(newN, names);
|
|
522
|
+
try { scorebar.style.display = ''; } catch(e){}
|
|
523
|
+
try { resetBtn.style.display = ''; } catch(e){}
|
|
524
|
+
try { updateActiveTeamHighlight(); } catch(e){}
|
|
525
|
+
setup.style.display='none';
|
|
526
|
+
try { saveState(); } catch(e){}
|
|
527
|
+
});
|
|
528
|
+
setup.appendChild(fTeams); setup.appendChild(namesWrap); setup.appendChild(fTimer); setup.appendChild(fMode); setup.appendChild(startBtn);
|
|
529
|
+
|
|
530
|
+
container.innerHTML = '';
|
|
531
|
+
const saved = loadState();
|
|
532
|
+
let resumePrompt = null;
|
|
533
|
+
if (saved && (saved.started || (saved.tileStates && Object.keys(saved.tileStates).length>0) || (Array.isArray(saved.teams) && saved.teams.some(t=> (t&&Number(t.score)||0)!==0)))){
|
|
534
|
+
resumePrompt = document.createElement('div'); resumePrompt.className='jeopardy-resume-prompt';
|
|
535
|
+
const txt = document.createElement('div'); txt.className='jeopardy-resume-text'; txt.textContent = 'Fortsett der du slapp?';
|
|
536
|
+
const actions = document.createElement('div'); actions.className='jeopardy-resume-actions';
|
|
537
|
+
const btnStart = document.createElement('button'); btnStart.className='j-btn accent'; btnStart.textContent='Start fra begynnelsen';
|
|
538
|
+
const btnResume = document.createElement('button'); btnResume.className='j-btn primary'; btnResume.textContent='Fortsett';
|
|
539
|
+
actions.appendChild(btnStart); actions.appendChild(btnResume);
|
|
540
|
+
resumePrompt.appendChild(txt); resumePrompt.appendChild(actions);
|
|
541
|
+
btnStart.addEventListener('click', ()=>{
|
|
542
|
+
try { clearState(); } catch(e){}
|
|
543
|
+
try { resumePrompt.remove(); } catch(e){}
|
|
544
|
+
try { setup.style.display = ''; } catch(e){}
|
|
545
|
+
});
|
|
546
|
+
btnResume.addEventListener('click', ()=>{ try { applySavedState(saved); } catch(e){}; try { resumePrompt.remove(); } catch(e){}; });
|
|
547
|
+
container.appendChild(resumePrompt);
|
|
548
|
+
try { setup.style.display = 'none'; } catch(e){}
|
|
549
|
+
}
|
|
550
|
+
container.appendChild(setup);
|
|
551
|
+
container.appendChild(topbar);
|
|
552
|
+
container.appendChild(turnIndicator);
|
|
553
|
+
container.appendChild(table);
|
|
554
|
+
container.appendChild(backdrop);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
document.addEventListener('DOMContentLoaded', function(){
|
|
558
|
+
document.querySelectorAll('.jeopardy-container[data-config]').forEach(initJeopardy);
|
|
559
|
+
});
|
|
560
|
+
})();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
class DraggableItem {
|
|
2
|
+
constructor(id, content, pairId) {
|
|
3
|
+
this.id = id;
|
|
4
|
+
this.content = content;
|
|
5
|
+
this.pairId = pairId;
|
|
6
|
+
this.element = null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
createElement() {
|
|
10
|
+
const div = document.createElement('div');
|
|
11
|
+
div.classList.add('draggable-item');
|
|
12
|
+
div.setAttribute('draggable', 'true');
|
|
13
|
+
div.dataset.id = this.id;
|
|
14
|
+
div.dataset.pairId = this.pairId;
|
|
15
|
+
|
|
16
|
+
// Set the inner HTML content directly
|
|
17
|
+
div.innerHTML = this.content;
|
|
18
|
+
this.element = div;
|
|
19
|
+
|
|
20
|
+
// Apply syntax highlighting if there is a <pre><code> block
|
|
21
|
+
if (this.containsCodeBlock(this.content)) {
|
|
22
|
+
// Only try to highlight if hljs (highlight.js) is available
|
|
23
|
+
if (typeof hljs !== 'undefined') {
|
|
24
|
+
hljs.highlightElement(div.querySelector('code'));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Render LaTeX math
|
|
29
|
+
this.renderMath();
|
|
30
|
+
|
|
31
|
+
this.addDragEvents();
|
|
32
|
+
return div;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
containsCodeBlock(content) {
|
|
36
|
+
// Check if the content contains a <pre><code> block
|
|
37
|
+
return /<pre><code[\s\S]*<\/code><\/pre>/.test(content);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
renderMath() {
|
|
41
|
+
// Ensure KaTeX renders LaTeX inside the item
|
|
42
|
+
renderMathInElement(this.element, {
|
|
43
|
+
delimiters: [
|
|
44
|
+
{ left: "$$", right: "$$", display: true },
|
|
45
|
+
{ left: "$", right: "$", display: false },
|
|
46
|
+
{ left: "\\[", right: "\\]", display: true },
|
|
47
|
+
{ left: "\\(", right: "\\)", display: false }
|
|
48
|
+
]
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
addDragEvents() {
|
|
53
|
+
this.element.addEventListener('dragstart', (e) => {
|
|
54
|
+
e.dataTransfer.setData('text/plain', this.id);
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
this.element.style.opacity = '0'; // Hide the item temporarily while dragging
|
|
57
|
+
}, 0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
this.element.addEventListener('dragend', () => {
|
|
61
|
+
this.element.style.opacity = '1'; // Show the item again when dragging ends
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|