@nebularstreams/overlay-sdk 1.0.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/package.json +44 -0
- package/preview/assets/images/favicon.ico +0 -0
- package/preview/assets/images/favicon.png +0 -0
- package/preview/assets/images/login-right.webp +0 -0
- package/preview/assets/images/nebular.color.100h.png +0 -0
- package/preview/assets/images/nebular.color.white.100h.png +0 -0
- package/preview/assets/images/nebular.medallion.transparent..png +0 -0
- package/preview/assets/images/testcard.png +0 -0
- package/preview/assets/webfonts/fa-brands-400.woff2 +0 -0
- package/preview/assets/webfonts/fa-solid-900.woff2 +0 -0
- package/preview/bundle.min.js +6 -0
- package/preview/index.css +28 -0
- package/preview/index.html +23 -0
- package/readme.md +132 -0
- package/skill/nebular-overlay/SKILL.md +56 -0
- package/skill/nebular-overlay/agents/openai.yaml +4 -0
- package/skill/nebular-overlay/references/examples.md +327 -0
- package/skill/nebular-overlay/references/manifest.md +153 -0
- package/skill/nebular-overlay/references/runtime-and-design.md +88 -0
- package/skill/nebular-overlay/scripts/validate-templates.mjs +244 -0
- package/templates/all/broadcast/breaking-news/index.html +100 -0
- package/templates/all/broadcast/breaking-news/manifest.json +9 -0
- package/templates/all/broadcast/empty/index.html +46 -0
- package/templates/all/broadcast/empty/manifest.json +7 -0
- package/templates/all/broadcast/empty/nebular.medallion.transparent.png +0 -0
- package/templates/all/broadcast/event-countdown/index.html +160 -0
- package/templates/all/broadcast/event-countdown/logo.png +0 -0
- package/templates/all/broadcast/event-countdown/manifest.json +33 -0
- package/templates/all/broadcast/lower-third/index.html +77 -0
- package/templates/all/broadcast/lower-third/manifest.json +8 -0
- package/templates/all/broadcast/newsticker/index.html +153 -0
- package/templates/all/broadcast/newsticker/manifest.json +21 -0
- package/templates/all/broadcast/newsticker-frames/background-label-green.png +0 -0
- package/templates/all/broadcast/newsticker-frames/background-label-orange.png +0 -0
- package/templates/all/broadcast/newsticker-frames/banner.png +0 -0
- package/templates/all/broadcast/newsticker-frames/index.html +229 -0
- package/templates/all/broadcast/newsticker-frames/manifest.json +20 -0
- package/templates/all/broadcast/weather/index.html +382 -0
- package/templates/all/broadcast/weather/manifest.json +104 -0
- package/templates/all/engagement/call-to-action/index.html +116 -0
- package/templates/all/engagement/call-to-action/manifest.json +12 -0
- package/templates/all/engagement/chat/index.html +121 -0
- package/templates/all/engagement/chat/manifest.json +32 -0
- package/templates/all/engagement/post/index.html +132 -0
- package/templates/all/engagement/post/manifest.json +31 -0
- package/templates/all/gambling/bingo/index.html +227 -0
- package/templates/all/gambling/bingo/manifest.json +144 -0
- package/templates/all/gambling/keno/index.html +202 -0
- package/templates/all/gambling/keno/manifest.json +129 -0
- package/templates/all/gambling/lottery/index.html +159 -0
- package/templates/all/gambling/lottery/manifest.json +95 -0
- package/templates/all/gambling/prize-wall/index.html +256 -0
- package/templates/all/gambling/prize-wall/manifest.json +108 -0
- package/templates/all/gambling/roulette/index.html +200 -0
- package/templates/all/gambling/roulette/manifest.json +78 -0
- package/templates/all/gaming/battleship/index.html +578 -0
- package/templates/all/gaming/battleship/manifest.json +279 -0
- package/templates/all/gaming/gta/mission/index.html +121 -0
- package/templates/all/gaming/gta/mission/manifest.json +38 -0
- package/templates/all/sports/football/scoreboard/index.html +115 -0
- package/templates/all/sports/football/scoreboard/manifest.json +54 -0
- package/templates/all/sports/tennis/scoreboard/index.html +120 -0
- package/templates/all/sports/tennis/scoreboard/manifest.json +92 -0
- package/templates/all/trivia/audience-poll/index.html +60 -0
- package/templates/all/trivia/audience-poll/manifest.json +26 -0
- package/templates/all/trivia/classic-quiz/index.html +49 -0
- package/templates/all/trivia/classic-quiz/manifest.json +15 -0
- package/templates/all/trivia/lightning-round/index.html +126 -0
- package/templates/all/trivia/lightning-round/manifest.json +27 -0
- package/templates/all/trivia/pasapalabra/index.html +249 -0
- package/templates/all/trivia/pasapalabra/manifest.json +68 -0
- package/templates/all/trivia/quiz-deck/index.html +58 -0
- package/templates/all/trivia/quiz-deck/manifest.json +25 -0
- package/templates/all/trivia/true-or-false/index.html +81 -0
- package/templates/all/trivia/true-or-false/manifest.json +27 -0
- package/templates/basic/broadcast/empty/index.html +46 -0
- package/templates/basic/broadcast/empty/manifest.json +7 -0
- package/templates/basic/broadcast/empty/nebular.medallion.transparent.png +0 -0
- package/templates/basic/broadcast/event-countdown/index.html +160 -0
- package/templates/basic/broadcast/event-countdown/logo.png +0 -0
- package/templates/basic/broadcast/event-countdown/manifest.json +33 -0
- package/templates/basic/broadcast/lower-third/index.html +77 -0
- package/templates/basic/broadcast/lower-third/manifest.json +8 -0
- package/templates/basic/broadcast/newsticker/index.html +153 -0
- package/templates/basic/broadcast/newsticker/manifest.json +21 -0
- package/templates/basic/broadcast/weather/index.html +382 -0
- package/templates/basic/broadcast/weather/manifest.json +104 -0
- package/templates/basic/engagement/call-to-action/index.html +116 -0
- package/templates/basic/engagement/call-to-action/manifest.json +12 -0
- package/templates/basic/engagement/chat/index.html +121 -0
- package/templates/basic/engagement/chat/manifest.json +32 -0
- package/templates/basic/engagement/post/index.html +132 -0
- package/templates/basic/engagement/post/manifest.json +31 -0
- package/templates/basic/sports/football/scoreboard/index.html +115 -0
- package/templates/basic/sports/football/scoreboard/manifest.json +54 -0
- package/templates/basic/sports/tennis/scoreboard/index.html +120 -0
- package/templates/basic/sports/tennis/scoreboard/manifest.json +92 -0
- package/templates/debug/testing/security-probe/forbidden.js +1 -0
- package/templates/debug/testing/security-probe/forbidden.json +1 -0
- package/templates/debug/testing/security-probe/forbidden.mp4 +1 -0
- package/templates/debug/testing/security-probe/forbidden.svg +1 -0
- package/templates/debug/testing/security-probe/index.html +367 -0
- package/templates/debug/testing/security-probe/manifest.json +13 -0
- package/templates/empty/broadcast/empty/index.html +46 -0
- package/templates/empty/broadcast/empty/manifest.json +7 -0
- package/templates/empty/broadcast/empty/nebular.medallion.transparent.png +0 -0
- package/templates/games/gambling/bingo/index.html +227 -0
- package/templates/games/gambling/bingo/manifest.json +56 -0
- package/templates/games/gambling/keno/index.html +202 -0
- package/templates/games/gambling/keno/manifest.json +129 -0
- package/templates/games/gambling/lottery/index.html +159 -0
- package/templates/games/gambling/lottery/manifest.json +95 -0
- package/templates/games/gambling/prize-wall/index.html +256 -0
- package/templates/games/gambling/prize-wall/manifest.json +108 -0
- package/templates/games/gambling/roulette/index.html +200 -0
- package/templates/games/gambling/roulette/manifest.json +78 -0
- package/templates/games/gaming/battleship/index.html +578 -0
- package/templates/games/gaming/battleship/manifest.json +279 -0
- package/templates/games/gaming/gta/mission/index.html +121 -0
- package/templates/games/gaming/gta/mission/manifest.json +38 -0
- package/templates/games/trivia/audience-poll/index.html +60 -0
- package/templates/games/trivia/audience-poll/manifest.json +26 -0
- package/templates/games/trivia/classic-quiz/index.html +49 -0
- package/templates/games/trivia/classic-quiz/manifest.json +15 -0
- package/templates/games/trivia/lightning-round/index.html +126 -0
- package/templates/games/trivia/lightning-round/manifest.json +27 -0
- package/templates/games/trivia/pasapalabra/index.html +249 -0
- package/templates/games/trivia/pasapalabra/manifest.json +68 -0
- package/templates/games/trivia/quiz-deck/index.html +58 -0
- package/templates/games/trivia/quiz-deck/manifest.json +25 -0
- package/templates/games/trivia/true-or-false/index.html +81 -0
- package/templates/games/trivia/true-or-false/manifest.json +27 -0
- package/tool/cli.cjs +7 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Lightning Round</title>
|
|
6
|
+
<style>
|
|
7
|
+
* { box-sizing: border-box; }
|
|
8
|
+
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: transparent; }
|
|
9
|
+
body { --accent: #ff4e71; color: white; font-family: Inter, Arial, sans-serif; }
|
|
10
|
+
.round { position: absolute; left: 4%; right: 4%; bottom: 4%; display: grid; grid-template-columns: 220px 1fr 210px; min-height: 225px; overflow: hidden; border-radius: 26px; background: rgba(15,13,25,.97); border: 2px solid #ffffff24; box-shadow: 0 24px 70px #000b; transition: opacity .35s ease, transform .35s ease; }
|
|
11
|
+
.round.hidden { opacity: 0; transform: translateY(35px); }
|
|
12
|
+
.brand, .clock { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px; }
|
|
13
|
+
.brand { background: linear-gradient(145deg, color-mix(in srgb, var(--accent) 80%, #241025), #191426); text-align: center; }
|
|
14
|
+
.bolt { font-size: 59px; line-height: 1; }
|
|
15
|
+
.title { margin-top: 12px; font-size: 20px; font-weight: 950; letter-spacing: .12em; }
|
|
16
|
+
.content { display: flex; flex-direction: column; justify-content: center; padding: 28px 40px; }
|
|
17
|
+
.meta { margin-bottom: 10px; color: var(--accent); font-size: 19px; font-weight: 900; letter-spacing: .16em; }
|
|
18
|
+
.copy { font-size: 43px; line-height: 1.13; font-weight: 850; text-wrap: balance; }
|
|
19
|
+
.answer .copy { color: color-mix(in srgb, var(--accent) 70%, white); }
|
|
20
|
+
.clock { border-left: 2px solid #ffffff18; }
|
|
21
|
+
.seconds { font-variant-numeric: tabular-nums; font-size: 82px; line-height: .9; font-weight: 950; }
|
|
22
|
+
.unit { margin-top: 11px; font-size: 17px; font-weight: 850; letter-spacing: .15em; color: #ffffffa5; }
|
|
23
|
+
.phase { margin-top: 19px; padding: 7px 13px; border-radius: 999px; background: var(--accent); color: #170c12; font-size: 18px; font-weight: 950; letter-spacing: .1em; }
|
|
24
|
+
.seconds, .unit { transition: opacity .2s ease; }
|
|
25
|
+
.clock.answer .seconds, .clock.answer .unit { opacity: 0; }
|
|
26
|
+
.clock.answer .phase { margin-top: 0; font-size: 22px; }
|
|
27
|
+
</style>
|
|
28
|
+
</head>
|
|
29
|
+
<body>
|
|
30
|
+
<section class="round" id="round">
|
|
31
|
+
<aside class="brand"><div class="bolt">⚡</div><div class="title" id="title">LIGHTNING ROUND</div></aside>
|
|
32
|
+
<main class="content question" id="content"><div class="meta" id="meta">QUESTION 1 OF 3</div><div class="copy" id="copy">Capital of Japan?</div></main>
|
|
33
|
+
<aside class="clock"><div class="seconds" id="seconds">10</div><div class="unit">SECONDS</div><div class="phase" id="phase">QUESTION</div></aside>
|
|
34
|
+
</section>
|
|
35
|
+
<script>
|
|
36
|
+
const state = {
|
|
37
|
+
questions: [{question: "Capital of Japan?", answer: "Tokyo"}, {question: "Fastest land animal?", answer: "Cheetah"}, {question: "Square root of 144?", answer: "12"}],
|
|
38
|
+
index: 0,
|
|
39
|
+
phase: "question",
|
|
40
|
+
remaining: 10,
|
|
41
|
+
timing: {questionSeconds: 10, answerSeconds: 4, gapSeconds: 2},
|
|
42
|
+
status: "paused",
|
|
43
|
+
tick: performance.now()
|
|
44
|
+
};
|
|
45
|
+
const dom = {
|
|
46
|
+
body: document.body,
|
|
47
|
+
round: document.getElementById("round"),
|
|
48
|
+
title: document.getElementById("title"),
|
|
49
|
+
content: document.getElementById("content"),
|
|
50
|
+
meta: document.getElementById("meta"),
|
|
51
|
+
copy: document.getElementById("copy"),
|
|
52
|
+
clock: document.querySelector(".clock"),
|
|
53
|
+
seconds: document.getElementById("seconds"),
|
|
54
|
+
phase: document.getElementById("phase")
|
|
55
|
+
};
|
|
56
|
+
let lastVisualSignature;
|
|
57
|
+
const duration = (name) => Math.max(name === "gapSeconds" ? 0 : .25, Number(state.timing[name]) || 0);
|
|
58
|
+
|
|
59
|
+
function beginPhase(phase) {
|
|
60
|
+
state.phase = phase;
|
|
61
|
+
state.remaining = duration(`${phase}Seconds`);
|
|
62
|
+
}
|
|
63
|
+
function advance() {
|
|
64
|
+
if (state.phase === "question") beginPhase("answer");
|
|
65
|
+
else if (state.phase === "answer") beginPhase("gap");
|
|
66
|
+
else {
|
|
67
|
+
state.index = state.questions.length ? (state.index + 1) % state.questions.length : 0;
|
|
68
|
+
beginPhase("question");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function render() {
|
|
72
|
+
const count = state.questions.length;
|
|
73
|
+
state.index = Math.max(0, Math.min(count - 1, state.index));
|
|
74
|
+
const item = state.questions[state.index] || {question: "Ready?", answer: ""};
|
|
75
|
+
const answer = state.phase !== "question" ;
|
|
76
|
+
const displayedSeconds = state.phase === "question" ? Math.max(0, Math.ceil(state.remaining)) : null;
|
|
77
|
+
const visualSignature = JSON.stringify([state.phase, state.index, count, item.question, item.answer, displayedSeconds]);
|
|
78
|
+
if (visualSignature === lastVisualSignature) return;
|
|
79
|
+
lastVisualSignature = visualSignature;
|
|
80
|
+
|
|
81
|
+
dom.round.classList.toggle("hidden", state.phase === "gap" || count === 0);
|
|
82
|
+
dom.meta.textContent = `${answer ? "ANSWER" : "QUESTION"} ${count ? state.index + 1 : 0} OF ${count}`;
|
|
83
|
+
dom.copy.textContent = answer ? item.answer : item.question;
|
|
84
|
+
dom.content.className = `content ${answer ? "answer" : "question"}`;
|
|
85
|
+
dom.clock.classList.toggle("answer", answer);
|
|
86
|
+
if (displayedSeconds != null) dom.seconds.textContent = displayedSeconds;
|
|
87
|
+
dom.phase.textContent = answer ? "ANSWER" : "QUESTION";
|
|
88
|
+
|
|
89
|
+
window.invalidate?.();
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
function animate(now) {
|
|
93
|
+
const elapsed = (now - state.tick) / 1000; state.tick = now;
|
|
94
|
+
if (state.status === "running" && state.questions.length) {
|
|
95
|
+
state.remaining -= elapsed;
|
|
96
|
+
while (state.remaining <= 0) {
|
|
97
|
+
const overrun = -state.remaining;
|
|
98
|
+
advance();
|
|
99
|
+
state.remaining -= overrun;
|
|
100
|
+
}
|
|
101
|
+
render();
|
|
102
|
+
}
|
|
103
|
+
requestAnimationFrame(animate);
|
|
104
|
+
}
|
|
105
|
+
window.addEventListener("nebular-overlay:update", ({detail = {}}) => {
|
|
106
|
+
let questionsChanged = false;
|
|
107
|
+
let timingChanged = false;
|
|
108
|
+
if (Array.isArray(detail.questions)) {
|
|
109
|
+
state.questions = detail.questions;
|
|
110
|
+
state.index = 0;
|
|
111
|
+
questionsChanged = true;
|
|
112
|
+
}
|
|
113
|
+
if (detail.timing?.questionSeconds != null) { state.timing.questionSeconds = detail.timing.questionSeconds; timingChanged = true; }
|
|
114
|
+
if (detail.timing?.answerSeconds != null) { state.timing.answerSeconds = detail.timing.answerSeconds; timingChanged = true; }
|
|
115
|
+
if (detail.timing?.gapSeconds != null) { state.timing.gapSeconds = detail.timing.gapSeconds; timingChanged = true; }
|
|
116
|
+
if (detail.timer?.status != null) state.status = detail.timer.status;
|
|
117
|
+
if (questionsChanged) beginPhase("question");
|
|
118
|
+
else if (timingChanged && state.status === "paused") state.remaining = duration(`${state.phase}Seconds`);
|
|
119
|
+
if (detail.content?.title != null) dom.title.textContent = detail.content.title;
|
|
120
|
+
if (detail.style?.accent != null) dom.body.style.setProperty("--accent", detail.style.accent);
|
|
121
|
+
state.tick = performance.now(); render();
|
|
122
|
+
});
|
|
123
|
+
render(); requestAnimationFrame(animate);
|
|
124
|
+
</script>
|
|
125
|
+
</body>
|
|
126
|
+
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instructions": "Build the question-and-answer deck and configure the question, answer and hidden-gap durations. Set Playback to running and press Update to start the automatic loop; select paused to freeze it at its current phase.",
|
|
3
|
+
"meta": {
|
|
4
|
+
"content/title": {"type": "string", "phase": "edit", "label": "Round title", "hint": "Enter round title", "default": "LIGHTNING ROUND"},
|
|
5
|
+
"questions": {
|
|
6
|
+
"type": "set",
|
|
7
|
+
"phase": "edit",
|
|
8
|
+
"label": "Questions", "hint": "Ordered question-and-answer cards used by the automatic loop",
|
|
9
|
+
"max": 30,
|
|
10
|
+
"container": "big",
|
|
11
|
+
"default": [
|
|
12
|
+
{"question": "Capital of Japan?", "answer": "Tokyo"},
|
|
13
|
+
{"question": "Fastest land animal?", "answer": "Cheetah"},
|
|
14
|
+
{"question": "Square root of 144?", "answer": "12"}
|
|
15
|
+
],
|
|
16
|
+
"meta": {
|
|
17
|
+
"question": {"type": "string", "phase": "edit", "label": "Question", "hint": "Enter question"},
|
|
18
|
+
"answer": {"type": "string", "phase": "edit", "label": "Answer", "hint": "Enter answer"}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"timing/questionSeconds": {"type": "number", "phase": "edit", "label": "Question time", "hint": "Enter seconds", "min": 1, "max": 300, "default": 10},
|
|
22
|
+
"timing/answerSeconds": {"type": "number", "phase": "edit", "label": "Answer time", "hint": "Enter seconds", "min": 1, "max": 60, "default": 5},
|
|
23
|
+
"timing/gapSeconds": {"type": "number", "phase": "edit", "label": "Hidden gap", "hint": "Enter seconds", "min": 0, "max": 30, "default": 5},
|
|
24
|
+
"timer/status": {"type": "choose", "phase": "play", "label": "Playback", "hint": "Start or pause automatic question playback", "choose": ["running", "paused"], "default": "paused"},
|
|
25
|
+
"style/accent": {"type": "color", "phase": "edit", "label": "Accent", "hint": "Accent colour used by the round graphic", "default": "#ff4e71"}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="es">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<style>
|
|
7
|
+
* {
|
|
8
|
+
box-sizing: border-box
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
html, body {
|
|
12
|
+
width: 100%;
|
|
13
|
+
height: 100%;
|
|
14
|
+
margin: 0;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
background: transparent;
|
|
17
|
+
font-family: Inter, Arial, sans-serif
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
body {
|
|
21
|
+
color: #fff
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.game {
|
|
25
|
+
position: relative;
|
|
26
|
+
width: 100%;
|
|
27
|
+
height: 100%
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.letters {
|
|
31
|
+
position: absolute;
|
|
32
|
+
inset: 0;
|
|
33
|
+
aspect-ratio: 1 / 1;
|
|
34
|
+
height: 900px;
|
|
35
|
+
left: calc(50% - 450px);
|
|
36
|
+
top: calc(50% - 450px);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.letter {
|
|
40
|
+
position: absolute;
|
|
41
|
+
left: 50%;
|
|
42
|
+
top: 50%;
|
|
43
|
+
display: grid;
|
|
44
|
+
place-items: center;
|
|
45
|
+
width: 4.25rem;
|
|
46
|
+
height: 4.25rem;
|
|
47
|
+
border: .22rem solid #8cddff;
|
|
48
|
+
border-radius: 50%;
|
|
49
|
+
color: #fff;
|
|
50
|
+
background: linear-gradient(145deg, #198ee8, #0758b4);
|
|
51
|
+
box-shadow: 0 .55rem 1.2rem #0008, inset 0 .14rem .4rem #ffffff4a, 0 0 1rem #159cff55;
|
|
52
|
+
font-size: 1.75rem;
|
|
53
|
+
font-weight: 950;
|
|
54
|
+
transform: translate(-50%, -50%);
|
|
55
|
+
transition: background .3s ease, border-color .3s ease, box-shadow .3s ease, transform .3s ease
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.letter.correct {
|
|
59
|
+
border-color: #a9ffd1;
|
|
60
|
+
background: linear-gradient(145deg, #24cf75, #087b42);
|
|
61
|
+
box-shadow: 0 .55rem 1.2rem #0008, inset 0 .14rem .4rem #ffffff52, 0 0 1.4rem #20e77d99
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.letter.failed {
|
|
65
|
+
border-color: #ffc1c6;
|
|
66
|
+
background: linear-gradient(145deg, #f34657, #a80e23);
|
|
67
|
+
box-shadow: 0 .55rem 1.2rem #0008, inset 0 .14rem .4rem #ffffff45, 0 0 1.4rem #ff334d88
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.answers-label {
|
|
71
|
+
color: #8bcce9;
|
|
72
|
+
font-size: .86rem;
|
|
73
|
+
font-weight: 850;
|
|
74
|
+
letter-spacing: .32em
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.clock, .answers-holder {
|
|
78
|
+
--progress: 1;
|
|
79
|
+
position: absolute;
|
|
80
|
+
display: grid;
|
|
81
|
+
place-items: center;
|
|
82
|
+
width: 10rem;
|
|
83
|
+
height: 10rem;
|
|
84
|
+
margin: 1.35rem auto 1rem;
|
|
85
|
+
border-radius: 50%;
|
|
86
|
+
background: conic-gradient(white, calc(var(--progress) * 1turn), #132b45 0);
|
|
87
|
+
filter: drop-shadow(0 0 1.5rem #159cff55);
|
|
88
|
+
bottom: 0;
|
|
89
|
+
left: 2rem;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.clock::before {
|
|
93
|
+
content: "";
|
|
94
|
+
position: absolute;
|
|
95
|
+
inset: .8rem;
|
|
96
|
+
border-radius: inherit;
|
|
97
|
+
background: radial-gradient(circle at 50% 35%, orange, #ff4400 72%);
|
|
98
|
+
border: .1rem solid #a5e7ff55;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.seconds {
|
|
102
|
+
position: relative;
|
|
103
|
+
font-size: 5rem;
|
|
104
|
+
font-weight: 950;
|
|
105
|
+
font-variant-numeric: tabular-nums;
|
|
106
|
+
line-height: 1
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.clock-status {
|
|
110
|
+
min-height: 1.3rem;
|
|
111
|
+
color: #62d2ff;
|
|
112
|
+
font-size: .9rem;
|
|
113
|
+
font-weight: 850;
|
|
114
|
+
letter-spacing: .28em;
|
|
115
|
+
margin: 1rem;
|
|
116
|
+
position: absolute;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.divider {
|
|
120
|
+
height: .1rem;
|
|
121
|
+
margin: 2rem 0;
|
|
122
|
+
background: linear-gradient(90deg, transparent, #7cdcff66, transparent)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#answers {
|
|
126
|
+
background: #00aa00;
|
|
127
|
+
width: 5rem;
|
|
128
|
+
height: 5rem;
|
|
129
|
+
border-radius: 10000px;
|
|
130
|
+
line-height: 5rem;
|
|
131
|
+
text-align: center;
|
|
132
|
+
font-weight: bold;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.answers-holder {
|
|
136
|
+
left: 11rem;
|
|
137
|
+
width: 6rem;
|
|
138
|
+
height: 6rem;
|
|
139
|
+
color: white;
|
|
140
|
+
font-size: 2.5rem;
|
|
141
|
+
bottom: 1.5rem;
|
|
142
|
+
z-index: -1;
|
|
143
|
+
}
|
|
144
|
+
</style>
|
|
145
|
+
</head>
|
|
146
|
+
<body>
|
|
147
|
+
<main class="game">
|
|
148
|
+
<section class="halo">
|
|
149
|
+
<div class="letters" id="letters"></div>
|
|
150
|
+
</section>
|
|
151
|
+
|
|
152
|
+
<div class="clock" id="clock">
|
|
153
|
+
<div class="seconds" id="seconds">60</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div class="answers-holder">
|
|
157
|
+
<div class="answers" id="answers">0</div>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<div class="clock-status" id="clock-status">PAUSA</div>
|
|
161
|
+
|
|
162
|
+
</main>
|
|
163
|
+
|
|
164
|
+
<script>
|
|
165
|
+
const
|
|
166
|
+
LETTERS = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "Ñ", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"],
|
|
167
|
+
$ = id => document.getElementById(id),
|
|
168
|
+
cells = new Map();
|
|
169
|
+
|
|
170
|
+
let
|
|
171
|
+
correct = new Set(),
|
|
172
|
+
failed = new Set(),
|
|
173
|
+
remaining = Number($("seconds").textContent),
|
|
174
|
+
total = remaining,
|
|
175
|
+
running = false,
|
|
176
|
+
startedAt = 0,
|
|
177
|
+
startedWith = remaining,
|
|
178
|
+
animationGeneration = 0;
|
|
179
|
+
|
|
180
|
+
LETTERS.forEach((letter, index) => {
|
|
181
|
+
const
|
|
182
|
+
angle = (index / LETTERS.length) * Math.PI * 2 - Math.PI / 2,
|
|
183
|
+
cell = document.createElement("div");
|
|
184
|
+
|
|
185
|
+
cell.className = "letter";
|
|
186
|
+
cell.textContent = letter;
|
|
187
|
+
cell.style.left = `${50 + Math.cos(angle) * 43}%`;
|
|
188
|
+
cell.style.top = `${50 + Math.sin(angle) * 43}%`;
|
|
189
|
+
$("letters").append(cell);
|
|
190
|
+
cells.set(letter, cell)
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
function renderLetters() {
|
|
194
|
+
cells.forEach((cell, letter) => {
|
|
195
|
+
cell.classList.toggle("correct", correct.has(letter));
|
|
196
|
+
cell.classList.toggle("failed", !correct.has(letter) && failed.has(letter));
|
|
197
|
+
});
|
|
198
|
+
$("answers").textContent = correct.size
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function renderClock() {
|
|
202
|
+
$("seconds").textContent = Math.max(0, Math.ceil(remaining));
|
|
203
|
+
$("clock").style.setProperty("--progress", total > 0 ? Math.max(0, remaining / total) : 0);
|
|
204
|
+
$("clock-status").textContent = remaining <= 0 ? "FIN" : running ? "EN MARCHA" : "PAUSA"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function readElapsed(now = performance.now()) {
|
|
208
|
+
if (running) remaining = Math.max(0, startedWith - (now - startedAt) / 1000);
|
|
209
|
+
return remaining
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function animateClock(generation) {
|
|
213
|
+
if (!running || generation !== animationGeneration) return;
|
|
214
|
+
readElapsed();
|
|
215
|
+
if (remaining <= 0) running = false;
|
|
216
|
+
renderClock();
|
|
217
|
+
if (running) requestAnimationFrame(() => animateClock(generation))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function setSeconds(value) {
|
|
221
|
+
remaining = Math.max(0, Number(value) || 0);
|
|
222
|
+
total = Math.max(remaining, 1);
|
|
223
|
+
startedWith = remaining;
|
|
224
|
+
startedAt = performance.now();
|
|
225
|
+
renderClock()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function setClockStatus(status) {
|
|
229
|
+
const shouldRun = status === "running" && remaining > 0;
|
|
230
|
+
readElapsed();
|
|
231
|
+
running = shouldRun;
|
|
232
|
+
animationGeneration++;
|
|
233
|
+
if (running) {
|
|
234
|
+
startedWith = remaining;
|
|
235
|
+
startedAt = performance.now();
|
|
236
|
+
requestAnimationFrame(() => animateClock(animationGeneration))
|
|
237
|
+
} else renderClock()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
window.addEventListener("nebular-overlay:update", ({detail}) => {
|
|
241
|
+
if (Array.isArray(detail.game?.correct)) correct = new Set(detail.game.correct.map(String));
|
|
242
|
+
if (Array.isArray(detail.game?.failed)) failed = new Set(detail.game.failed.map(String));
|
|
243
|
+
if (Array.isArray(detail.game?.correct) || Array.isArray(detail.game?.failed)) renderLetters();
|
|
244
|
+
if (detail.timer?.seconds != null) setSeconds(detail.timer.seconds);
|
|
245
|
+
if (detail.timer?.status != null) setClockStatus(detail.timer.status)
|
|
246
|
+
});
|
|
247
|
+
</script>
|
|
248
|
+
</body>
|
|
249
|
+
</html>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instructions": "Set the available time, mark letters as correct or failed as answers are given, and use the clock control to run or pause the countdown. Press Update after each game-state change. Use Reset game to clear all answers, restore 60 seconds and pause the clock, then press Update.",
|
|
3
|
+
"meta": {
|
|
4
|
+
"timer/seconds": {
|
|
5
|
+
"type": "number",
|
|
6
|
+
"phase": "edit",
|
|
7
|
+
"label": "Time", "hint": "Enter seconds",
|
|
8
|
+
"container": "small",
|
|
9
|
+
"min": 0,
|
|
10
|
+
"max": 600,
|
|
11
|
+
"default": 60
|
|
12
|
+
},
|
|
13
|
+
"timer/status": {
|
|
14
|
+
"type": "choose",
|
|
15
|
+
"phase": "play",
|
|
16
|
+
"label": "Clock", "hint": "Start or pause the game clock",
|
|
17
|
+
"choose": [
|
|
18
|
+
"running",
|
|
19
|
+
"paused"
|
|
20
|
+
],
|
|
21
|
+
"default": "paused"
|
|
22
|
+
},
|
|
23
|
+
"game/correct": {
|
|
24
|
+
"type": "multi",
|
|
25
|
+
"phase": "play",
|
|
26
|
+
"container": "big",
|
|
27
|
+
"label": "Correct answers", "hint": "Letters answered correctly",
|
|
28
|
+
"choose": [
|
|
29
|
+
"A", "B", "C", "D", "E", "F", "G", "H", "I",
|
|
30
|
+
"J", "K", "L", "M", "N", "Ñ", "O", "P", "Q",
|
|
31
|
+
"R", "S", "T", "U", "V", "W", "X", "Y", "Z"
|
|
32
|
+
],
|
|
33
|
+
"cols": 9,
|
|
34
|
+
"rows": 3,
|
|
35
|
+
"responsive": true,
|
|
36
|
+
"minCellWidth": "2.54rem",
|
|
37
|
+
"default": []
|
|
38
|
+
},
|
|
39
|
+
"game/failed": {
|
|
40
|
+
"type": "multi",
|
|
41
|
+
"phase": "play",
|
|
42
|
+
"container": "big",
|
|
43
|
+
"label": "Failed answers", "hint": "Letters answered incorrectly",
|
|
44
|
+
"choose": [
|
|
45
|
+
"A", "B", "C", "D", "E", "F", "G", "H", "I",
|
|
46
|
+
"J", "K", "L", "M", "N", "Ñ", "O", "P", "Q",
|
|
47
|
+
"R", "S", "T", "U", "V", "W", "X", "Y", "Z"
|
|
48
|
+
],
|
|
49
|
+
"cols": 9,
|
|
50
|
+
"rows": 3,
|
|
51
|
+
"responsive": true,
|
|
52
|
+
"minCellWidth": "2.54rem",
|
|
53
|
+
"default": []
|
|
54
|
+
},
|
|
55
|
+
"game/reset": {
|
|
56
|
+
"type": "reset",
|
|
57
|
+
"phase": "play",
|
|
58
|
+
"icon": "undo",
|
|
59
|
+
"text": "Reset game", "hint": "Clear all answers and return the clock to its initial state",
|
|
60
|
+
"values": {
|
|
61
|
+
"timer/seconds": 60,
|
|
62
|
+
"timer/status": "paused",
|
|
63
|
+
"game/correct": [],
|
|
64
|
+
"game/failed": []
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Quiz Deck</title>
|
|
6
|
+
<style>
|
|
7
|
+
* { box-sizing: border-box; }
|
|
8
|
+
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: transparent; }
|
|
9
|
+
body { --accent: #7c5cff; color: white; font-family: Inter, Arial, sans-serif; }
|
|
10
|
+
.deck { position: absolute; left: 5%; right: 5%; bottom: 5%; display: grid; grid-template-columns: 230px 1fr; min-height: 235px; border-radius: 25px; overflow: hidden; background: rgba(12,13,25,.96); border: 2px solid #ffffff22; box-shadow: 0 22px 65px #000a; }
|
|
11
|
+
.rail { display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 20px; background: linear-gradient(155deg, var(--accent), color-mix(in srgb, var(--accent) 45%, #080918)); text-align: center; }
|
|
12
|
+
.count { font-size: 67px; line-height: 1; font-weight: 950; }
|
|
13
|
+
.total { margin-top: 8px; font-size: 18px; font-weight: 800; letter-spacing: .12em; opacity: .82; }
|
|
14
|
+
.title { margin-top: 20px; font-size: 18px; font-weight: 950; letter-spacing: .1em; }
|
|
15
|
+
.card { display: flex; flex-direction: column; justify-content: center; padding: 28px 42px; }
|
|
16
|
+
.eyebrow { margin-bottom: 12px; color: var(--accent); font-size: 20px; font-weight: 900; letter-spacing: .18em; }
|
|
17
|
+
.copy { font-size: 44px; line-height: 1.14; font-weight: 850; text-wrap: balance; transition: opacity .18s ease, transform .18s ease; }
|
|
18
|
+
.answer .eyebrow::after { content: "ANSWER"; }
|
|
19
|
+
.question .eyebrow::after { content: "QUESTION"; }
|
|
20
|
+
.answer .copy { color: color-mix(in srgb, var(--accent) 70%, white); }
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
<section class="deck" id="deck">
|
|
25
|
+
<aside class="rail"><div class="count" id="current">1</div><div class="total" id="total">OF 3</div><div class="title" id="title">THE BIG QUIZ</div></aside>
|
|
26
|
+
<main class="card question" id="card"><div class="eyebrow"></div><div class="copy" id="copy">What is the largest ocean on Earth?</div></main>
|
|
27
|
+
</section>
|
|
28
|
+
<script>
|
|
29
|
+
const state = {
|
|
30
|
+
questions: [
|
|
31
|
+
{question: "What is the largest ocean on Earth?", answer: "The Pacific Ocean"},
|
|
32
|
+
{question: "How many sides does a dodecagon have?", answer: "12"},
|
|
33
|
+
{question: "Which element has the symbol Au?", answer: "Gold"}
|
|
34
|
+
],
|
|
35
|
+
current: 1, display: "question"
|
|
36
|
+
};
|
|
37
|
+
function render() {
|
|
38
|
+
const count = state.questions.length;
|
|
39
|
+
const index = Math.max(0, Math.min(count - 1, Number(state.current || 1) - 1));
|
|
40
|
+
const item = state.questions[index] || {question: "Ready for the next question?", answer: ""};
|
|
41
|
+
const answer = state.display === "answer";
|
|
42
|
+
document.getElementById("current").textContent = count ? index + 1 : 0;
|
|
43
|
+
document.getElementById("total").textContent = `OF ${count}`;
|
|
44
|
+
document.getElementById("copy").textContent = answer ? item.answer : item.question;
|
|
45
|
+
document.getElementById("card").className = `card ${answer ? "answer" : "question"}`;
|
|
46
|
+
}
|
|
47
|
+
window.addEventListener("nebular-overlay:update", ({detail = {}}) => {
|
|
48
|
+
if (Array.isArray(detail.questions)) state.questions = detail.questions;
|
|
49
|
+
if (detail.control?.current != null) state.current = detail.control.current;
|
|
50
|
+
if (detail.control?.display != null) state.display = detail.control.display;
|
|
51
|
+
if (detail.content?.title != null) document.getElementById("title").textContent = detail.content.title;
|
|
52
|
+
if (detail.style?.accent != null) document.body.style.setProperty("--accent", detail.style.accent);
|
|
53
|
+
render();
|
|
54
|
+
});
|
|
55
|
+
render();
|
|
56
|
+
</script>
|
|
57
|
+
</body>
|
|
58
|
+
</html>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instructions": "Build the question-and-answer deck, select the current question number and publish it in question mode. Switch Display to answer and press Update when the answer should be revealed.",
|
|
3
|
+
"meta": {
|
|
4
|
+
"content/title": {"type": "string", "phase": "edit", "label": "Quiz title", "hint": "Enter quiz title", "default": "THE BIG QUIZ"},
|
|
5
|
+
"questions": {
|
|
6
|
+
"type": "set",
|
|
7
|
+
"phase": "edit",
|
|
8
|
+
"label": "Question deck", "hint": "Ordered collection of questions and possible answers",
|
|
9
|
+
"max": 20,
|
|
10
|
+
"container": "big",
|
|
11
|
+
"default": [
|
|
12
|
+
{"question": "What is the largest ocean on Earth?", "answer": "The Pacific Ocean"},
|
|
13
|
+
{"question": "How many sides does a dodecagon have?", "answer": "12"},
|
|
14
|
+
{"question": "Which element has the symbol Au?", "answer": "Gold"}
|
|
15
|
+
],
|
|
16
|
+
"meta": {
|
|
17
|
+
"question": {"type": "string", "phase": "edit", "label": "Question", "hint": "Enter question"},
|
|
18
|
+
"answer": {"type": "string", "phase": "edit", "label": "Answer", "hint": "Enter answer"}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"control/current": {"label": "Current question", "phase": "play", "type": "selector", "set": "questions"},
|
|
22
|
+
"control/display": {"type": "choose", "phase": "play", "label": "Display", "hint": "Choose which part of the current card viewers see", "choose": ["question", "answer"], "default": "question"},
|
|
23
|
+
"style/accent": {"type": "color", "phase": "edit", "label": "Accent", "hint": "Accent colour used by the quiz graphic", "default": "#7c5cff"}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>True or False</title>
|
|
6
|
+
<style>
|
|
7
|
+
* { box-sizing: border-box; }
|
|
8
|
+
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: transparent; }
|
|
9
|
+
body { --accent: #19d3ae; color: #fff; font-family: Inter, Arial, sans-serif; }
|
|
10
|
+
.panel { position: absolute; left: 5%; right: 5%; bottom: 5%; padding: 25px 30px 28px; border-radius: 26px; background: rgba(7,17,22,.96); border: 2px solid #ffffff24; box-shadow: 0 24px 64px #000a; transition: opacity .35s ease, transform .35s ease; }
|
|
11
|
+
.panel.hidden { opacity: 0; transform: translateY(35px); }
|
|
12
|
+
.header { display: flex; align-items: center; gap: 18px; margin-bottom: 16px; }
|
|
13
|
+
.category { padding: 9px 16px; border-radius: 999px; color: #071613; background: var(--accent); font-size: 20px; font-weight: 950; letter-spacing: .12em; }
|
|
14
|
+
.statement { font-size: 39px; line-height: 1.14; font-weight: 850; text-wrap: balance; }
|
|
15
|
+
.choices { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
16
|
+
.choice { display: grid; place-items: center; height: 82px; border: 2px solid #ffffff22; border-radius: 17px; background: #ffffff0a; font-size: 34px; font-weight: 950; letter-spacing: .08em; transition: .35s ease; }
|
|
17
|
+
.revealed .choice:not(.correct) { opacity: .25; }
|
|
18
|
+
.revealed .choice.correct { color: #071613; background: var(--accent); border-color: white; box-shadow: 0 0 32px color-mix(in srgb, var(--accent) 50%, transparent); }
|
|
19
|
+
.explanation { display: none; margin-top: 15px; color: #d9fff7; font-size: 25px; font-weight: 650; }
|
|
20
|
+
.revealed .explanation { display: block; }
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
<section class="panel" id="panel">
|
|
25
|
+
<div class="header"><span class="category" id="category">SCIENCE</span><div class="statement" id="statement">Lightning never strikes the same place twice.</div></div>
|
|
26
|
+
<div class="choices"><div class="choice" data-choice="true">TRUE</div><div class="choice" data-choice="false">FALSE</div></div>
|
|
27
|
+
<div class="explanation" id="explanation">Lightning can strike the same place repeatedly.</div>
|
|
28
|
+
</section>
|
|
29
|
+
<script>
|
|
30
|
+
const state = {
|
|
31
|
+
questions: [
|
|
32
|
+
{category: "SCIENCE", statement: "Lightning never strikes the same place twice.", correct: "false", explanation: "Lightning can strike the same place repeatedly."},
|
|
33
|
+
{category: "GEOGRAPHY", statement: "The Great Wall of China is visible from the Moon with the naked eye.", correct: "false", explanation: "It is far too narrow to be distinguished from the Moon."},
|
|
34
|
+
{category: "NATURE", statement: "Octopuses have three hearts.", correct: "true", explanation: "Two pump blood to the gills and one pumps it around the body."}
|
|
35
|
+
],
|
|
36
|
+
current: 1,
|
|
37
|
+
timing: {questionSeconds: 8, answerSeconds: 4},
|
|
38
|
+
timers: []
|
|
39
|
+
};
|
|
40
|
+
const dom = {
|
|
41
|
+
body: document.body,
|
|
42
|
+
panel: document.getElementById("panel"),
|
|
43
|
+
category: document.getElementById("category"),
|
|
44
|
+
statement: document.getElementById("statement"),
|
|
45
|
+
explanation: document.getElementById("explanation"),
|
|
46
|
+
choices: [...document.querySelectorAll("[data-choice]")]
|
|
47
|
+
};
|
|
48
|
+
const seconds = (value, fallback) => Math.max(.25, Number(value) || fallback);
|
|
49
|
+
function clearSequence() {
|
|
50
|
+
state.timers.forEach(clearTimeout);
|
|
51
|
+
state.timers = [];
|
|
52
|
+
}
|
|
53
|
+
function startCurrentQuestion() {
|
|
54
|
+
clearSequence();
|
|
55
|
+
const index = Math.max(0, Math.min(state.questions.length - 1, Number(state.current || 1) - 1));
|
|
56
|
+
const question = state.questions[index];
|
|
57
|
+
if (!question) { dom.panel.classList.add("hidden"); return; }
|
|
58
|
+
|
|
59
|
+
dom.category.textContent = question.category ?? "TRUE OR FALSE";
|
|
60
|
+
dom.statement.textContent = question.statement ?? "";
|
|
61
|
+
dom.explanation.textContent = question.explanation ?? "";
|
|
62
|
+
dom.choices.forEach(el => el.classList.toggle("correct", el.dataset.choice === question.correct));
|
|
63
|
+
dom.panel.classList.remove("revealed", "hidden");
|
|
64
|
+
|
|
65
|
+
state.timers.push(setTimeout(() => {
|
|
66
|
+
dom.panel.classList.add("revealed");
|
|
67
|
+
state.timers.push(setTimeout(() => dom.panel.classList.add("hidden"), seconds(state.timing.answerSeconds, 4) * 1000));
|
|
68
|
+
}, seconds(state.timing.questionSeconds, 8) * 1000));
|
|
69
|
+
}
|
|
70
|
+
window.addEventListener("nebular-overlay:update", ({detail = {}}) => {
|
|
71
|
+
let shouldStart = false;
|
|
72
|
+
if (Array.isArray(detail.questions)) { state.questions = detail.questions; shouldStart = true; }
|
|
73
|
+
if (detail.control?.current != null) { state.current = detail.control.current; shouldStart = true; }
|
|
74
|
+
if (detail.timing?.questionSeconds != null) state.timing.questionSeconds = detail.timing.questionSeconds;
|
|
75
|
+
if (detail.timing?.answerSeconds != null) state.timing.answerSeconds = detail.timing.answerSeconds;
|
|
76
|
+
if (detail.style?.accent != null) dom.body.style.setProperty("--accent", detail.style.accent);
|
|
77
|
+
if (shouldStart) startCurrentQuestion();
|
|
78
|
+
});
|
|
79
|
+
</script>
|
|
80
|
+
</body>
|
|
81
|
+
</html>
|