@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,227 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Bingo Caller Board</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: #fff;
21
+ font-family: Inter, system-ui, sans-serif;
22
+ }
23
+
24
+ .bingo {
25
+ position: absolute;
26
+ inset: auto 5% 9% 5%;
27
+ display: grid;
28
+ grid-template-columns: 22rem minmax(0, 1fr);
29
+ gap: 1rem;
30
+ filter: drop-shadow(0 1.4rem 3rem rgba(0, 0, 0, .34));
31
+ }
32
+
33
+ .caller,
34
+ .board {
35
+ background: rgba(8, 13, 22, .94);
36
+ border: 1px solid rgba(255, 255, 255, .16);
37
+ border-radius: 1.2rem;
38
+ }
39
+
40
+ .caller {
41
+ display: flex;
42
+ flex-direction: column;
43
+ align-items: center;
44
+ justify-content: center;
45
+ min-height: 38rem;
46
+ padding: 1.5rem;
47
+ text-align: center;
48
+ }
49
+
50
+ .eyebrow {
51
+ color: #ffb33f;
52
+ font-size: 1.2rem;
53
+ font-weight: 850;
54
+ letter-spacing: .18em;
55
+ text-transform: uppercase;
56
+ }
57
+
58
+ .ball {
59
+ width: 12rem;
60
+ height: 12rem;
61
+ margin: 1.4rem 0;
62
+ display: grid;
63
+ place-items: center;
64
+ border-radius: 50%;
65
+ background: radial-gradient(circle at 34% 28%, #fff8b3 0 8%, #ffc848 34%, #f18b12 72%);
66
+ border: .55rem solid rgba(255, 255, 255, .85);
67
+ box-shadow: inset -.8rem -1rem 1.5rem rgba(132, 51, 0, .3), 0 1rem 2.5rem rgba(255, 151, 21, .25);
68
+ color: #172033;
69
+ font-size: 5.2rem;
70
+ font-weight: 950;
71
+ animation: called .45s cubic-bezier(.18, .89, .32, 1.28);
72
+ }
73
+
74
+ .title {
75
+ font-size: 2rem;
76
+ font-weight: 900;
77
+ }
78
+
79
+ .subtitle {
80
+ margin-top: .35rem;
81
+ color: #aeb8c8;
82
+ font-size: 1.3rem;
83
+ line-height: 1.3;
84
+ }
85
+
86
+ .count {
87
+ margin-top: 1.4rem;
88
+ color: #ffbd53;
89
+ font-size: 1.2rem;
90
+ font-weight: 800;
91
+ letter-spacing: .08em;
92
+ text-transform: uppercase;
93
+ }
94
+
95
+ .board {
96
+ min-height: 38rem;
97
+ padding: 1rem;
98
+ }
99
+
100
+ .board-header {
101
+ display: flex;
102
+ justify-content: space-between;
103
+ align-items: center;
104
+ padding: .15rem .25rem .9rem;
105
+ }
106
+
107
+ .board-header strong {
108
+ font-size: 1.4rem;
109
+ letter-spacing: .08em;
110
+ }
111
+
112
+ .legend {
113
+ color: #aeb8c8;
114
+ font-size: 1.05rem;
115
+ font-weight: 750;
116
+ }
117
+
118
+ .numbers {
119
+ display: grid;
120
+ grid-template-columns: repeat(10, minmax(0, 1fr));
121
+ gap: .45rem;
122
+ }
123
+
124
+ .number {
125
+ display: grid;
126
+ place-items: center;
127
+ border-radius: .55rem;
128
+ background: rgba(255, 255, 255, .06);
129
+ border: 1px solid rgba(255, 255, 255, .1);
130
+ color: rgba(255, 255, 255, .4);
131
+ font-size: 2rem;
132
+ font-weight: 850;
133
+ transition: background .25s ease, color .25s ease, transform .25s ease;
134
+ aspect-ratio: 1.9;
135
+ }
136
+
137
+ .number.called {
138
+ background: #fff;
139
+ border-color: #fff;
140
+ color: #172033;
141
+ }
142
+
143
+ .number.current {
144
+ z-index: 1;
145
+ background: #ff9d18;
146
+ border-color: #ffd486;
147
+ color: #111827;
148
+ transform: scale(1.14);
149
+ box-shadow: 0 0 0 .22rem rgba(255, 157, 24, .25), 0 .6rem 1.4rem rgba(0, 0, 0, .35);
150
+ }
151
+
152
+ @keyframes called {
153
+ from {
154
+ opacity: 0;
155
+ transform: scale(.55) rotate(-12deg);
156
+ }
157
+ }
158
+ </style>
159
+ </head>
160
+ <body>
161
+ <main class="bingo">
162
+ <section class="caller">
163
+ <div class="eyebrow" id="eyebrow">Just called</div>
164
+ <div class="ball" id="ball">73</div>
165
+ <div class="title" id="title">Friday Night Bingo</div>
166
+ <div class="subtitle" id="subtitle">Eyes down — good luck!</div>
167
+ <div class="count" id="count">8 numbers called</div>
168
+ </section>
169
+
170
+ <section class="board">
171
+ <div class="board-header">
172
+ <strong>CALLED NUMBERS</strong>
173
+ <span class="legend">WHITE · CALLED&nbsp;&nbsp;&nbsp; ORANGE · CURRENT</span>
174
+ </div>
175
+ <div class="numbers" id="numbers"></div>
176
+ </section>
177
+ </main>
178
+
179
+ <script>
180
+ const
181
+ MAX_NUMBER = 90,
182
+ $ = (id) => document.getElementById(id),
183
+ cells = new Map();
184
+
185
+ let called = [];
186
+
187
+ for (let number = 1; number <= MAX_NUMBER; number++) {
188
+ const cell = document.createElement("div");
189
+ cell.className = "number";
190
+ cell.textContent = number;
191
+ $("numbers").append(cell);
192
+ cells.set(number, cell);
193
+ }
194
+
195
+ function updateBoard() {
196
+ const
197
+ values = (Array.isArray(called) ? called : []).map(Number).filter(number => number >= 1 && number <= MAX_NUMBER),
198
+ currentNumber = values.at(-1),
199
+ calledNumbers = new Set(values);
200
+
201
+ cells.forEach((cell, number) => {
202
+ cell.classList.toggle("called", calledNumbers.has(number));
203
+ cell.classList.toggle("current", number === currentNumber);
204
+ });
205
+
206
+ const ballValue = currentNumber ?? "—";
207
+ if ($("ball").textContent !== String(ballValue)) {
208
+ const replacement = $("ball").cloneNode(false);
209
+ replacement.textContent = ballValue;
210
+ $("ball").replaceWith(replacement);
211
+ }
212
+
213
+ $("count").textContent = `${calledNumbers.size} ${calledNumbers.size === 1 ? "number" : "numbers"} called`;
214
+ }
215
+
216
+ window.addEventListener("nebular-overlay:update", ({detail}) => {
217
+
218
+ if (detail.content?.eyebrow != null) $("eyebrow").textContent = detail.content.eyebrow;
219
+ if (detail.content?.title != null) $("title").textContent = detail.content.title;
220
+ if (detail.content?.subtitle != null) $("subtitle").textContent = detail.content.subtitle;
221
+ if (detail.game?.called != null) called = detail.game.called;
222
+ if (detail.game?.called != null) updateBoard();
223
+ })
224
+
225
+ </script>
226
+ </body>
227
+ </html>
@@ -0,0 +1,56 @@
1
+ {
2
+ "instructions": "Select each number as it is called, then press Update. The most recently selected number is highlighted and displayed automatically. Use Reset balls to clear the selection, then press Update to publish the reset. Update the event copy before play if desired.",
3
+ "meta": {
4
+ "content/eyebrow": {
5
+ "type": "string",
6
+ "phase": "edit",
7
+ "label": "Current number label", "hint": "Enter short label",
8
+ "default": "JUST CALLED"
9
+ },
10
+ "content/title": {
11
+ "type": "string",
12
+ "phase": "edit",
13
+ "label": "Event title", "hint": "Enter event title",
14
+ "default": "Friday Night Bingo"
15
+ },
16
+ "content/subtitle": {
17
+ "type": "string",
18
+ "phase": "edit",
19
+ "label": "Subtitle", "hint": "Enter subtitle",
20
+ "default": "Eyes down — good luck!"
21
+ },
22
+ "game/called": {
23
+ "type": "multi",
24
+ "phase": "play",
25
+ "label": "Called numbers", "hint": "Numbers called so far; the latest selection is highlighted",
26
+ "container": "big",
27
+ "choose": [
28
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
29
+ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
30
+ 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31
+ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
32
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
33
+ 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
34
+ 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
35
+ 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
36
+ 81, 82, 83, 84, 85, 86, 87, 88, 89, 90
37
+ ],
38
+ "cols": 10,
39
+ "rows": 9,
40
+ "responsive": true,
41
+ "minCellWidth": "2.54rem",
42
+ "default": [
43
+ 3, 8, 17, 24, 31, 42, 56, 73
44
+ ]
45
+ },
46
+ "game/reset": {
47
+ "type": "reset",
48
+ "phase": "play",
49
+ "icon": "undo",
50
+ "text": "Reset balls", "hint": "Clear every called number",
51
+ "values": {
52
+ "game/called": []
53
+ }
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,202 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Keno Live Board</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
+ .keno {
25
+ position: absolute;
26
+ inset: auto 5% 9% 5%;
27
+ display: grid;
28
+ grid-template-columns: 19rem 1fr;
29
+ gap: .9rem;
30
+ }
31
+
32
+ .side, .board {
33
+ background: #081b2df2;
34
+ border: 1px solid #ffffff29;
35
+ border-radius: 1rem;
36
+ box-shadow: 0 1.3rem 3.5rem #0006
37
+ }
38
+
39
+ .side {
40
+ display: flex;
41
+ flex-direction: column;
42
+ align-items: center;
43
+ justify-content: center;
44
+ padding: 1.3rem;
45
+ background: radial-gradient(circle at 50% 30%, #174a67, #071725 70%);
46
+ text-align: center
47
+ }
48
+
49
+ .logo {
50
+ color: #61ddff;
51
+ font-size: 1.15rem;
52
+ font-weight: 900;
53
+ letter-spacing: .2em
54
+ }
55
+
56
+ .latest {
57
+ width: 10.5rem;
58
+ height: 10.5rem;
59
+ margin: 1.2rem;
60
+ display: grid;
61
+ place-items: center;
62
+ border-radius: 50%;
63
+ background: radial-gradient(circle at 32% 25%, #fff 0 6%, #73f2ff 28%, #1689c5 72%);
64
+ border: .5rem solid #d8faff;
65
+ color: #062239;
66
+ font-size: 4.5rem;
67
+ font-weight: 950;
68
+ animation: pop .4s ease
69
+ }
70
+
71
+ .title {
72
+ font-size: 1.9rem;
73
+ font-weight: 900
74
+ }
75
+
76
+ .subtitle {
77
+ margin-top: .4rem;
78
+ color: #9eb9c9;
79
+ font-size: 1.25rem;
80
+ line-height: 1.3
81
+ }
82
+
83
+ .count {
84
+ margin-top: 1.2rem;
85
+ color: #61ddff;
86
+ font-size: 1.15rem;
87
+ font-weight: 850
88
+ }
89
+
90
+ .board {
91
+ padding: 1rem
92
+ }
93
+
94
+ .header {
95
+ display: flex;
96
+ justify-content: space-between;
97
+ margin-bottom: .75rem
98
+ }
99
+
100
+ .header strong {
101
+ font-size: 1.3rem;
102
+ letter-spacing: .1em
103
+ }
104
+
105
+ .header span {
106
+ color: #91aebf;
107
+ font-size: 1rem;
108
+ font-weight: 750
109
+ }
110
+
111
+ .numbers {
112
+ display: grid;
113
+ grid-template-columns:repeat(10, minmax(0, 1fr));
114
+ gap: .36rem
115
+ }
116
+
117
+ .number {
118
+ height: 3.6rem;
119
+ display: grid;
120
+ place-items: center;
121
+ border-radius: .45rem;
122
+ background: #ffffff0a;
123
+ border: 1px solid #ffffff14;
124
+ color: #ffffff54;
125
+ font-size: 1.2rem;
126
+ font-weight: 800
127
+ }
128
+
129
+ .number.hit {
130
+ background: #eefcff;
131
+ color: #08233a;
132
+ border-color: #fff
133
+ }
134
+
135
+ .number.latest {
136
+ width: auto;
137
+ height: 3.6rem;
138
+ margin: 0;
139
+ border: .15rem solid #bbf7ff;
140
+ background: #25c5ee;
141
+ color: #062239;
142
+ font-size: 1.35rem;
143
+ transform: scale(1.12);
144
+ box-shadow: 0 .6rem 1.3rem #0008;
145
+ z-index: 2
146
+ }
147
+
148
+ @keyframes pop {
149
+ from {
150
+ opacity: 0;
151
+ transform: scale(.45) rotate(18deg)
152
+ }
153
+ }
154
+ </style>
155
+ </head>
156
+ <body>
157
+ <main class="keno">
158
+ <aside class="side">
159
+ <div class="logo">KENO LIVE</div>
160
+ <div class="latest" id="latest">63</div>
161
+ <div class="title" id="title">Evening Draw</div>
162
+ <div class="subtitle" id="subtitle">Watch your numbers light up</div>
163
+ <div class="count" id="count">9 numbers drawn</div>
164
+ </aside>
165
+ <section class="board">
166
+ <div class="header"><strong>DRAW BOARD</strong><span>1–80 · LATEST NUMBER IN BLUE</span></div>
167
+ <div class="numbers" id="numbers"></div>
168
+ </section>
169
+ </main>
170
+ <script>
171
+ const
172
+ ALL = Array.from({length: 80}, (_, i) => i + 1);
173
+
174
+ const $ = id => document.getElementById(id), cells = new Map();
175
+
176
+ ALL.forEach(n => {
177
+ const cell = document.createElement("div");
178
+ cell.className = "number";
179
+ cell.textContent = n;
180
+ $("numbers").append(cell);
181
+ cells.set(n, cell)
182
+ });
183
+
184
+ function render(numbers) {
185
+ const values = (Array.isArray(numbers) ? numbers : []).map(Number).filter(n => n >= 1 && n <= 80), hits = new Set(values), latest = values.at(-1);
186
+ cells.forEach((cell, n) => {
187
+ cell.classList.toggle("hit", hits.has(n));
188
+ cell.classList.toggle("latest", n === latest)
189
+ });
190
+ $("latest").textContent = latest ?? "—";
191
+ $("count").textContent = `${hits.size} ${hits.size === 1 ? "number" : "numbers"} drawn`
192
+ }
193
+
194
+ window.addEventListener("nebular-overlay:update", ({detail}) => {
195
+ if (detail.content?.title != null) $("title").textContent = detail.content.title;
196
+ if (detail.content?.subtitle != null) $("subtitle").textContent = detail.content.subtitle;
197
+ if (detail.draw?.numbers != null) render(detail.draw.numbers)
198
+ });
199
+
200
+ </script>
201
+ </body>
202
+ </html>
@@ -0,0 +1,129 @@
1
+ {
2
+ "instructions": "Select all numbers drawn in the current game and edit the draw title or subtitle as needed. Press Update after each draw to refresh the board. Use Reset draw to clear the numbers, then press Update to publish the reset.",
3
+ "meta": {
4
+ "content/title": {
5
+ "type": "string",
6
+ "phase": "edit",
7
+ "label": "Draw title", "hint": "Enter draw title",
8
+ "default": "Evening Draw"
9
+ },
10
+ "content/subtitle": {
11
+ "type": "string",
12
+ "phase": "edit",
13
+ "label": "Subtitle", "hint": "Enter subtitle",
14
+ "default": "Watch your numbers light up"
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
+ 50,
72
+ 51,
73
+ 52,
74
+ 53,
75
+ 54,
76
+ 55,
77
+ 56,
78
+ 57,
79
+ 58,
80
+ 59,
81
+ 60,
82
+ 61,
83
+ 62,
84
+ 63,
85
+ 64,
86
+ 65,
87
+ 66,
88
+ 67,
89
+ 68,
90
+ 69,
91
+ 70,
92
+ 71,
93
+ 72,
94
+ 73,
95
+ 74,
96
+ 75,
97
+ 76,
98
+ 77,
99
+ 78,
100
+ 79,
101
+ 80
102
+ ],
103
+ "cols": 10,
104
+ "rows": 8,
105
+ "responsive": true,
106
+ "minCellWidth": "2.54rem",
107
+ "default": [
108
+ 4,
109
+ 11,
110
+ 23,
111
+ 37,
112
+ 44,
113
+ 52,
114
+ 58,
115
+ 71,
116
+ 63
117
+ ]
118
+ },
119
+ "draw/reset": {
120
+ "type": "reset",
121
+ "phase": "play",
122
+ "icon": "undo",
123
+ "text": "Reset draw", "hint": "Clear every number from the current draw",
124
+ "values": {
125
+ "draw/numbers": []
126
+ }
127
+ }
128
+ }
129
+ }