@shaykec/bridge 0.1.0
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.
- package/README.md +65 -0
- package/package.json +26 -0
- package/src/protocol.js +303 -0
- package/src/protocol.test.js +373 -0
- package/src/router.js +149 -0
- package/src/router.test.js +329 -0
- package/src/server.js +497 -0
- package/src/templates.js +155 -0
- package/src/templates.test.js +256 -0
- package/templates/celebrate.html +259 -0
- package/templates/code-playground.html +294 -0
- package/templates/dashboard.html +337 -0
- package/templates/diagram-architecture.html +449 -0
- package/templates/diagram-flow.html +382 -0
- package/templates/diagram-mermaid.html +220 -0
- package/templates/quiz-drag-order.html +375 -0
- package/templates/quiz-fill-blank.html +468 -0
- package/templates/quiz-matching.html +501 -0
- package/templates/quiz-timed-choice.html +361 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>ClaudeTeach — Fill in the Blank</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
body {
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
11
|
+
background: #0d1117;
|
|
12
|
+
color: #c9d1d9;
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
min-height: 100vh;
|
|
18
|
+
padding: 2rem;
|
|
19
|
+
}
|
|
20
|
+
h2 {
|
|
21
|
+
color: #58a6ff;
|
|
22
|
+
margin-bottom: 0.5rem;
|
|
23
|
+
font-size: 1.2rem;
|
|
24
|
+
text-align: center;
|
|
25
|
+
}
|
|
26
|
+
.question {
|
|
27
|
+
margin-bottom: 1.5rem;
|
|
28
|
+
font-size: 1.1rem;
|
|
29
|
+
text-align: center;
|
|
30
|
+
max-width: 600px;
|
|
31
|
+
line-height: 1.5;
|
|
32
|
+
}
|
|
33
|
+
.code-block {
|
|
34
|
+
background: #161b22;
|
|
35
|
+
border: 1px solid #30363d;
|
|
36
|
+
border-radius: 8px;
|
|
37
|
+
padding: 1.25rem 1.5rem;
|
|
38
|
+
max-width: 650px;
|
|
39
|
+
width: 100%;
|
|
40
|
+
margin-bottom: 1.5rem;
|
|
41
|
+
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
|
42
|
+
font-size: 14px;
|
|
43
|
+
line-height: 1.8;
|
|
44
|
+
overflow-x: auto;
|
|
45
|
+
white-space: pre-wrap;
|
|
46
|
+
position: relative;
|
|
47
|
+
}
|
|
48
|
+
.language-label {
|
|
49
|
+
position: absolute;
|
|
50
|
+
top: 0.5rem;
|
|
51
|
+
right: 0.75rem;
|
|
52
|
+
background: #21262d;
|
|
53
|
+
color: #8b949e;
|
|
54
|
+
padding: 0.15rem 0.5rem;
|
|
55
|
+
border-radius: 4px;
|
|
56
|
+
font-size: 0.7rem;
|
|
57
|
+
text-transform: uppercase;
|
|
58
|
+
letter-spacing: 0.03em;
|
|
59
|
+
}
|
|
60
|
+
.code-line {
|
|
61
|
+
display: block;
|
|
62
|
+
padding: 1px 0;
|
|
63
|
+
}
|
|
64
|
+
.code-line-number {
|
|
65
|
+
display: inline-block;
|
|
66
|
+
width: 2.5em;
|
|
67
|
+
text-align: right;
|
|
68
|
+
padding-right: 1em;
|
|
69
|
+
color: #484f58;
|
|
70
|
+
user-select: none;
|
|
71
|
+
font-size: 13px;
|
|
72
|
+
}
|
|
73
|
+
.blank-input {
|
|
74
|
+
background: #0d1117;
|
|
75
|
+
border: 1px dashed #58a6ff;
|
|
76
|
+
border-radius: 4px;
|
|
77
|
+
color: #58a6ff;
|
|
78
|
+
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
|
79
|
+
font-size: 14px;
|
|
80
|
+
padding: 2px 6px;
|
|
81
|
+
min-width: 80px;
|
|
82
|
+
outline: none;
|
|
83
|
+
transition: border-color 0.15s, background 0.15s;
|
|
84
|
+
}
|
|
85
|
+
.blank-input:focus {
|
|
86
|
+
border-color: #58a6ff;
|
|
87
|
+
background: #1c2333;
|
|
88
|
+
box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.2);
|
|
89
|
+
}
|
|
90
|
+
.blank-input.correct {
|
|
91
|
+
border-color: #238636;
|
|
92
|
+
color: #3fb950;
|
|
93
|
+
background: rgba(35, 134, 54, 0.1);
|
|
94
|
+
}
|
|
95
|
+
.blank-input.incorrect {
|
|
96
|
+
border-color: #f85149;
|
|
97
|
+
color: #f85149;
|
|
98
|
+
background: rgba(248, 81, 73, 0.1);
|
|
99
|
+
}
|
|
100
|
+
.blank-input:disabled {
|
|
101
|
+
cursor: not-allowed;
|
|
102
|
+
opacity: 0.8;
|
|
103
|
+
}
|
|
104
|
+
.hint-text {
|
|
105
|
+
font-size: 0.8rem;
|
|
106
|
+
color: #8b949e;
|
|
107
|
+
margin-bottom: 1.5rem;
|
|
108
|
+
max-width: 600px;
|
|
109
|
+
text-align: center;
|
|
110
|
+
}
|
|
111
|
+
.btn-row {
|
|
112
|
+
display: flex;
|
|
113
|
+
gap: 0.75rem;
|
|
114
|
+
margin-bottom: 1rem;
|
|
115
|
+
}
|
|
116
|
+
.btn {
|
|
117
|
+
padding: 0.6rem 1.5rem;
|
|
118
|
+
border: none;
|
|
119
|
+
border-radius: 6px;
|
|
120
|
+
font-size: 0.95rem;
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
font-weight: 500;
|
|
123
|
+
transition: background 0.15s;
|
|
124
|
+
}
|
|
125
|
+
.btn:focus { outline: 2px solid #58a6ff; outline-offset: 2px; }
|
|
126
|
+
.btn-primary {
|
|
127
|
+
background: #238636;
|
|
128
|
+
color: #fff;
|
|
129
|
+
}
|
|
130
|
+
.btn-primary:hover { background: #2ea043; }
|
|
131
|
+
.btn-primary:disabled {
|
|
132
|
+
background: #21262d;
|
|
133
|
+
color: #484f58;
|
|
134
|
+
cursor: not-allowed;
|
|
135
|
+
}
|
|
136
|
+
.btn-secondary {
|
|
137
|
+
background: #21262d;
|
|
138
|
+
color: #c9d1d9;
|
|
139
|
+
border: 1px solid #30363d;
|
|
140
|
+
}
|
|
141
|
+
.btn-secondary:hover { background: #30363d; }
|
|
142
|
+
.btn-hint {
|
|
143
|
+
background: #1c2333;
|
|
144
|
+
color: #58a6ff;
|
|
145
|
+
border: 1px solid #30363d;
|
|
146
|
+
}
|
|
147
|
+
.btn-hint:hover { background: #283040; }
|
|
148
|
+
.result {
|
|
149
|
+
margin-top: 1rem;
|
|
150
|
+
padding: 1rem 1.5rem;
|
|
151
|
+
border-radius: 8px;
|
|
152
|
+
font-weight: 500;
|
|
153
|
+
text-align: center;
|
|
154
|
+
display: none;
|
|
155
|
+
max-width: 600px;
|
|
156
|
+
width: 100%;
|
|
157
|
+
}
|
|
158
|
+
.result.correct {
|
|
159
|
+
display: block;
|
|
160
|
+
background: rgba(35, 134, 54, 0.15);
|
|
161
|
+
border: 1px solid #238636;
|
|
162
|
+
color: #3fb950;
|
|
163
|
+
}
|
|
164
|
+
.result.incorrect {
|
|
165
|
+
display: block;
|
|
166
|
+
background: rgba(248, 81, 73, 0.15);
|
|
167
|
+
border: 1px solid #f85149;
|
|
168
|
+
color: #f85149;
|
|
169
|
+
}
|
|
170
|
+
.result .correct-answers {
|
|
171
|
+
margin-top: 0.5rem;
|
|
172
|
+
font-size: 0.9rem;
|
|
173
|
+
font-weight: 400;
|
|
174
|
+
}
|
|
175
|
+
.sr-only {
|
|
176
|
+
position: absolute;
|
|
177
|
+
width: 1px;
|
|
178
|
+
height: 1px;
|
|
179
|
+
padding: 0;
|
|
180
|
+
margin: -1px;
|
|
181
|
+
overflow: hidden;
|
|
182
|
+
clip: rect(0, 0, 0, 0);
|
|
183
|
+
border: 0;
|
|
184
|
+
}
|
|
185
|
+
</style>
|
|
186
|
+
</head>
|
|
187
|
+
<body>
|
|
188
|
+
<h2>Fill in the Blanks</h2>
|
|
189
|
+
<div class="question" id="question">{{question}}</div>
|
|
190
|
+
<div aria-live="polite" class="sr-only" id="announcer"></div>
|
|
191
|
+
<div class="code-block" id="codeBlock">
|
|
192
|
+
<span class="language-label" id="langLabel">bash</span>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="hint-text" id="hintText">Fill in the missing parts of the code and click Submit.</div>
|
|
195
|
+
<div class="btn-row">
|
|
196
|
+
<button class="btn btn-primary" id="submitBtn" onclick="submitAnswer()">Submit</button>
|
|
197
|
+
<button class="btn btn-secondary" onclick="resetBlanks()">Reset</button>
|
|
198
|
+
<button class="btn btn-hint" onclick="showHint()">Hint</button>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="result" id="result"></div>
|
|
201
|
+
|
|
202
|
+
<script>
|
|
203
|
+
/* --- Bridge connection --- */
|
|
204
|
+
var ws = null;
|
|
205
|
+
function connectBridge() {
|
|
206
|
+
try {
|
|
207
|
+
ws = new WebSocket('ws://' + location.host + '/ws');
|
|
208
|
+
ws.onopen = function() {
|
|
209
|
+
ws.send(JSON.stringify({
|
|
210
|
+
v: 1, type: 'sys:connect',
|
|
211
|
+
payload: { clientType: 'template' },
|
|
212
|
+
source: 'template', timestamp: Date.now()
|
|
213
|
+
}));
|
|
214
|
+
};
|
|
215
|
+
ws.onerror = function() { ws = null; };
|
|
216
|
+
ws.onclose = function() { ws = null; };
|
|
217
|
+
} catch(e) { ws = null; }
|
|
218
|
+
}
|
|
219
|
+
connectBridge();
|
|
220
|
+
|
|
221
|
+
function sendResultMsg(payload) {
|
|
222
|
+
var msg = {
|
|
223
|
+
v: 1, type: 'event:quiz-answer',
|
|
224
|
+
payload: payload,
|
|
225
|
+
source: 'template', timestamp: Date.now()
|
|
226
|
+
};
|
|
227
|
+
if (ws && ws.readyState === 1) { ws.send(JSON.stringify(msg)); }
|
|
228
|
+
try {
|
|
229
|
+
fetch('/api/event', {
|
|
230
|
+
method: 'POST',
|
|
231
|
+
headers: { 'Content-Type': 'application/json' },
|
|
232
|
+
body: JSON.stringify(msg)
|
|
233
|
+
}).catch(function(){});
|
|
234
|
+
} catch(e) {}
|
|
235
|
+
if (window.parent !== window) { window.parent.postMessage(msg, '*'); }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* --- Quiz data --- */
|
|
239
|
+
var quizData = {
|
|
240
|
+
question: 'Complete the code below:',
|
|
241
|
+
language: 'bash',
|
|
242
|
+
/* codeLines: array of strings, blanks marked as ___N___ where N is blank index */
|
|
243
|
+
codeLines: [
|
|
244
|
+
'git ___0___ feature-branch',
|
|
245
|
+
'git ___1___ feature-branch',
|
|
246
|
+
'echo "Ready to code!"'
|
|
247
|
+
],
|
|
248
|
+
blanks: [
|
|
249
|
+
{ answer: 'branch', hint: 'Creates a new branch', placeholder: '...' },
|
|
250
|
+
{ answer: 'checkout', hint: 'Switches to the branch', placeholder: '...' }
|
|
251
|
+
]
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
var startTime = Date.now();
|
|
255
|
+
var answered = false;
|
|
256
|
+
|
|
257
|
+
function init(data) {
|
|
258
|
+
if (data) quizData = Object.assign({}, quizData, data);
|
|
259
|
+
|
|
260
|
+
// Support items array format
|
|
261
|
+
if (quizData.items && !quizData.blanks) {
|
|
262
|
+
quizData.blanks = quizData.items;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
document.getElementById('question').textContent = quizData.question;
|
|
266
|
+
document.getElementById('langLabel').textContent = quizData.language || 'code';
|
|
267
|
+
startTime = Date.now();
|
|
268
|
+
answered = false;
|
|
269
|
+
document.getElementById('result').className = 'result';
|
|
270
|
+
document.getElementById('result').style.display = 'none';
|
|
271
|
+
document.getElementById('submitBtn').disabled = false;
|
|
272
|
+
renderCode();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function renderCode() {
|
|
276
|
+
var block = document.getElementById('codeBlock');
|
|
277
|
+
var langLabel = '<span class="language-label">' + escapeHtml(quizData.language || 'code') + '</span>';
|
|
278
|
+
var html = langLabel;
|
|
279
|
+
|
|
280
|
+
quizData.codeLines.forEach(function(line, lineNum) {
|
|
281
|
+
var lineHtml = '<span class="code-line"><span class="code-line-number">' + (lineNum + 1) + '</span>';
|
|
282
|
+
|
|
283
|
+
// Replace ___N___ with input fields
|
|
284
|
+
var processed = line.replace(/___(\d+)___/g, function(match, idx) {
|
|
285
|
+
var blankIdx = parseInt(idx);
|
|
286
|
+
var blank = quizData.blanks[blankIdx] || {};
|
|
287
|
+
var placeholder = blank.placeholder || '...';
|
|
288
|
+
var size = Math.max(blank.answer ? blank.answer.length + 2 : 10, 8);
|
|
289
|
+
return '<input type="text" class="blank-input" data-blank="' + blankIdx + '" ' +
|
|
290
|
+
'placeholder="' + escapeHtml(placeholder) + '" ' +
|
|
291
|
+
'size="' + size + '" ' +
|
|
292
|
+
'autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" ' +
|
|
293
|
+
'aria-label="Blank ' + (blankIdx + 1) + (blank.hint ? ': ' + blank.hint : '') + '">';
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Escape any non-blank parts
|
|
297
|
+
var parts = line.split(/___\d+___/);
|
|
298
|
+
var blanks = line.match(/___\d+___/g) || [];
|
|
299
|
+
var finalHtml = '';
|
|
300
|
+
parts.forEach(function(part, i) {
|
|
301
|
+
finalHtml += escapeHtml(part);
|
|
302
|
+
if (i < blanks.length) {
|
|
303
|
+
var blankIdx = parseInt(blanks[i].replace(/___/g, ''));
|
|
304
|
+
var blank = quizData.blanks[blankIdx] || {};
|
|
305
|
+
var placeholder = blank.placeholder || '...';
|
|
306
|
+
var size = Math.max(blank.answer ? blank.answer.length + 2 : 10, 8);
|
|
307
|
+
finalHtml += '<input type="text" class="blank-input" data-blank="' + blankIdx + '" ' +
|
|
308
|
+
'placeholder="' + escapeHtml(placeholder) + '" ' +
|
|
309
|
+
'size="' + size + '" ' +
|
|
310
|
+
'autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" ' +
|
|
311
|
+
'aria-label="Blank ' + (blankIdx + 1) + (blank.hint ? ': ' + blank.hint : '') + '">';
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
lineHtml += finalHtml + '</span>';
|
|
316
|
+
html += lineHtml;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
block.innerHTML = html;
|
|
320
|
+
|
|
321
|
+
// Add Enter key to submit
|
|
322
|
+
var inputs = block.querySelectorAll('.blank-input');
|
|
323
|
+
inputs.forEach(function(input, i) {
|
|
324
|
+
input.addEventListener('keydown', function(e) {
|
|
325
|
+
if (e.key === 'Enter') {
|
|
326
|
+
e.preventDefault();
|
|
327
|
+
// Move to next blank or submit
|
|
328
|
+
if (i < inputs.length - 1) {
|
|
329
|
+
inputs[i + 1].focus();
|
|
330
|
+
} else {
|
|
331
|
+
submitAnswer();
|
|
332
|
+
}
|
|
333
|
+
} else if (e.key === 'Tab' && !e.shiftKey && i < inputs.length - 1) {
|
|
334
|
+
e.preventDefault();
|
|
335
|
+
inputs[i + 1].focus();
|
|
336
|
+
} else if (e.key === 'Tab' && e.shiftKey && i > 0) {
|
|
337
|
+
e.preventDefault();
|
|
338
|
+
inputs[i - 1].focus();
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Focus first blank
|
|
344
|
+
if (inputs.length > 0) inputs[0].focus();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function submitAnswer() {
|
|
348
|
+
if (answered) return;
|
|
349
|
+
answered = true;
|
|
350
|
+
|
|
351
|
+
var inputs = document.querySelectorAll('.blank-input');
|
|
352
|
+
var correctCount = 0;
|
|
353
|
+
var totalBlanks = quizData.blanks.length;
|
|
354
|
+
var userAnswers = [];
|
|
355
|
+
|
|
356
|
+
inputs.forEach(function(input) {
|
|
357
|
+
var blankIdx = parseInt(input.dataset.blank);
|
|
358
|
+
var blank = quizData.blanks[blankIdx];
|
|
359
|
+
var userAnswer = input.value.trim();
|
|
360
|
+
userAnswers.push(userAnswer);
|
|
361
|
+
|
|
362
|
+
// Check answer - case-insensitive, trimmed
|
|
363
|
+
var isCorrect = false;
|
|
364
|
+
if (blank) {
|
|
365
|
+
var expected = blank.answer;
|
|
366
|
+
if (Array.isArray(expected)) {
|
|
367
|
+
// Multiple acceptable answers
|
|
368
|
+
isCorrect = expected.some(function(a) {
|
|
369
|
+
return a.trim().toLowerCase() === userAnswer.toLowerCase();
|
|
370
|
+
});
|
|
371
|
+
} else {
|
|
372
|
+
isCorrect = expected.trim().toLowerCase() === userAnswer.toLowerCase();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
input.disabled = true;
|
|
377
|
+
if (isCorrect) {
|
|
378
|
+
input.classList.add('correct');
|
|
379
|
+
correctCount++;
|
|
380
|
+
} else {
|
|
381
|
+
input.classList.add('incorrect');
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
var allCorrect = correctCount === totalBlanks;
|
|
386
|
+
var timeMs = Date.now() - startTime;
|
|
387
|
+
|
|
388
|
+
var resultEl = document.getElementById('result');
|
|
389
|
+
resultEl.className = 'result ' + (allCorrect ? 'correct' : 'incorrect');
|
|
390
|
+
|
|
391
|
+
if (allCorrect) {
|
|
392
|
+
resultEl.innerHTML = 'Perfect! All blanks filled correctly! (' + (timeMs / 1000).toFixed(1) + 's)';
|
|
393
|
+
} else {
|
|
394
|
+
var correctAnswers = quizData.blanks.map(function(b, i) {
|
|
395
|
+
var answer = Array.isArray(b.answer) ? b.answer[0] : b.answer;
|
|
396
|
+
return 'Blank ' + (i + 1) + ': <strong>' + escapeHtml(answer) + '</strong>';
|
|
397
|
+
}).join(' | ');
|
|
398
|
+
resultEl.innerHTML = correctCount + ' of ' + totalBlanks + ' correct.' +
|
|
399
|
+
'<div class="correct-answers">Answers: ' + correctAnswers + '</div>';
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
document.getElementById('submitBtn').disabled = true;
|
|
403
|
+
announce(allCorrect ? 'All blanks correct!' : correctCount + ' of ' + totalBlanks + ' correct');
|
|
404
|
+
|
|
405
|
+
sendResultMsg({
|
|
406
|
+
quizType: 'fill-blank',
|
|
407
|
+
answer: userAnswers,
|
|
408
|
+
correct: allCorrect,
|
|
409
|
+
score: correctCount,
|
|
410
|
+
total: totalBlanks,
|
|
411
|
+
timeMs: timeMs
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function resetBlanks() {
|
|
416
|
+
answered = false;
|
|
417
|
+
document.getElementById('result').className = 'result';
|
|
418
|
+
document.getElementById('result').style.display = 'none';
|
|
419
|
+
document.getElementById('submitBtn').disabled = false;
|
|
420
|
+
renderCode();
|
|
421
|
+
announce('Blanks reset');
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function showHint() {
|
|
425
|
+
var inputs = document.querySelectorAll('.blank-input');
|
|
426
|
+
var shownHint = false;
|
|
427
|
+
inputs.forEach(function(input) {
|
|
428
|
+
if (input.value.trim() === '' && !shownHint) {
|
|
429
|
+
var blankIdx = parseInt(input.dataset.blank);
|
|
430
|
+
var blank = quizData.blanks[blankIdx];
|
|
431
|
+
if (blank && blank.hint) {
|
|
432
|
+
document.getElementById('hintText').textContent = 'Hint for blank ' + (blankIdx + 1) + ': ' + blank.hint;
|
|
433
|
+
input.focus();
|
|
434
|
+
shownHint = true;
|
|
435
|
+
announce('Hint: ' + blank.hint);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
if (!shownHint) {
|
|
440
|
+
document.getElementById('hintText').textContent = 'No more hints available.';
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function escapeHtml(str) {
|
|
445
|
+
var div = document.createElement('div');
|
|
446
|
+
div.textContent = str;
|
|
447
|
+
return div.innerHTML;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function announce(msg) {
|
|
451
|
+
document.getElementById('announcer').textContent = msg;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/* --- Initialization --- */
|
|
455
|
+
window.addEventListener('message', function(e) {
|
|
456
|
+
if (e.data && e.data.type === 'quiz-data') {
|
|
457
|
+
init(e.data.payload);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
if (window.__QUIZ_DATA__) {
|
|
462
|
+
init(window.__QUIZ_DATA__);
|
|
463
|
+
} else {
|
|
464
|
+
init();
|
|
465
|
+
}
|
|
466
|
+
</script>
|
|
467
|
+
</body>
|
|
468
|
+
</html>
|