@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,77 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Lower Third</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
|
+
.lower {
|
|
25
|
+
position: absolute;
|
|
26
|
+
left: 5%;
|
|
27
|
+
bottom: 8%;
|
|
28
|
+
display: flex;
|
|
29
|
+
filter: drop-shadow(0 1rem 2rem #0005)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.accent {
|
|
33
|
+
width: 1rem;
|
|
34
|
+
background: #ff9818;
|
|
35
|
+
border-radius: .55rem 0 0 .55rem
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.content {
|
|
39
|
+
min-width: 48rem;
|
|
40
|
+
padding: 1.8rem 2.4rem;
|
|
41
|
+
background: #090e17f0;
|
|
42
|
+
border-radius: 0 .75rem .75rem 0
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.name {
|
|
46
|
+
font-size: 3.1rem;
|
|
47
|
+
font-weight: 850
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.role {
|
|
51
|
+
margin-top: .45rem;
|
|
52
|
+
color: #ffb14b;
|
|
53
|
+
font-size: 1.8rem;
|
|
54
|
+
font-weight: 650;
|
|
55
|
+
letter-spacing: .08em;
|
|
56
|
+
text-transform: uppercase
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
59
|
+
</head>
|
|
60
|
+
<body>
|
|
61
|
+
<section class="lower">
|
|
62
|
+
<div class="accent" id="accent"></div>
|
|
63
|
+
<div class="content">
|
|
64
|
+
<div class="name" id="name">Alex Morgan</div>
|
|
65
|
+
<div class="role" id="role">Creative Director · Nebular TV</div>
|
|
66
|
+
</div>
|
|
67
|
+
</section>
|
|
68
|
+
<script>
|
|
69
|
+
const $ = id => document.getElementById(id);
|
|
70
|
+
window.addEventListener("nebular-overlay:update", ({detail}) => {
|
|
71
|
+
if (detail.person?.name != null) $("name").textContent = detail.person.name;
|
|
72
|
+
if (detail.person?.role != null) $("role").textContent = detail.person.role;
|
|
73
|
+
if (detail.style?.accent != null) $("accent").style.backgroundColor = detail.style.accent
|
|
74
|
+
});
|
|
75
|
+
</script>
|
|
76
|
+
</body>
|
|
77
|
+
</html>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instructions": "Enter the guest or presenter name and their role or subtitle, choose an accent colour, then press Update to refresh the lower-third identification.",
|
|
3
|
+
"meta": {
|
|
4
|
+
"person/name": {"type": "string", "phase": "play", "label": "Name", "hint": "Enter name", "default": "Alex Morgan"},
|
|
5
|
+
"person/role": {"type": "string", "phase": "play", "label": "Role or subtitle", "hint": "Enter role or subtitle", "default": "Creative Director · Nebular TV"},
|
|
6
|
+
"style/accent": {"type": "color", "phase": "edit", "label": "label", "hint": "Accent colour used by the lower-third graphic", "default": "#ff9818"}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>News Ticker</title>
|
|
6
|
+
<style>
|
|
7
|
+
* { box-sizing: border-box }
|
|
8
|
+
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: transparent }
|
|
9
|
+
body { color: #fff; font-family: Inter, Arial, Helvetica, sans-serif }
|
|
10
|
+
.ticker {
|
|
11
|
+
--background: #0759a6;
|
|
12
|
+
position: absolute; left: 0; right: 0; bottom: 0; height: 8.75rem;
|
|
13
|
+
display: grid; grid-template-columns: minmax(15rem, 20%) minmax(0, 1fr) minmax(13rem, 17%);
|
|
14
|
+
overflow: hidden; background: var(--background); border-radius: .35rem; box-shadow: 0 1.1rem 3rem #0007
|
|
15
|
+
}
|
|
16
|
+
.zone { min-width: 0; position: relative; display: flex; align-items: center; overflow: hidden }
|
|
17
|
+
.topic, .brand { justify-content: center; padding: 0 1.6rem; background: #0002 }
|
|
18
|
+
.topic { border-right: 1px solid #fff5 }
|
|
19
|
+
.brand { border-left: 1px solid #fff5 }
|
|
20
|
+
.viewport { position: relative; width: 100%; height: 100%; overflow: hidden }
|
|
21
|
+
.line {
|
|
22
|
+
position: absolute; inset: 0; display: flex; align-items: center; min-width: 0;
|
|
23
|
+
transition: transform .58s cubic-bezier(.7, 0, .25, 1), opacity .58s ease;
|
|
24
|
+
will-change: transform, opacity
|
|
25
|
+
}
|
|
26
|
+
.topic .line {
|
|
27
|
+
justify-content: center; text-align: center; font-size: 2.35rem; line-height: 1.05;
|
|
28
|
+
font-weight: 900; letter-spacing: .055em; text-transform: uppercase
|
|
29
|
+
}
|
|
30
|
+
.headline .line {
|
|
31
|
+
padding: 0 2.4rem; font-size: 3.25rem; line-height: 1.08;
|
|
32
|
+
font-weight: 760; letter-spacing: -.025em
|
|
33
|
+
}
|
|
34
|
+
.brand .line {
|
|
35
|
+
justify-content: center; text-align: center; font-size: 2.55rem; line-height: 1;
|
|
36
|
+
font-weight: 950; letter-spacing: .08em; text-transform: uppercase
|
|
37
|
+
}
|
|
38
|
+
.line.waiting { transform: translateY(110%); opacity: 0 }
|
|
39
|
+
.line.leaving { transform: translateY(-110%); opacity: 0 }
|
|
40
|
+
.ticker.empty { opacity: 0 }
|
|
41
|
+
</style>
|
|
42
|
+
</head>
|
|
43
|
+
<body>
|
|
44
|
+
<main class="ticker" id="ticker">
|
|
45
|
+
<section class="zone topic"><div class="viewport" id="topicSlot">
|
|
46
|
+
<div class="line">MEN'S DOUBLES</div><div class="line waiting"></div>
|
|
47
|
+
</div></section>
|
|
48
|
+
<section class="zone headline"><div class="viewport" id="headlineSlot">
|
|
49
|
+
<div class="line">Carlos Alcaraz defeats Tommy Paul, 7–6 7–6 6–4</div><div class="line waiting"></div>
|
|
50
|
+
</div></section>
|
|
51
|
+
<section class="zone brand"><div class="viewport"><div class="line" id="brand">US OPEN</div></div></section>
|
|
52
|
+
</main>
|
|
53
|
+
<script>
|
|
54
|
+
const defaults = [
|
|
55
|
+
{topic: "MEN'S DOUBLES", tagline: "Carlos Alcaraz defeats Tommy Paul in memorable performance, 6–4 6–4 6–2"},
|
|
56
|
+
{topic: "MEN'S DOUBLES", tagline: "The quarter-final line-up is now complete in New York"},
|
|
57
|
+
{topic: "WOMEN'S SINGLES", tagline: "A dramatic final set sends the defending champion through"},
|
|
58
|
+
{topic: "AROUND THE GROUNDS", tagline: "Evening play begins under the lights at Arthur Ashe Stadium"}
|
|
59
|
+
];
|
|
60
|
+
const TRANSITION_MS = 580;
|
|
61
|
+
const ticker = document.getElementById("ticker");
|
|
62
|
+
const brand = document.getElementById("brand");
|
|
63
|
+
|
|
64
|
+
function makeSlot(id) {
|
|
65
|
+
const lines = document.getElementById(id).querySelectorAll(".line");
|
|
66
|
+
return {current: lines[0], next: lines[1], timer: null};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const topicSlot = makeSlot("topicSlot");
|
|
70
|
+
const headlineSlot = makeSlot("headlineSlot");
|
|
71
|
+
let items = defaults;
|
|
72
|
+
let currentIndex = 0;
|
|
73
|
+
let intervalSeconds = 5;
|
|
74
|
+
let cycleTimer = null;
|
|
75
|
+
|
|
76
|
+
function cleanItems(value) {
|
|
77
|
+
if (!Array.isArray(value)) return [];
|
|
78
|
+
return value.map(item => ({
|
|
79
|
+
topic: String(item?.topic ?? "").trim(),
|
|
80
|
+
tagline: String(item?.tagline ?? "").trim()
|
|
81
|
+
})).filter(item => item.topic || item.tagline);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function setSlot(slot, text) {
|
|
85
|
+
clearTimeout(slot.timer);
|
|
86
|
+
slot.current.textContent = text;
|
|
87
|
+
slot.current.className = "line";
|
|
88
|
+
slot.next.textContent = "";
|
|
89
|
+
slot.next.className = "line waiting";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function animateSlot(slot, text) {
|
|
93
|
+
clearTimeout(slot.timer);
|
|
94
|
+
slot.next.textContent = text;
|
|
95
|
+
slot.next.className = "line waiting";
|
|
96
|
+
void slot.next.offsetHeight;
|
|
97
|
+
slot.current.className = "line leaving";
|
|
98
|
+
slot.next.className = "line";
|
|
99
|
+
slot.timer = setTimeout(() => {
|
|
100
|
+
const previous = slot.current;
|
|
101
|
+
slot.current = slot.next;
|
|
102
|
+
slot.next = previous;
|
|
103
|
+
slot.next.textContent = "";
|
|
104
|
+
slot.next.className = "line waiting";
|
|
105
|
+
}, TRANSITION_MS);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function showCurrent() {
|
|
109
|
+
const item = items[currentIndex];
|
|
110
|
+
ticker.classList.toggle("empty", !item);
|
|
111
|
+
if (!item) return;
|
|
112
|
+
setSlot(topicSlot, item.topic);
|
|
113
|
+
setSlot(headlineSlot, item.tagline);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function scheduleCycle() {
|
|
117
|
+
clearTimeout(cycleTimer);
|
|
118
|
+
if (items.length > 1) cycleTimer = setTimeout(nextItem, intervalSeconds * 1000);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function nextItem() {
|
|
122
|
+
if (items.length < 2) return;
|
|
123
|
+
const previous = items[currentIndex];
|
|
124
|
+
currentIndex = (currentIndex + 1) % items.length;
|
|
125
|
+
const next = items[currentIndex];
|
|
126
|
+
animateSlot(headlineSlot, next.tagline);
|
|
127
|
+
if (next.topic !== previous.topic) animateSlot(topicSlot, next.topic);
|
|
128
|
+
scheduleCycle();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function replaceItems(value) {
|
|
132
|
+
items = cleanItems(value);
|
|
133
|
+
currentIndex = 0;
|
|
134
|
+
showCurrent();
|
|
135
|
+
scheduleCycle();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
window.addEventListener("nebular-overlay:update", ({detail}) => {
|
|
139
|
+
if (detail.content?.brand != null) brand.textContent = detail.content.brand;
|
|
140
|
+
if (detail.style?.background) ticker.style.setProperty("--background", detail.style.background);
|
|
141
|
+
if (detail.timing?.interval != null) {
|
|
142
|
+
const interval = Number(detail.timing.interval);
|
|
143
|
+
if (Number.isFinite(interval)) intervalSeconds = Math.min(30, Math.max(2, interval));
|
|
144
|
+
scheduleCycle();
|
|
145
|
+
}
|
|
146
|
+
if (detail.news != null) replaceItems(detail.news);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
showCurrent();
|
|
150
|
+
scheduleCycle();
|
|
151
|
+
</script>
|
|
152
|
+
</body>
|
|
153
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instructions": "Build the running news list in playback order. Each item remains on screen for the selected interval; its headline slides upward to the next item, while the topic changes only when the next item belongs to a different topic.",
|
|
3
|
+
"meta": {
|
|
4
|
+
"content/brand": {"type": "string", "phase": "edit", "label": "Brand text", "hint": "US OPEN", "default": "US OPEN"},
|
|
5
|
+
"news": {
|
|
6
|
+
"type": "set", "phase": "edit", "label": "News items", "hint": "Headlines shown in their playback order", "container": "big", "max": 30,
|
|
7
|
+
"default": [
|
|
8
|
+
{"topic": "MEN'S DOUBLES", "tagline": "Carlos Alcaraz defeats Tommy Paul in memorable performance, 6-4 6–4 6–2"},
|
|
9
|
+
{"topic": "MEN'S DOUBLES", "tagline": "The quarter-final line-up is now complete in New York"},
|
|
10
|
+
{"topic": "WOMEN'S SINGLES", "tagline": "A dramatic final set sends the defending champion through"},
|
|
11
|
+
{"topic": "AROUND THE GROUNDS", "tagline": "Evening play begins under the lights at Arthur Ashe Stadium"}
|
|
12
|
+
],
|
|
13
|
+
"meta": {
|
|
14
|
+
"topic": {"type": "string", "phase": "edit", "label": "News topic", "hint": "MEN'S DOUBLES"},
|
|
15
|
+
"tagline": {"type": "string", "phase": "edit", "label": "News tagline", "hint": "Enter headline"}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"timing/interval": {"type": "number", "phase": "edit", "label": "News interval", "hint": "Seconds between headlines", "default": 5, "min": 2, "max": 30},
|
|
19
|
+
"style/background": {"type": "color", "phase": "edit", "label": "Background", "hint": "Background colour used by the ticker", "default": "#0759a6"}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>News Ticker</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
|
+
--hue: 0deg;
|
|
21
|
+
color: #000;
|
|
22
|
+
background-image: url(background-label-green.png);
|
|
23
|
+
font-family: Inter, Arial, Helvetica, sans-serif;
|
|
24
|
+
background-size: 100% 100%;
|
|
25
|
+
backdrop-filter: hue-rotate(var(--hue));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.ticker {
|
|
29
|
+
background-image: url(banner.png);
|
|
30
|
+
position: absolute;
|
|
31
|
+
left: 8%;
|
|
32
|
+
right: 8%;
|
|
33
|
+
bottom: 8%;
|
|
34
|
+
height: 8.75rem;
|
|
35
|
+
display: flex;
|
|
36
|
+
grid-template-columns: minmax(15rem, 20%) minmax(0, 1fr) minmax(13rem, 17%);
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
border-radius: .35rem;
|
|
39
|
+
box-shadow: 0 1.1rem 3rem #0007;
|
|
40
|
+
background-size: 100% 100%;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.zone {
|
|
44
|
+
min-width: 0;
|
|
45
|
+
position: relative;
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
overflow: hidden
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.topic {
|
|
52
|
+
justify-content: center;
|
|
53
|
+
padding: 0 1.6rem;
|
|
54
|
+
background: #0002
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.topic {
|
|
58
|
+
border-right: 1px solid #fff5;
|
|
59
|
+
width: 17rem;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.viewport {
|
|
63
|
+
position: relative;
|
|
64
|
+
width: 100%;
|
|
65
|
+
height: 100%;
|
|
66
|
+
overflow: hidden
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.line {
|
|
70
|
+
position: absolute;
|
|
71
|
+
inset: 0;
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
min-width: 0;
|
|
75
|
+
transition: transform .58s cubic-bezier(.7, 0, .25, 1), opacity .58s ease;
|
|
76
|
+
will-change: transform, opacity
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
section.zone.headline {
|
|
80
|
+
flex-grow: 1;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.topic .line {
|
|
84
|
+
justify-content: center;
|
|
85
|
+
text-align: center;
|
|
86
|
+
font-size: 2.35rem;
|
|
87
|
+
line-height: 1.05;
|
|
88
|
+
font-weight: 900;
|
|
89
|
+
letter-spacing: .055em;
|
|
90
|
+
text-transform: uppercase
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.headline .line {
|
|
94
|
+
padding: 0 2.4rem;
|
|
95
|
+
font-size: 2.75rem;
|
|
96
|
+
line-height: 1.08;
|
|
97
|
+
font-weight: 760;
|
|
98
|
+
letter-spacing: -.025em;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.line.waiting {
|
|
102
|
+
transform: translateY(110%);
|
|
103
|
+
opacity: 0
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.line.leaving {
|
|
107
|
+
transform: translateY(-110%);
|
|
108
|
+
opacity: 0
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.ticker.empty {
|
|
112
|
+
opacity: 0
|
|
113
|
+
}
|
|
114
|
+
</style>
|
|
115
|
+
</head>
|
|
116
|
+
<body>
|
|
117
|
+
<main class="ticker" id="ticker">
|
|
118
|
+
<section class="zone topic">
|
|
119
|
+
<div class="viewport" id="topicSlot">
|
|
120
|
+
<div class="line">MEN'S DOUBLES</div>
|
|
121
|
+
<div class="line waiting"></div>
|
|
122
|
+
</div>
|
|
123
|
+
</section>
|
|
124
|
+
<section class="zone headline">
|
|
125
|
+
<div class="viewport" id="headlineSlot">
|
|
126
|
+
<div class="line">Carlos Alcaraz defeats Tommy Paul, 7–6 7–6 6–4</div>
|
|
127
|
+
<div class="line waiting"></div>
|
|
128
|
+
</div>
|
|
129
|
+
</section>
|
|
130
|
+
</main>
|
|
131
|
+
<script>
|
|
132
|
+
const defaults = [
|
|
133
|
+
{topic: "MEN'S DOUBLES", tagline: "Carlos Alcaraz defeats Tommy Paul in memorable performance, 6–4 6–4 6–2"},
|
|
134
|
+
{topic: "MEN'S DOUBLES", tagline: "The quarter-final line-up is now complete in New York"},
|
|
135
|
+
{topic: "WOMEN'S SINGLES", tagline: "A dramatic final set sends the defending champion through"},
|
|
136
|
+
{topic: "AROUND THE GROUNDS", tagline: "Evening play begins under the lights at Arthur Ashe Stadium"}
|
|
137
|
+
];
|
|
138
|
+
const TRANSITION_MS = 580;
|
|
139
|
+
const ticker = document.getElementById("ticker");
|
|
140
|
+
|
|
141
|
+
function makeSlot(id) {
|
|
142
|
+
const lines = document.getElementById(id).querySelectorAll(".line");
|
|
143
|
+
return {current: lines[0], next: lines[1], timer: null};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const topicSlot = makeSlot("topicSlot");
|
|
147
|
+
const headlineSlot = makeSlot("headlineSlot");
|
|
148
|
+
let items = defaults;
|
|
149
|
+
let currentIndex = 0;
|
|
150
|
+
let intervalSeconds = 5;
|
|
151
|
+
let cycleTimer = null;
|
|
152
|
+
|
|
153
|
+
function cleanItems(value) {
|
|
154
|
+
if (!Array.isArray(value)) return [];
|
|
155
|
+
return value.map(item => ({
|
|
156
|
+
topic: String(item?.topic ?? "").trim(),
|
|
157
|
+
tagline: String(item?.tagline ?? "").trim()
|
|
158
|
+
})).filter(item => item.topic || item.tagline);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function setSlot(slot, text) {
|
|
162
|
+
clearTimeout(slot.timer);
|
|
163
|
+
slot.current.textContent = text;
|
|
164
|
+
slot.current.className = "line";
|
|
165
|
+
slot.next.textContent = "";
|
|
166
|
+
slot.next.className = "line waiting";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function animateSlot(slot, text) {
|
|
170
|
+
clearTimeout(slot.timer);
|
|
171
|
+
slot.next.textContent = text;
|
|
172
|
+
slot.next.className = "line waiting";
|
|
173
|
+
void slot.next.offsetHeight;
|
|
174
|
+
slot.current.className = "line leaving";
|
|
175
|
+
slot.next.className = "line";
|
|
176
|
+
slot.timer = setTimeout(() => {
|
|
177
|
+
const previous = slot.current;
|
|
178
|
+
slot.current = slot.next;
|
|
179
|
+
slot.next = previous;
|
|
180
|
+
slot.next.textContent = "";
|
|
181
|
+
slot.next.className = "line waiting";
|
|
182
|
+
}, TRANSITION_MS);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function showCurrent() {
|
|
186
|
+
const item = items[currentIndex];
|
|
187
|
+
ticker.classList.toggle("empty", !item);
|
|
188
|
+
if (!item) return;
|
|
189
|
+
setSlot(topicSlot, item.topic);
|
|
190
|
+
setSlot(headlineSlot, item.tagline);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function scheduleCycle() {
|
|
194
|
+
clearTimeout(cycleTimer);
|
|
195
|
+
if (items.length > 1) cycleTimer = setTimeout(nextItem, intervalSeconds * 1000);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function nextItem() {
|
|
199
|
+
if (items.length < 2) return;
|
|
200
|
+
const previous = items[currentIndex];
|
|
201
|
+
currentIndex = (currentIndex + 1) % items.length;
|
|
202
|
+
const next = items[currentIndex];
|
|
203
|
+
animateSlot(headlineSlot, next.tagline);
|
|
204
|
+
if (next.topic !== previous.topic) animateSlot(topicSlot, next.topic);
|
|
205
|
+
scheduleCycle();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function replaceItems(value) {
|
|
209
|
+
items = cleanItems(value);
|
|
210
|
+
currentIndex = 0;
|
|
211
|
+
showCurrent();
|
|
212
|
+
scheduleCycle();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
window.addEventListener("nebular-overlay:update", ({detail}) => {
|
|
216
|
+
if (detail.timing?.interval != null) {
|
|
217
|
+
const interval = Number(detail.timing.interval);
|
|
218
|
+
if (Number.isFinite(interval)) intervalSeconds = Math.min(30, Math.max(2, interval));
|
|
219
|
+
scheduleCycle();
|
|
220
|
+
}
|
|
221
|
+
if (detail.news != null) replaceItems(detail.news);
|
|
222
|
+
if (detail.style?.hue > 0) document.body.style.setProperty("--hue", detail.style.hue + "deg");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
showCurrent();
|
|
226
|
+
scheduleCycle();
|
|
227
|
+
</script>
|
|
228
|
+
</body>
|
|
229
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"instructions": "Build the running news list in playback order. Each item remains on screen for the selected interval; its headline slides upward to the next item, while the topic changes only when the next item belongs to a different topic.",
|
|
3
|
+
"meta": {
|
|
4
|
+
"news": {
|
|
5
|
+
"type": "set", "phase": "edit", "label": "News items", "hint": "Headlines shown in their playback order", "container": "big", "max": 30,
|
|
6
|
+
"default": [
|
|
7
|
+
{"topic": "MEN'S DOUBLES", "tagline": "Carlos Alcaraz defeats Tommy Paul in memorable performance, 6-4 6–4 6–2"},
|
|
8
|
+
{"topic": "MEN'S DOUBLES", "tagline": "The quarter-final line-up is now complete in New York"},
|
|
9
|
+
{"topic": "WOMEN'S SINGLES", "tagline": "A dramatic final set sends the defending champion through"},
|
|
10
|
+
{"topic": "AROUND THE GROUNDS", "tagline": "Evening play begins under the lights at Arthur Ashe Stadium"}
|
|
11
|
+
],
|
|
12
|
+
"meta": {
|
|
13
|
+
"topic": {"type": "string", "phase": "edit", "label": "News topic", "hint": "MEN'S DOUBLES"},
|
|
14
|
+
"tagline": {"type": "string", "phase": "edit", "label": "News tagline", "hint": "Enter headline"}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"timing/interval": {"type": "number", "phase": "edit", "label": "News interval", "hint": "Seconds between headlines", "default": 5, "min": 2, "max": 30},
|
|
18
|
+
"style/hue": {"type": "range", "phase": "edit", "label": "Color Theme", "min": 0, "max": 360, "hint": "General theme color used in the framer", "default": 0}
|
|
19
|
+
}
|
|
20
|
+
}
|