@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.
Files changed (133) hide show
  1. package/package.json +44 -0
  2. package/preview/assets/images/favicon.ico +0 -0
  3. package/preview/assets/images/favicon.png +0 -0
  4. package/preview/assets/images/login-right.webp +0 -0
  5. package/preview/assets/images/nebular.color.100h.png +0 -0
  6. package/preview/assets/images/nebular.color.white.100h.png +0 -0
  7. package/preview/assets/images/nebular.medallion.transparent..png +0 -0
  8. package/preview/assets/images/testcard.png +0 -0
  9. package/preview/assets/webfonts/fa-brands-400.woff2 +0 -0
  10. package/preview/assets/webfonts/fa-solid-900.woff2 +0 -0
  11. package/preview/bundle.min.js +6 -0
  12. package/preview/index.css +28 -0
  13. package/preview/index.html +23 -0
  14. package/readme.md +132 -0
  15. package/skill/nebular-overlay/SKILL.md +56 -0
  16. package/skill/nebular-overlay/agents/openai.yaml +4 -0
  17. package/skill/nebular-overlay/references/examples.md +327 -0
  18. package/skill/nebular-overlay/references/manifest.md +153 -0
  19. package/skill/nebular-overlay/references/runtime-and-design.md +88 -0
  20. package/skill/nebular-overlay/scripts/validate-templates.mjs +244 -0
  21. package/templates/all/broadcast/breaking-news/index.html +100 -0
  22. package/templates/all/broadcast/breaking-news/manifest.json +9 -0
  23. package/templates/all/broadcast/empty/index.html +46 -0
  24. package/templates/all/broadcast/empty/manifest.json +7 -0
  25. package/templates/all/broadcast/empty/nebular.medallion.transparent.png +0 -0
  26. package/templates/all/broadcast/event-countdown/index.html +160 -0
  27. package/templates/all/broadcast/event-countdown/logo.png +0 -0
  28. package/templates/all/broadcast/event-countdown/manifest.json +33 -0
  29. package/templates/all/broadcast/lower-third/index.html +77 -0
  30. package/templates/all/broadcast/lower-third/manifest.json +8 -0
  31. package/templates/all/broadcast/newsticker/index.html +153 -0
  32. package/templates/all/broadcast/newsticker/manifest.json +21 -0
  33. package/templates/all/broadcast/newsticker-frames/background-label-green.png +0 -0
  34. package/templates/all/broadcast/newsticker-frames/background-label-orange.png +0 -0
  35. package/templates/all/broadcast/newsticker-frames/banner.png +0 -0
  36. package/templates/all/broadcast/newsticker-frames/index.html +229 -0
  37. package/templates/all/broadcast/newsticker-frames/manifest.json +20 -0
  38. package/templates/all/broadcast/weather/index.html +382 -0
  39. package/templates/all/broadcast/weather/manifest.json +104 -0
  40. package/templates/all/engagement/call-to-action/index.html +116 -0
  41. package/templates/all/engagement/call-to-action/manifest.json +12 -0
  42. package/templates/all/engagement/chat/index.html +121 -0
  43. package/templates/all/engagement/chat/manifest.json +32 -0
  44. package/templates/all/engagement/post/index.html +132 -0
  45. package/templates/all/engagement/post/manifest.json +31 -0
  46. package/templates/all/gambling/bingo/index.html +227 -0
  47. package/templates/all/gambling/bingo/manifest.json +144 -0
  48. package/templates/all/gambling/keno/index.html +202 -0
  49. package/templates/all/gambling/keno/manifest.json +129 -0
  50. package/templates/all/gambling/lottery/index.html +159 -0
  51. package/templates/all/gambling/lottery/manifest.json +95 -0
  52. package/templates/all/gambling/prize-wall/index.html +256 -0
  53. package/templates/all/gambling/prize-wall/manifest.json +108 -0
  54. package/templates/all/gambling/roulette/index.html +200 -0
  55. package/templates/all/gambling/roulette/manifest.json +78 -0
  56. package/templates/all/gaming/battleship/index.html +578 -0
  57. package/templates/all/gaming/battleship/manifest.json +279 -0
  58. package/templates/all/gaming/gta/mission/index.html +121 -0
  59. package/templates/all/gaming/gta/mission/manifest.json +38 -0
  60. package/templates/all/sports/football/scoreboard/index.html +115 -0
  61. package/templates/all/sports/football/scoreboard/manifest.json +54 -0
  62. package/templates/all/sports/tennis/scoreboard/index.html +120 -0
  63. package/templates/all/sports/tennis/scoreboard/manifest.json +92 -0
  64. package/templates/all/trivia/audience-poll/index.html +60 -0
  65. package/templates/all/trivia/audience-poll/manifest.json +26 -0
  66. package/templates/all/trivia/classic-quiz/index.html +49 -0
  67. package/templates/all/trivia/classic-quiz/manifest.json +15 -0
  68. package/templates/all/trivia/lightning-round/index.html +126 -0
  69. package/templates/all/trivia/lightning-round/manifest.json +27 -0
  70. package/templates/all/trivia/pasapalabra/index.html +249 -0
  71. package/templates/all/trivia/pasapalabra/manifest.json +68 -0
  72. package/templates/all/trivia/quiz-deck/index.html +58 -0
  73. package/templates/all/trivia/quiz-deck/manifest.json +25 -0
  74. package/templates/all/trivia/true-or-false/index.html +81 -0
  75. package/templates/all/trivia/true-or-false/manifest.json +27 -0
  76. package/templates/basic/broadcast/empty/index.html +46 -0
  77. package/templates/basic/broadcast/empty/manifest.json +7 -0
  78. package/templates/basic/broadcast/empty/nebular.medallion.transparent.png +0 -0
  79. package/templates/basic/broadcast/event-countdown/index.html +160 -0
  80. package/templates/basic/broadcast/event-countdown/logo.png +0 -0
  81. package/templates/basic/broadcast/event-countdown/manifest.json +33 -0
  82. package/templates/basic/broadcast/lower-third/index.html +77 -0
  83. package/templates/basic/broadcast/lower-third/manifest.json +8 -0
  84. package/templates/basic/broadcast/newsticker/index.html +153 -0
  85. package/templates/basic/broadcast/newsticker/manifest.json +21 -0
  86. package/templates/basic/broadcast/weather/index.html +382 -0
  87. package/templates/basic/broadcast/weather/manifest.json +104 -0
  88. package/templates/basic/engagement/call-to-action/index.html +116 -0
  89. package/templates/basic/engagement/call-to-action/manifest.json +12 -0
  90. package/templates/basic/engagement/chat/index.html +121 -0
  91. package/templates/basic/engagement/chat/manifest.json +32 -0
  92. package/templates/basic/engagement/post/index.html +132 -0
  93. package/templates/basic/engagement/post/manifest.json +31 -0
  94. package/templates/basic/sports/football/scoreboard/index.html +115 -0
  95. package/templates/basic/sports/football/scoreboard/manifest.json +54 -0
  96. package/templates/basic/sports/tennis/scoreboard/index.html +120 -0
  97. package/templates/basic/sports/tennis/scoreboard/manifest.json +92 -0
  98. package/templates/debug/testing/security-probe/forbidden.js +1 -0
  99. package/templates/debug/testing/security-probe/forbidden.json +1 -0
  100. package/templates/debug/testing/security-probe/forbidden.mp4 +1 -0
  101. package/templates/debug/testing/security-probe/forbidden.svg +1 -0
  102. package/templates/debug/testing/security-probe/index.html +367 -0
  103. package/templates/debug/testing/security-probe/manifest.json +13 -0
  104. package/templates/empty/broadcast/empty/index.html +46 -0
  105. package/templates/empty/broadcast/empty/manifest.json +7 -0
  106. package/templates/empty/broadcast/empty/nebular.medallion.transparent.png +0 -0
  107. package/templates/games/gambling/bingo/index.html +227 -0
  108. package/templates/games/gambling/bingo/manifest.json +56 -0
  109. package/templates/games/gambling/keno/index.html +202 -0
  110. package/templates/games/gambling/keno/manifest.json +129 -0
  111. package/templates/games/gambling/lottery/index.html +159 -0
  112. package/templates/games/gambling/lottery/manifest.json +95 -0
  113. package/templates/games/gambling/prize-wall/index.html +256 -0
  114. package/templates/games/gambling/prize-wall/manifest.json +108 -0
  115. package/templates/games/gambling/roulette/index.html +200 -0
  116. package/templates/games/gambling/roulette/manifest.json +78 -0
  117. package/templates/games/gaming/battleship/index.html +578 -0
  118. package/templates/games/gaming/battleship/manifest.json +279 -0
  119. package/templates/games/gaming/gta/mission/index.html +121 -0
  120. package/templates/games/gaming/gta/mission/manifest.json +38 -0
  121. package/templates/games/trivia/audience-poll/index.html +60 -0
  122. package/templates/games/trivia/audience-poll/manifest.json +26 -0
  123. package/templates/games/trivia/classic-quiz/index.html +49 -0
  124. package/templates/games/trivia/classic-quiz/manifest.json +15 -0
  125. package/templates/games/trivia/lightning-round/index.html +126 -0
  126. package/templates/games/trivia/lightning-round/manifest.json +27 -0
  127. package/templates/games/trivia/pasapalabra/index.html +249 -0
  128. package/templates/games/trivia/pasapalabra/manifest.json +68 -0
  129. package/templates/games/trivia/quiz-deck/index.html +58 -0
  130. package/templates/games/trivia/quiz-deck/manifest.json +25 -0
  131. package/templates/games/trivia/true-or-false/index.html +81 -0
  132. package/templates/games/trivia/true-or-false/manifest.json +27 -0
  133. package/tool/cli.cjs +7 -0
@@ -0,0 +1,159 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Lottery Draw</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
+ font-family: Inter, system-ui, sans-serif;
21
+ color: #fff
22
+ }
23
+
24
+ .draw {
25
+ position: absolute;
26
+ left: 6%;
27
+ right: 6%;
28
+ bottom: 9%;
29
+ padding: 1.5rem 1.8rem 1.8rem;
30
+ background: linear-gradient(135deg, #281451f2, #0c1838f2);
31
+ border: 1px solid #ffffff38;
32
+ border-radius: 1.4rem;
33
+ box-shadow: 0 1.5rem 4rem #0006
34
+ }
35
+
36
+ .head {
37
+ display: flex;
38
+ justify-content: space-between;
39
+ align-items: end
40
+ }
41
+
42
+ .eyebrow {
43
+ color: #8ce8ff;
44
+ font-size: 1.2rem;
45
+ font-weight: 850;
46
+ letter-spacing: .18em
47
+ }
48
+
49
+ .title {
50
+ margin-top: .25rem;
51
+ font-size: 2.6rem;
52
+ font-weight: 950
53
+ }
54
+
55
+ .status {
56
+ color: #c8c6dd;
57
+ font-size: 1.3rem;
58
+ font-weight: 750
59
+ }
60
+
61
+ .balls {
62
+ display: flex;
63
+ justify-content: center;
64
+ align-items: center;
65
+ gap: 1.1rem;
66
+ min-height: 12rem;
67
+ margin-top: 1.2rem;
68
+ padding: 1.2rem;
69
+ border-radius: 1rem;
70
+ background: #ffffff0c
71
+ }
72
+
73
+ .empty {
74
+ color: #ffffff66;
75
+ font-size: 1.6rem;
76
+ font-weight: 750
77
+ }
78
+
79
+ .ball {
80
+ width: 8rem;
81
+ height: 8rem;
82
+ display: grid;
83
+ place-items: center;
84
+ border-radius: 50%;
85
+ background: radial-gradient(circle at 32% 25%, #fff 0 7%, #70e2ff 29%, #385ddb 72%);
86
+ border: .45rem solid #ffffffc7;
87
+ color: #10173c;
88
+ font-size: 3rem;
89
+ font-weight: 950;
90
+ box-shadow: inset -.7rem -.8rem 1.2rem #14217666, 0 .8rem 1.6rem #0006
91
+ }
92
+
93
+ .ball.latest {
94
+ width: 9.5rem;
95
+ height: 9.5rem;
96
+ background: radial-gradient(circle at 32% 25%, #fff 0 7%, #ffe66d 29%, #ff8d21 72%);
97
+ animation: drop .45s cubic-bezier(.2, .9, .35, 1.25)
98
+ }
99
+
100
+ .order {
101
+ margin-top: .65rem;
102
+ text-align: center;
103
+ color: #aaa9c3;
104
+ font-size: 1.15rem;
105
+ font-weight: 800;
106
+ letter-spacing: .1em
107
+ }
108
+
109
+ @keyframes drop {
110
+ from {
111
+ opacity: 0;
112
+ transform: translateY(-4rem) rotate(-20deg) scale(.6)
113
+ }
114
+ }
115
+ </style>
116
+ </head>
117
+ <body>
118
+ <section class="draw">
119
+ <div class="head">
120
+ <div>
121
+ <div class="eyebrow" id="eyebrow">OFFICIAL DRAW</div>
122
+ <div class="title" id="title">Lucky 49</div>
123
+ </div>
124
+ <div class="status" id="status">6 numbers drawn</div>
125
+ </div>
126
+ <div class="balls" id="balls"></div>
127
+ <div class="order">NUMBERS ARE DISPLAYED IN DRAW ORDER</div>
128
+ </section>
129
+ <script>
130
+ const ALL = Array.from({length: 49}, (_, i) => i + 1);
131
+ const $ = id => document.getElementById(id);
132
+
133
+ function render(numbers) {
134
+ const values = (Array.isArray(numbers) ? numbers : []).map(Number).filter(n => n >= 1 && n <= 49), holder = $("balls");
135
+ holder.replaceChildren();
136
+ if (!values.length) {
137
+ const empty = document.createElement("div");
138
+ empty.className = "empty";
139
+ empty.textContent = "Waiting for the first number…";
140
+ holder.append(empty)
141
+ } else values.forEach((number, index) => {
142
+ const ball = document.createElement("div");
143
+ ball.className = `ball ${index === values.length - 1 ? "latest" : ""}`;
144
+ ball.textContent = number;
145
+ holder.append(ball)
146
+ });
147
+ $("status").textContent = `${values.length} ${values.length === 1 ? "number" : "numbers"} drawn`
148
+ }
149
+
150
+ window.addEventListener("nebular-overlay:update", ({detail}) => {
151
+ if (detail.content?.eyebrow != null) $("eyebrow").textContent = detail.content.eyebrow;
152
+ if (detail.content?.title != null) $("title").textContent = detail.content.title;
153
+ if (detail.draw?.numbers != null) render(detail.draw.numbers)
154
+ });
155
+
156
+
157
+ </script>
158
+ </body>
159
+ </html>
@@ -0,0 +1,95 @@
1
+ {
2
+ "instructions": "Select the official drawn numbers, edit the lottery title and draw label if needed, then press Update to publish the result strip. Use Reset draw to clear the numbers, then press Update to publish the reset.",
3
+ "meta": {
4
+ "content/eyebrow": {
5
+ "type": "string",
6
+ "phase": "edit",
7
+ "label": "Draw label", "hint": "Enter short label",
8
+ "default": "OFFICIAL DRAW"
9
+ },
10
+ "content/title": {
11
+ "type": "string",
12
+ "phase": "edit",
13
+ "label": "Lottery title", "hint": "Enter lottery title",
14
+ "default": "Lucky 49"
15
+ },
16
+ "draw/numbers": {
17
+ "type": "multi",
18
+ "phase": "play",
19
+ "container": "big",
20
+ "label": "Drawn numbers", "hint": "Numbers selected in the current draw",
21
+ "choose": [
22
+ 1,
23
+ 2,
24
+ 3,
25
+ 4,
26
+ 5,
27
+ 6,
28
+ 7,
29
+ 8,
30
+ 9,
31
+ 10,
32
+ 11,
33
+ 12,
34
+ 13,
35
+ 14,
36
+ 15,
37
+ 16,
38
+ 17,
39
+ 18,
40
+ 19,
41
+ 20,
42
+ 21,
43
+ 22,
44
+ 23,
45
+ 24,
46
+ 25,
47
+ 26,
48
+ 27,
49
+ 28,
50
+ 29,
51
+ 30,
52
+ 31,
53
+ 32,
54
+ 33,
55
+ 34,
56
+ 35,
57
+ 36,
58
+ 37,
59
+ 38,
60
+ 39,
61
+ 40,
62
+ 41,
63
+ 42,
64
+ 43,
65
+ 44,
66
+ 45,
67
+ 46,
68
+ 47,
69
+ 48,
70
+ 49
71
+ ],
72
+ "cols": 7,
73
+ "rows": 7,
74
+ "responsive": true,
75
+ "minCellWidth": "2.54rem",
76
+ "default": [
77
+ 7,
78
+ 14,
79
+ 22,
80
+ 31,
81
+ 38,
82
+ 46
83
+ ]
84
+ },
85
+ "draw/reset": {
86
+ "type": "reset",
87
+ "phase": "play",
88
+ "icon": "undo",
89
+ "text": "Reset draw", "hint": "Clear every number from the current draw",
90
+ "values": {
91
+ "draw/numbers": []
92
+ }
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,256 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Prize Wall</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
+ font-family: Inter, system-ui, sans-serif;
21
+ color: #fff
22
+ }
23
+
24
+ .wall {
25
+ position: absolute;
26
+ inset: auto 5% 9% 5%;
27
+ display: grid;
28
+ grid-template-columns:1fr 21rem;
29
+ gap: 1rem
30
+ }
31
+
32
+ .board, .reveal {
33
+ background: #16121ff2;
34
+ border: 1px solid #ffffff2b;
35
+ border-radius: 1.2rem;
36
+ box-shadow: 0 1.5rem 4rem #0006
37
+ }
38
+
39
+ .board {
40
+ padding: 1.2rem
41
+ }
42
+
43
+ .head {
44
+ display: flex;
45
+ justify-content: space-between;
46
+ align-items: center;
47
+ margin-bottom: 1rem
48
+ }
49
+
50
+ .head strong {
51
+ font-size: 1.7rem;
52
+ letter-spacing: .1em
53
+ }
54
+
55
+ .head span {
56
+ color: #baadc8;
57
+ font-size: 1.1rem;
58
+ font-weight: 750
59
+ }
60
+
61
+ .doors {
62
+ display: grid;
63
+ grid-template-columns:repeat(6, minmax(0, 1fr));
64
+ gap: .65rem
65
+ }
66
+
67
+ .door {
68
+ height: 6rem;
69
+ display: grid;
70
+ place-items: center;
71
+ border-radius: .6rem;
72
+ background: linear-gradient(145deg, #57306e, #27152f);
73
+ border: 2px solid #ce9cff44;
74
+ color: #e7d3ff;
75
+ font-size: 1.9rem;
76
+ font-weight: 950;
77
+ box-shadow: inset 0 0 0 .25rem #ffffff08
78
+ }
79
+
80
+ .door-icon {
81
+ display: none;
82
+ font-size: 2.65rem;
83
+ line-height: 1;
84
+ filter: grayscale(.2)
85
+ }
86
+
87
+ .door.open {
88
+ background: #141119;
89
+ color: #fff;
90
+ border-color: #ffffff18
91
+ }
92
+
93
+ .door.open .door-number,
94
+ .door.revealed .door-number {
95
+ display: none
96
+ }
97
+
98
+ .door.open .door-icon,
99
+ .door.revealed .door-icon {
100
+ display: block
101
+ }
102
+
103
+ .door.revealed {
104
+ background: #211929;
105
+ border-color: #8d729f55;
106
+ color: #cdbed7;
107
+ opacity: .78
108
+ }
109
+
110
+ .door.latest {
111
+ background: linear-gradient(145deg, #ffd95d, #e98914);
112
+ border-color: #fff0a6;
113
+ color: #2a1820;
114
+ transform: scale(1.08);
115
+ box-shadow: 0 .8rem 1.8rem #0008;
116
+ animation: open .45s ease
117
+ }
118
+
119
+ .reveal {
120
+ display: flex;
121
+ flex-direction: column;
122
+ align-items: center;
123
+ justify-content: center;
124
+ padding: 1.5rem;
125
+ text-align: center;
126
+ background: radial-gradient(circle at 50% 25%, #56336a, #17121f 68%)
127
+ }
128
+
129
+ .eyebrow {
130
+ color: #ffcf55;
131
+ font-size: 1.15rem;
132
+ font-weight: 850;
133
+ letter-spacing: .16em
134
+ }
135
+
136
+ .prize-number {
137
+ font-size: 5.5rem;
138
+ font-weight: 950;
139
+ line-height: 1;
140
+ margin: 1.1rem
141
+ }
142
+
143
+ .prize {
144
+ font-size: 2.2rem;
145
+ font-weight: 900
146
+ }
147
+
148
+ .reveal-icon {
149
+ margin-top: .5rem;
150
+ font-size: 3.2rem;
151
+ line-height: 1
152
+ }
153
+
154
+ .count {
155
+ margin-top: 1.5rem;
156
+ color: #ffc958;
157
+ font-size: 1.2rem;
158
+ font-weight: 800
159
+ }
160
+
161
+ @keyframes open {
162
+ 0% {
163
+ transform: perspective(20rem) rotateY(85deg)
164
+ }
165
+ 100% {
166
+ transform: scale(1.08)
167
+ }
168
+ }
169
+ </style>
170
+ </head>
171
+ <body>
172
+ <main class="wall">
173
+ <section class="board">
174
+ <div class="head"><strong id="title">THE PRIZE WALL</strong><span>SELECT A DOOR TO REVEAL IT</span></div>
175
+ <div class="doors" id="doors"></div>
176
+ </section>
177
+ <aside class="reveal">
178
+ <div class="eyebrow">LATEST REVEAL</div>
179
+ <div class="prize-number" id="number">—</div>
180
+ <div class="prize" id="prize">No Prize</div>
181
+ <div class="reveal-icon" id="revealIcon">🕸️</div>
182
+ <div class="count" id="count">0 of 30 opened</div>
183
+ </aside>
184
+ </main>
185
+ <script>
186
+ const
187
+ ALL = Array.from({length: 30}, (_, i) => i + 1),
188
+ ICONS = {
189
+ none: "🕸️",
190
+ gift: "🎁",
191
+ car: "🚘",
192
+ plane: "✈️",
193
+ money: "💰",
194
+ gem: "💎",
195
+ tv: "📺",
196
+ phone: "📱",
197
+ ticket: "🎟️",
198
+ suitcase: "🧳",
199
+ star: "⭐"
200
+ },
201
+ $ = id => document.getElementById(id),
202
+ doors = new Map(),
203
+ state = {prizes: [], opened: [], reveal: "hidden"};
204
+
205
+ ALL.forEach(number => {
206
+ const
207
+ door = document.createElement("div"),
208
+ doorNumber = document.createElement("span"),
209
+ doorIcon = document.createElement("span");
210
+ door.className = "door";
211
+ doorNumber.className = "door-number";
212
+ doorNumber.textContent = number;
213
+ doorIcon.className = "door-icon";
214
+ door.append(doorNumber, doorIcon);
215
+ $("doors").append(door);
216
+ doors.set(number, {door, icon: doorIcon})
217
+ });
218
+
219
+ function render() {
220
+ const
221
+ opened = (Array.isArray(state.opened) ? state.opened : []).map(Number).filter(n => n >= 1 && n <= 30),
222
+ openedSet = new Set(opened),
223
+ revealAll = state.reveal === "revealed",
224
+ latest = opened.at(-1),
225
+ prizes = new Map((Array.isArray(state.prizes) ? state.prizes : [])
226
+ .map(prize => [Number(prize?.position), prize])
227
+ .filter(([position]) => position >= 1 && position <= 30)),
228
+ latestPrize = prizes.get(latest),
229
+ latestWon = latestPrize?.icon !== "none" && Boolean(latestPrize?.prize?.trim());
230
+
231
+ doors.forEach(({door, icon}, position) => {
232
+ const
233
+ prize = prizes.get(position),
234
+ won = prize?.icon !== "none" && Boolean(prize?.prize?.trim());
235
+ door.classList.toggle("open", openedSet.has(position));
236
+ door.classList.toggle("revealed", revealAll && !openedSet.has(position));
237
+ door.classList.toggle("latest", position === latest);
238
+ icon.textContent = won ? (ICONS[prize.icon] || ICONS.gift) : ICONS.none;
239
+ door.title = won ? prize.prize : "No Prize"
240
+ });
241
+ $("number").textContent = latest ?? "—";
242
+ $("prize").textContent = latestWon ? latestPrize.prize : "No Prize";
243
+ $("revealIcon").textContent = latestWon ? (ICONS[latestPrize.icon] || ICONS.gift) : ICONS.none;
244
+ $("count").textContent = `${openedSet.size} of 30 opened`
245
+ }
246
+ window.addEventListener("nebular-overlay:update", ({detail}) => {
247
+ if (detail.content?.title != null) $("title").textContent = detail.content.title;
248
+ if (detail.game?.prizes != null) state.prizes = detail.game.prizes;
249
+ if (detail.game?.opened != null) state.opened = detail.game.opened;
250
+ if (detail.game?.reveal != null) state.reveal = detail.game.reveal;
251
+ if (detail.game?.prizes != null || detail.game?.opened != null || detail.game?.reveal != null) render()
252
+ });
253
+
254
+ </script>
255
+ </body>
256
+ </html>
@@ -0,0 +1,108 @@
1
+ {
2
+ "instructions": "Configure each prize with its wall position, name and icon before play. During the game, select every opened door and press Update; the latest selected door displays its assigned prize automatically. When the game ends, reveal the remaining doors to show where every prize was hidden. Use Reset wall to close every door and hide the remaining prizes, then press Update.",
3
+ "meta": {
4
+ "content/title": {
5
+ "type": "string",
6
+ "phase": "edit",
7
+ "label": "Wall title", "hint": "Enter wall title",
8
+ "default": "THE PRIZE WALL"
9
+ },
10
+ "game/prizes": {
11
+ "type": "set",
12
+ "phase": "edit",
13
+ "label": "Prizes", "hint": "Assign prizes to positions on the wall",
14
+ "container": "big",
15
+ "max": 30,
16
+ "default": [
17
+ {"position": 2, "prize": "Buick Limousine", "icon": "car"},
18
+ {"position": 7, "prize": "Tropical Holiday", "icon": "plane"},
19
+ {"position": 12, "prize": "$10,000 Cash", "icon": "money"},
20
+ {"position": 19, "prize": "Diamond Necklace", "icon": "gem"},
21
+ {"position": 25, "prize": "Home Cinema", "icon": "tv"}
22
+ ],
23
+ "meta": {
24
+ "position": {"size": 2, "type": "number", "label": "Position", "hint": "1–30", "min": 1, "max": 30},
25
+ "prize": {"size": 6, "type": "string", "label": "Prize", "hint": "Enter prize name"},
26
+ "icon": {
27
+ "size": 3,
28
+ "type": "select",
29
+ "label": "Icon", "hint": "Symbol displayed inside the opened door",
30
+ "choose": [
31
+ {"value": "none", "caption": "No prize"},
32
+ {"value": "gift", "caption": "Gift"},
33
+ {"value": "car", "caption": "Car"},
34
+ {"value": "plane", "caption": "Holiday"},
35
+ {"value": "money", "caption": "Cash"},
36
+ {"value": "gem", "caption": "Jewellery"},
37
+ {"value": "tv", "caption": "Television"},
38
+ {"value": "phone", "caption": "Phone"},
39
+ {"value": "ticket", "caption": "Tickets"},
40
+ {"value": "suitcase", "caption": "Luggage"},
41
+ {"value": "star", "caption": "Special prize"}
42
+ ],
43
+ "default": "none"
44
+ }
45
+ }
46
+ },
47
+ "game/opened": {
48
+ "type": "multi",
49
+ "phase": "play",
50
+ "label": "Opened doors", "hint": "Doors that have already been opened",
51
+ "container": "big",
52
+ "choose": [
53
+ 1,
54
+ 2,
55
+ 3,
56
+ 4,
57
+ 5,
58
+ 6,
59
+ 7,
60
+ 8,
61
+ 9,
62
+ 10,
63
+ 11,
64
+ 12,
65
+ 13,
66
+ 14,
67
+ 15,
68
+ 16,
69
+ 17,
70
+ 18,
71
+ 19,
72
+ 20,
73
+ 21,
74
+ 22,
75
+ 23,
76
+ 24,
77
+ 25,
78
+ 26,
79
+ 27,
80
+ 28,
81
+ 29,
82
+ 30
83
+ ],
84
+ "cols": 6,
85
+ "rows": 5,
86
+ "responsive": true,
87
+ "minCellWidth": "2.54rem",
88
+ "default": []
89
+ },
90
+ "game/reveal": {
91
+ "type": "choose",
92
+ "phase": "play",
93
+ "label": "Remaining prizes", "hint": "Reveal every unopened position when the game ends",
94
+ "choose": ["hidden", "revealed"],
95
+ "default": "hidden"
96
+ },
97
+ "game/reset": {
98
+ "type": "reset",
99
+ "phase": "play",
100
+ "icon": "undo",
101
+ "text": "Reset wall", "hint": "Close every door and reset the wall",
102
+ "values": {
103
+ "game/opened": [],
104
+ "game/reveal": "hidden"
105
+ }
106
+ }
107
+ }
108
+ }