@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,120 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Tennis Scoreboard</title>
|
|
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
|
+
}
|
|
18
|
+
|
|
19
|
+
body {
|
|
20
|
+
color: white;
|
|
21
|
+
font-family: Inter, system-ui, sans-serif;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.board {
|
|
25
|
+
position: absolute;
|
|
26
|
+
left: 4%;
|
|
27
|
+
bottom: 7%;
|
|
28
|
+
width: 34rem;
|
|
29
|
+
border-radius: .7rem;
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
box-shadow: 0 1rem 3rem #0007;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.heading {
|
|
35
|
+
display: flex;
|
|
36
|
+
justify-content: space-between;
|
|
37
|
+
padding: .55rem 1rem;
|
|
38
|
+
background: #b7d62b;
|
|
39
|
+
color: #17200c;
|
|
40
|
+
font-size: .75rem;
|
|
41
|
+
font-weight: 900;
|
|
42
|
+
letter-spacing: .12em;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.player {
|
|
46
|
+
display: grid;
|
|
47
|
+
grid-template-columns: 1fr repeat(3, 3.6rem);
|
|
48
|
+
align-items: center;
|
|
49
|
+
min-height: 3.6rem;
|
|
50
|
+
background: rgba(15, 23, 31, .95);
|
|
51
|
+
border-top: 1px solid rgba(255, 255, 255, .13);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.name {
|
|
55
|
+
padding: 0 1rem;
|
|
56
|
+
font-weight: 800;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.cell {
|
|
60
|
+
height: 100%;
|
|
61
|
+
display: grid;
|
|
62
|
+
place-items: center;
|
|
63
|
+
border-left: 1px solid rgba(255, 255, 255, .13);
|
|
64
|
+
font-size: 1.25rem;
|
|
65
|
+
font-weight: 900;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.points {
|
|
69
|
+
background: #fff;
|
|
70
|
+
color: #14200d;
|
|
71
|
+
font-size: 1.55rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.serving::before {
|
|
75
|
+
content: "●";
|
|
76
|
+
color: #b7d62b;
|
|
77
|
+
margin-right: .55rem;
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
80
|
+
</head>
|
|
81
|
+
<body>
|
|
82
|
+
|
|
83
|
+
<section class="board">
|
|
84
|
+
<header class="heading"><span id="event">CENTRE COURT</span><span>SETS · GAMES · POINTS</span></header>
|
|
85
|
+
<div class="player">
|
|
86
|
+
<div class="name serving" id="playerOne">PLAYER ONE</div>
|
|
87
|
+
<div class="cell" id="playerOneSets">0</div>
|
|
88
|
+
<div class="cell" id="playerOneGames">0</div>
|
|
89
|
+
<div class="cell points" id="playerOnePoints">0</div>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="player">
|
|
92
|
+
<div class="name" id="playerTwo">PLAYER TWO</div>
|
|
93
|
+
<div class="cell" id="playerTwoSets">0</div>
|
|
94
|
+
<div class="cell" id="playerTwoGames">0</div>
|
|
95
|
+
<div class="cell points" id="playerTwoPoints">0</div>
|
|
96
|
+
</div>
|
|
97
|
+
</section>
|
|
98
|
+
<script>
|
|
99
|
+
|
|
100
|
+
const $ = (name, value) => {
|
|
101
|
+
const element = document.getElementById(name);
|
|
102
|
+
if (element) element.textContent = value;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
window.addEventListener("nebular-overlay:update", ({detail}) => {
|
|
106
|
+
if (detail.event != null) $("event", detail.event);
|
|
107
|
+
if (detail.playerOne?.name != null) $("playerOne", detail.playerOne.name);
|
|
108
|
+
if (detail.playerOne?.sets != null) $("playerOneSets", detail.playerOne.sets);
|
|
109
|
+
if (detail.playerOne?.games != null) $("playerOneGames", detail.playerOne.games);
|
|
110
|
+
if (detail.playerOne?.points != null) $("playerOnePoints", detail.playerOne.points);
|
|
111
|
+
if (detail.playerTwo?.name != null) $("playerTwo", detail.playerTwo.name);
|
|
112
|
+
if (detail.playerTwo?.sets != null) $("playerTwoSets", detail.playerTwo.sets);
|
|
113
|
+
if (detail.playerTwo?.games != null) $("playerTwoGames", detail.playerTwo.games);
|
|
114
|
+
if (detail.playerTwo?.points != null) $("playerTwoPoints", detail.playerTwo.points);
|
|
115
|
+
window.invalidate?.()
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
</script>
|
|
119
|
+
</body>
|
|
120
|
+
</html>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instructions": "Enter the event and player names, then update sets, games and points as play progresses. Use the point selectors for 0, 15, 30, 40 and advantage, and press Update after each scoring change.",
|
|
3
|
+
"meta": {
|
|
4
|
+
"event": {
|
|
5
|
+
"type": "string",
|
|
6
|
+
"phase": "edit",
|
|
7
|
+
"label": "Court or event", "hint": "Enter court or event",
|
|
8
|
+
"default": "CENTRE COURT"
|
|
9
|
+
},
|
|
10
|
+
"playerOne/name": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"phase": "edit",
|
|
13
|
+
"label": "Player one", "hint": "Enter player name",
|
|
14
|
+
"default": "PLAYER ONE"
|
|
15
|
+
},
|
|
16
|
+
"playerTwo/name": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"phase": "edit",
|
|
19
|
+
"label": "Player two", "hint": "Enter player name",
|
|
20
|
+
"default": "PLAYER TWO"
|
|
21
|
+
},
|
|
22
|
+
"playerOne/playName": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"phase": "play",
|
|
25
|
+
"label": "Player one", "hint": "First player's name displayed during play",
|
|
26
|
+
"source": "playerOne/name",
|
|
27
|
+
"readonly": true
|
|
28
|
+
},
|
|
29
|
+
"playerOne/sets": {
|
|
30
|
+
"type": "number",
|
|
31
|
+
"phase": "play",
|
|
32
|
+
"label": "Player one sets", "hint": "Enter sets",
|
|
33
|
+
"min": 0,
|
|
34
|
+
"default": 0
|
|
35
|
+
},
|
|
36
|
+
"playerOne/games": {
|
|
37
|
+
"type": "number",
|
|
38
|
+
"phase": "play",
|
|
39
|
+
"label": "Player one games", "hint": "Enter games",
|
|
40
|
+
"min": 0,
|
|
41
|
+
"default": 0
|
|
42
|
+
},
|
|
43
|
+
"playerOne/points": {
|
|
44
|
+
"type": "choose",
|
|
45
|
+
"phase": "play",
|
|
46
|
+
"label": "Player one points", "hint": "Current-game points for the first player",
|
|
47
|
+
"break": true,
|
|
48
|
+
"default": "0",
|
|
49
|
+
"choose": [
|
|
50
|
+
"0",
|
|
51
|
+
"15",
|
|
52
|
+
"30",
|
|
53
|
+
"40",
|
|
54
|
+
"A"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"playerTwo/playName": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"phase": "play",
|
|
60
|
+
"label": "Player two", "hint": "Second player's name displayed during play",
|
|
61
|
+
"source": "playerTwo/name",
|
|
62
|
+
"readonly": true
|
|
63
|
+
},
|
|
64
|
+
"playerTwo/sets": {
|
|
65
|
+
"type": "number",
|
|
66
|
+
"phase": "play",
|
|
67
|
+
"label": "Player two sets", "hint": "Enter sets",
|
|
68
|
+
"min": 0,
|
|
69
|
+
"default": 0
|
|
70
|
+
},
|
|
71
|
+
"playerTwo/games": {
|
|
72
|
+
"type": "number",
|
|
73
|
+
"phase": "play",
|
|
74
|
+
"label": "Player two games", "hint": "Enter games",
|
|
75
|
+
"min": 0,
|
|
76
|
+
"default": 0
|
|
77
|
+
},
|
|
78
|
+
"playerTwo/points": {
|
|
79
|
+
"type": "choose",
|
|
80
|
+
"phase": "play",
|
|
81
|
+
"label": "Player two points", "hint": "Current-game points for the second player",
|
|
82
|
+
"default": "0",
|
|
83
|
+
"choose": [
|
|
84
|
+
"0",
|
|
85
|
+
"15",
|
|
86
|
+
"30",
|
|
87
|
+
"40",
|
|
88
|
+
"A"
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Audience Poll</title>
|
|
6
|
+
<style>
|
|
7
|
+
* { box-sizing: border-box; }
|
|
8
|
+
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: transparent; }
|
|
9
|
+
body { --accent: #36d1ff; color: white; font-family: Inter, Arial, sans-serif; }
|
|
10
|
+
.poll { position: absolute; left: 5%; right: 5%; bottom: 5%; padding: 24px 30px 28px; border-radius: 26px; background: rgba(7,17,27,.96); border: 2px solid #ffffff24; box-shadow: 0 24px 70px #000b; }
|
|
11
|
+
.heading { display: flex; align-items: center; gap: 20px; margin-bottom: 18px; }
|
|
12
|
+
.eyebrow { flex: none; padding: 9px 16px; border-radius: 999px; background: var(--accent); color: #06151d; font-size: 19px; font-weight: 950; letter-spacing: .13em; }
|
|
13
|
+
.question { font-size: 38px; line-height: 1.12; font-weight: 850; text-wrap: balance; }
|
|
14
|
+
.options { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 18px; }
|
|
15
|
+
.option { position: relative; min-height: 65px; overflow: hidden; border-radius: 14px; background: #ffffff0c; border: 2px solid #ffffff18; }
|
|
16
|
+
.bar { position: absolute; inset: 0 auto 0 0; width: 0; background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 70%, #132e44), color-mix(in srgb, var(--accent) 30%, transparent)); transition: width .7s cubic-bezier(.2,.8,.2,1); }
|
|
17
|
+
.row { position: relative; z-index: 1; display: flex; align-items: center; justify-content: space-between; min-height: 61px; padding: 8px 17px; font-size: 26px; font-weight: 800; }
|
|
18
|
+
.percent { display: none; margin-left: 15px; color: white; font-size: 30px; font-weight: 950; font-variant-numeric: tabular-nums; }
|
|
19
|
+
.results .percent { display: block; }
|
|
20
|
+
.question-only .bar { width: 0 !important; }
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
<section class="poll question-only" id="poll">
|
|
25
|
+
<div class="heading"><div class="eyebrow" id="eyebrow">AUDIENCE POLL</div><div class="question" id="question">Which destination should we visit next?</div></div>
|
|
26
|
+
<div class="options" id="options"></div>
|
|
27
|
+
</section>
|
|
28
|
+
<script>
|
|
29
|
+
const state = {
|
|
30
|
+
options: [{label: "Tokyo", votes: 420}, {label: "New York", votes: 310}, {label: "Barcelona", votes: 180}, {label: "Cape Town", votes: 90}],
|
|
31
|
+
display: "question"
|
|
32
|
+
};
|
|
33
|
+
function render() {
|
|
34
|
+
const container = document.getElementById("options");
|
|
35
|
+
const visibleOptions = state.options.slice(0, 4);
|
|
36
|
+
const totalVotes = visibleOptions.reduce((total, option) => total + Math.max(0, Number(option.votes) || 0), 0);
|
|
37
|
+
container.replaceChildren(...visibleOptions.map((option) => {
|
|
38
|
+
const item = document.createElement("div"); item.className = "option";
|
|
39
|
+
const votes = Math.max(0, Number(option.votes) || 0);
|
|
40
|
+
const percent = totalVotes > 0 ? Math.round(votes * 100 / totalVotes) : 0;
|
|
41
|
+
item.innerHTML = `<div class="bar" style="width:${percent}%"></div><div class="row"><span></span><strong class="percent">${percent}%</strong></div>`;
|
|
42
|
+
item.querySelector("span").textContent = option.label ?? "Option";
|
|
43
|
+
return item;
|
|
44
|
+
}));
|
|
45
|
+
const results = state.display === "results";
|
|
46
|
+
document.getElementById("poll").classList.toggle("results", results);
|
|
47
|
+
document.getElementById("poll").classList.toggle("question-only", !results);
|
|
48
|
+
}
|
|
49
|
+
window.addEventListener("nebular-overlay:update", ({detail = {}}) => {
|
|
50
|
+
if (Array.isArray(detail.options)) state.options = detail.options;
|
|
51
|
+
if (detail.control?.display != null) state.display = detail.control.display;
|
|
52
|
+
if (detail.content?.eyebrow != null) document.getElementById("eyebrow").textContent = detail.content.eyebrow;
|
|
53
|
+
if (detail.content?.question != null) document.getElementById("question").textContent = detail.content.question;
|
|
54
|
+
if (detail.style?.accent != null) document.body.style.setProperty("--accent", detail.style.accent);
|
|
55
|
+
render();
|
|
56
|
+
});
|
|
57
|
+
render();
|
|
58
|
+
</script>
|
|
59
|
+
</body>
|
|
60
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instructions": "Build up to four poll options and enter their raw vote counts; percentages are calculated automatically. Keep Display on question while voting, then select results and press Update to reveal the animated outcome.",
|
|
3
|
+
"meta": {
|
|
4
|
+
"content/eyebrow": {"type": "string", "phase": "edit", "label": "Label", "hint": "Enter short label", "default": "AUDIENCE POLL"},
|
|
5
|
+
"content/question": {"type": "string", "phase": "edit", "label": "Question", "hint": "Enter question", "container": "big", "default": "Which destination should we visit next?"},
|
|
6
|
+
"options": {
|
|
7
|
+
"type": "set",
|
|
8
|
+
"label": "Poll options", "hint": "Answer choices and their current raw vote counts",
|
|
9
|
+
"max": 4,
|
|
10
|
+
"container": "big",
|
|
11
|
+
"default": [
|
|
12
|
+
{"label": "Tokyo", "votes": 420},
|
|
13
|
+
{"label": "New York", "votes": 310},
|
|
14
|
+
{"label": "Barcelona", "votes": 180},
|
|
15
|
+
{"label": "Cape Town", "votes": 90}
|
|
16
|
+
],
|
|
17
|
+
"meta": {
|
|
18
|
+
"label": {"type": "string", "phase": "edit", "label": "Option", "hint": "Enter option text"},
|
|
19
|
+
"staticlabel": {"type": "string", "phase": "play", "label": "Option", "hint": "Answer text displayed while voting", "source": "label", "readonly": true},
|
|
20
|
+
"votes": {"type": "number", "phase": "play", "label": "Votes", "hint": "Enter votes", "min": 0, "default": 0}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"control/display": {"type": "choose", "phase": "play", "label": "Display", "hint": "Choose whether viewers see the question or results", "choose": ["question", "results"], "default": "question"},
|
|
24
|
+
"style/accent": {"type": "color", "phase": "edit", "label": "Accent", "hint": "Accent colour used by the poll graphic", "default": "#36d1ff"}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Classic Quiz</title>
|
|
6
|
+
<style>
|
|
7
|
+
* { box-sizing: border-box; }
|
|
8
|
+
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: transparent; }
|
|
9
|
+
body { --accent: #ffb21c; color: #fff; font-family: Inter, Arial, sans-serif; }
|
|
10
|
+
.quiz { position: absolute; left: 4%; right: 4%; bottom: 4%; padding: 24px 28px 26px; border-radius: 24px; background: linear-gradient(135deg, rgba(13,16,29,.97), rgba(25,29,48,.95)); border: 2px solid #ffffff24; box-shadow: 0 20px 60px #0009; }
|
|
11
|
+
.top { display: flex; align-items: center; gap: 22px; margin-bottom: 18px; }
|
|
12
|
+
.badge { flex: none; padding: 10px 18px; border-radius: 999px; background: var(--accent); color: #17120a; font-size: 21px; font-weight: 900; letter-spacing: .12em; }
|
|
13
|
+
.question { font-size: 39px; line-height: 1.12; font-weight: 850; text-wrap: balance; }
|
|
14
|
+
.answers { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
15
|
+
.answer { display: flex; align-items: center; min-height: 68px; padding: 10px 18px; border: 2px solid #ffffff20; border-radius: 14px; background: #ffffff0b; font-size: 27px; font-weight: 750; transition: .35s ease; }
|
|
16
|
+
.letter { display: grid; place-items: center; width: 42px; height: 42px; margin-right: 14px; border-radius: 50%; background: #fff; color: #151827; font-size: 22px; font-weight: 950; }
|
|
17
|
+
.revealed .answer:not(.correct) { opacity: .32; }
|
|
18
|
+
.revealed .answer.correct { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 24%, #151827); transform: translateY(-2px); box-shadow: 0 0 28px color-mix(in srgb, var(--accent) 45%, transparent); }
|
|
19
|
+
</style>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<section class="quiz" id="quiz">
|
|
23
|
+
<div class="top"><div class="badge" id="category">GENERAL KNOWLEDGE</div><div class="question" id="question">Which planet is known as the Red Planet?</div></div>
|
|
24
|
+
<div class="answers">
|
|
25
|
+
<div class="answer" data-answer="a"><span class="letter">A</span><span id="a">Venus</span></div>
|
|
26
|
+
<div class="answer" data-answer="b"><span class="letter">B</span><span id="b">Mars</span></div>
|
|
27
|
+
<div class="answer" data-answer="c"><span class="letter">C</span><span id="c">Jupiter</span></div>
|
|
28
|
+
<div class="answer" data-answer="d"><span class="letter">D</span><span id="d">Mercury</span></div>
|
|
29
|
+
</div>
|
|
30
|
+
</section>
|
|
31
|
+
<script>
|
|
32
|
+
const state = {correct: "b", display: "question"};
|
|
33
|
+
const text = (id, value) => { if (value != null) document.getElementById(id).textContent = value; };
|
|
34
|
+
function render() {
|
|
35
|
+
document.getElementById("quiz").classList.toggle("revealed", state.display === "answer");
|
|
36
|
+
document.querySelectorAll("[data-answer]").forEach(el => el.classList.toggle("correct", el.dataset.answer === state.correct));
|
|
37
|
+
}
|
|
38
|
+
window.addEventListener("nebular-overlay:update", ({detail = {}}) => {
|
|
39
|
+
text("category", detail.content?.category); text("question", detail.content?.question);
|
|
40
|
+
["a", "b", "c", "d"].forEach(key => text(key, detail.answers?.[key]));
|
|
41
|
+
if (detail.control?.correct != null) state.correct = detail.control.correct;
|
|
42
|
+
if (detail.control?.display != null) state.display = detail.control.display;
|
|
43
|
+
if (detail.style?.accent != null) document.body.style.setProperty("--accent", detail.style.accent);
|
|
44
|
+
render();
|
|
45
|
+
});
|
|
46
|
+
render();
|
|
47
|
+
</script>
|
|
48
|
+
</body>
|
|
49
|
+
</html>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instructions": "Enter the category, question and four possible answers, then select the correct answer. Publish in question mode first; switch Display to answer and press Update when it is time to reveal the result.",
|
|
3
|
+
"meta": {
|
|
4
|
+
"content/category": {"type": "string", "phase": "edit", "label": "Category", "hint": "Enter category", "default": "GENERAL KNOWLEDGE"},
|
|
5
|
+
"content/question": {"type": "string", "phase": "edit", "label": "Question", "hint": "Enter question", "container": "big", "default": "Which planet is known as the Red Planet?"},
|
|
6
|
+
"answers/a": {"type": "string", "phase": "edit", "label": "Answer A", "hint": "Enter answer A", "default": "Venus"},
|
|
7
|
+
"answers/b": {"type": "string", "phase": "edit", "label": "Answer B", "hint": "Enter answer B", "default": "Mars"},
|
|
8
|
+
"answers/c": {"type": "string", "phase": "edit", "label": "Answer C", "hint": "Enter answer C", "default": "Jupiter"},
|
|
9
|
+
"answers/d": {"type": "string", "phase": "edit", "label": "Answer D", "hint": "Enter answer D", "default": "Mercury"},
|
|
10
|
+
"control/correct": {"type": "choose", "phase": "edit", "label": "Correct answer", "hint": "Answer revealed as correct", "choose": ["none", "a", "b", "c", "d"], "default": "b"},
|
|
11
|
+
"content/playQuestion": {"type": "string", "phase": "play", "label": "Question", "hint": "Question currently shown during play", "source": "content/question", "readonly": true},
|
|
12
|
+
"control/display": {"type": "choose", "phase": "play", "label": "Display", "hint": "Choose whether viewers see the question or the answer", "choose": ["question", "answer"], "default": "question"},
|
|
13
|
+
"style/accent": {"type": "color", "phase": "edit", "label": "Accent", "hint": "Accent colour used by the quiz graphic", "default": "#ffb21c"}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -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
|
+
}
|