overlaify 0.0.0-stage → 0.1.2
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/LICENSE +21 -0
- package/README.md +123 -2
- package/assets/overlay-spec.md +196 -0
- package/assets/sdk/v1/overlaify.js +437 -0
- package/assets/templates/be-right-back.html +123 -0
- package/assets/templates/catalog.json +348 -0
- package/assets/templates/chat-stack.html +68 -0
- package/assets/templates/death-counter.html +94 -0
- package/assets/templates/emote-wall.html +125 -0
- package/assets/templates/goal-bar.html +89 -0
- package/assets/templates/hug-command.html +151 -0
- package/assets/templates/neon-alert.html +84 -0
- package/assets/templates/newsroom-ticker.html +136 -0
- package/assets/templates/starting-soon.html +145 -0
- package/assets/templates/subathon-timer.html +127 -0
- package/assets/templates/webcam-frame.html +93 -0
- package/bin/overlaify.js +114 -0
- package/lib/commands.js +67 -0
- package/lib/dev.html +328 -0
- package/lib/dev.js +181 -0
- package/lib/publish.js +272 -0
- package/lib/starter.html +51 -0
- package/lib/validate.js +362 -0
- package/lib/ws.js +79 -0
- package/package.json +35 -4
- package/skill/SKILL.md +88 -0
- package/skill/examples/chat-stack.html +68 -0
- package/skill/examples/goal-bar.html +89 -0
- package/skill/examples/neon-alert.html +84 -0
- package/skill/references/overlay-spec.md +196 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"slug": "neon-alert",
|
|
4
|
+
"official": true,
|
|
5
|
+
"author": "Dev User (google)",
|
|
6
|
+
"title": "Neon Pulse Alerts",
|
|
7
|
+
"kind": "Alert box",
|
|
8
|
+
"tier": "free",
|
|
9
|
+
"desc": "Glowing pop-in alerts for follows, subs, gifts, bits and raids. Accent color via ?color=.",
|
|
10
|
+
"seoTitle": "Neon Pulse Alerts — Free Neon Twitch Alert Box for OBS",
|
|
11
|
+
"metaDesc": "Free neon Twitch alert box for OBS: glowing pop-in alerts for subs, resubs, gifted subs, bits and raids. Pick any accent color, paste one URL, go live.",
|
|
12
|
+
"about": [
|
|
13
|
+
"Neon Pulse is a free Twitch alert box that drops a glowing, sign-style card into the top of your scene whenever someone subscribes, gifts subs, cheers bits or raids your channel. The viewer's name shimmers across the card while a pulsing ring keeps it readable over busy gameplay.",
|
|
14
|
+
"Every alert type gets its own wording: resubs show the month streak and tier, gift bombs show the number of subs dropped, and cheers show the bits total plus the viewer's message. Alerts queue up so a raid followed by a wave of subs never overlaps."
|
|
15
|
+
],
|
|
16
|
+
"features": [
|
|
17
|
+
"Sub, resub, gifted sub, gift bomb, bits and raid alerts",
|
|
18
|
+
"Shows resub messages and cheer messages",
|
|
19
|
+
"Accent color and on-screen duration set from the URL",
|
|
20
|
+
"Alerts queue instead of overlapping",
|
|
21
|
+
"Built-in sounds with volume and mute controls",
|
|
22
|
+
"Transparent 1920 × 1080 browser source"
|
|
23
|
+
],
|
|
24
|
+
"listedAt": "2026-10-28T14:00:41.475Z",
|
|
25
|
+
"style": "Neon",
|
|
26
|
+
"accent": "#c6ff3d",
|
|
27
|
+
"crop": [
|
|
28
|
+
480,
|
|
29
|
+
0,
|
|
30
|
+
960
|
|
31
|
+
],
|
|
32
|
+
"preview": "/assets/previews/neon-alert.png",
|
|
33
|
+
"clip": true
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"slug": "chat-stack",
|
|
37
|
+
"official": true,
|
|
38
|
+
"author": "Dev User (google)",
|
|
39
|
+
"title": "Glass Chat Stack",
|
|
40
|
+
"kind": "Chat",
|
|
41
|
+
"tier": "free",
|
|
42
|
+
"desc": "Frosted-glass chat with Twitch emotes, badges and auto-fade. Hides bots and !commands.",
|
|
43
|
+
"seoTitle": "Glass Chat Stack — Free Twitch Chat Overlay for OBS",
|
|
44
|
+
"metaDesc": "Free frosted-glass Twitch chat overlay for OBS with emotes, mod/sub/VIP badges and auto-fade. Hides bots and !commands. One browser source URL, no login.",
|
|
45
|
+
"about": [
|
|
46
|
+
"Glass Chat Stack puts your Twitch chat on stream as a column of frosted-glass bubbles in the bottom-left corner. Each message is tinted with the chatter's own Twitch name color, shows mod, sub, VIP and broadcaster badges, and renders Twitch emotes inline.",
|
|
47
|
+
"It is built to stay out of the way: old messages fade after 30 seconds, only the newest few are kept on screen, and messages from Nightbot, StreamElements, Streamlabs or anything starting with ! are hidden so command spam never reaches your viewers."
|
|
48
|
+
],
|
|
49
|
+
"features": [
|
|
50
|
+
"Twitch emotes rendered inline",
|
|
51
|
+
"Mod, sub, VIP and broadcaster badges",
|
|
52
|
+
"Chatter name colors from Twitch",
|
|
53
|
+
"Auto-fade with adjustable timing",
|
|
54
|
+
"Hides bots and !commands",
|
|
55
|
+
"Highlights bits sent with a message"
|
|
56
|
+
],
|
|
57
|
+
"listedAt": "2026-10-28T14:00:41.511Z",
|
|
58
|
+
"style": "Frosted glass",
|
|
59
|
+
"accent": "#9fd8ff",
|
|
60
|
+
"crop": [
|
|
61
|
+
0,
|
|
62
|
+
420,
|
|
63
|
+
1100
|
|
64
|
+
],
|
|
65
|
+
"preview": "/assets/previews/chat-stack.png",
|
|
66
|
+
"clip": true
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"slug": "goal-bar",
|
|
70
|
+
"official": true,
|
|
71
|
+
"author": "Dev User (google)",
|
|
72
|
+
"title": "Liquid Goal Bar",
|
|
73
|
+
"kind": "Goal",
|
|
74
|
+
"tier": "free",
|
|
75
|
+
"desc": "Animated sub / bits goal bar that remembers progress. ?type=bits&goal=5000",
|
|
76
|
+
"seoTitle": "Liquid Goal Bar — Free Twitch Sub & Bits Goal Overlay for OBS",
|
|
77
|
+
"metaDesc": "Free animated Twitch goal bar for OBS. Track a sub goal or bits goal live, with a flowing liquid fill that remembers progress between streams. Fully set from the URL.",
|
|
78
|
+
"about": [
|
|
79
|
+
"Liquid Goal Bar is a free Twitch goal overlay that fills up live as your community subs or cheers. A striped, flowing gradient slides across the bar with every contribution, and the whole bar turns lime green with a fanfare when you hit the goal.",
|
|
80
|
+
"Progress is saved in the OBS browser source, so the bar picks up where it left off next stream. Choose a sub goal or a bits goal, set the target, starting value, label and color right in the URL, and add as many bars as you like for different goals."
|
|
81
|
+
],
|
|
82
|
+
"features": [
|
|
83
|
+
"Sub goal or bits goal",
|
|
84
|
+
"Counts subs, gifted subs and gift bombs",
|
|
85
|
+
"Remembers progress between streams",
|
|
86
|
+
"Custom label, target, starting value and color",
|
|
87
|
+
"Celebration state and sound when the goal is reached",
|
|
88
|
+
"Transparent background, sits at the top of the scene"
|
|
89
|
+
],
|
|
90
|
+
"listedAt": "2026-10-28T14:00:41.530Z",
|
|
91
|
+
"style": "Liquid fill",
|
|
92
|
+
"accent": "#ff4f8b",
|
|
93
|
+
"crop": [
|
|
94
|
+
400,
|
|
95
|
+
0,
|
|
96
|
+
1120
|
|
97
|
+
],
|
|
98
|
+
"preview": "/assets/previews/goal-bar.png",
|
|
99
|
+
"clip": true
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"slug": "starting-soon",
|
|
103
|
+
"official": true,
|
|
104
|
+
"author": "Dev User (google)",
|
|
105
|
+
"title": "Starting Soon Countdown",
|
|
106
|
+
"kind": "Scene",
|
|
107
|
+
"tier": "free",
|
|
108
|
+
"desc": "Full-screen starting soon screen with a live countdown timer and chat while viewers wait. ?minutes=5",
|
|
109
|
+
"seoTitle": "Starting Soon Screen — Free Twitch Countdown Overlay for OBS",
|
|
110
|
+
"metaDesc": "Free animated starting soon screen for Twitch and OBS with a live countdown timer, your own headline and a chat feed so viewers can say hi before you go live.",
|
|
111
|
+
"about": [
|
|
112
|
+
"Starting Soon Countdown is a free, full-screen starting soon scene for Twitch. Drifting neon light, a big headline and a live countdown timer tell early viewers exactly when the stream starts, and when the timer hits zero it switches to a pulsing “going live any second” message.",
|
|
113
|
+
"Set the countdown in minutes or to a clock time like 19:30, and change the headline, the line underneath and the accent color right in the URL. A side panel shows the latest chat messages, new subs, cheers and raids, so the waiting room already feels alive before you switch scenes."
|
|
114
|
+
],
|
|
115
|
+
"features": [
|
|
116
|
+
"Live countdown in minutes or to a clock time",
|
|
117
|
+
"Custom headline, message and accent color",
|
|
118
|
+
"Live chat feed while viewers wait",
|
|
119
|
+
"Highlights subs, gifted subs, bits and raids",
|
|
120
|
+
"Animated background, or transparent with bg=0",
|
|
121
|
+
"Full 1920 × 1080 scene, no image files needed"
|
|
122
|
+
],
|
|
123
|
+
"listedAt": "2026-10-28T14:00:41.611Z",
|
|
124
|
+
"style": "Neon gradient",
|
|
125
|
+
"accent": "#b48bff",
|
|
126
|
+
"preview": "/assets/previews/starting-soon.png",
|
|
127
|
+
"clip": true
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"slug": "be-right-back",
|
|
131
|
+
"official": true,
|
|
132
|
+
"author": "Dev User (google)",
|
|
133
|
+
"title": "Cozy BRB Screen",
|
|
134
|
+
"kind": "Scene",
|
|
135
|
+
"tier": "free",
|
|
136
|
+
"desc": "Warm “be right back” scene with a steaming mug, away timer and scrolling chat ticker.",
|
|
137
|
+
"seoTitle": "Be Right Back Screen — Free Animated BRB Overlay for Twitch & OBS",
|
|
138
|
+
"metaDesc": "Free animated BRB screen for Twitch and OBS: a cozy be right back scene with an away timer or countdown, steaming mug animation and a live chat ticker.",
|
|
139
|
+
"about": [
|
|
140
|
+
"Cozy BRB Screen is a free be right back scene for Twitch streams. Warm lamplight, floating dust and a steaming mug keep the screen moving while you step away, and a big serif headline makes it obvious you will be back soon.",
|
|
141
|
+
"By default the timer counts how long you have been away; add a number of minutes and it becomes a “back in” countdown instead. A chat ticker along the bottom keeps the latest messages, subs, cheers and raids on screen, so your community can keep chatting during the break."
|
|
142
|
+
],
|
|
143
|
+
"features": [
|
|
144
|
+
"Away timer or “back in” countdown",
|
|
145
|
+
"Live chat ticker along the bottom",
|
|
146
|
+
"Highlights subs, gifted subs, bits and raids",
|
|
147
|
+
"Custom headline, message and accent color",
|
|
148
|
+
"Animated background, or transparent with bg=0",
|
|
149
|
+
"Full 1920 × 1080 scene, no image files needed"
|
|
150
|
+
],
|
|
151
|
+
"listedAt": "2026-10-28T14:00:41.628Z",
|
|
152
|
+
"style": "Coffee break",
|
|
153
|
+
"accent": "#ffb547",
|
|
154
|
+
"preview": "/assets/previews/be-right-back.png",
|
|
155
|
+
"clip": true
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"slug": "webcam-frame",
|
|
159
|
+
"official": true,
|
|
160
|
+
"author": "Dev User (google)",
|
|
161
|
+
"title": "Neon Webcam Frame",
|
|
162
|
+
"kind": "Webcam frame",
|
|
163
|
+
"tier": "free",
|
|
164
|
+
"desc": "Animated gradient facecam border with a name tag that flashes on subs, bits and raids.",
|
|
165
|
+
"seoTitle": "Neon Webcam Frame — Free Animated Facecam Border for OBS",
|
|
166
|
+
"metaDesc": "Free animated webcam frame for Twitch and OBS: a glowing gradient facecam border with a name tag that flashes on subs, bits and raids. Size and position from the URL.",
|
|
167
|
+
"about": [
|
|
168
|
+
"Neon Webcam Frame is a free webcam overlay that wraps your facecam in a slowly rotating neon gradient border with rounded corners and a live name tag. The inside is fully transparent, so you place it over your camera source and it just works.",
|
|
169
|
+
"Set the exact position and size in pixels so it lines up with your camera, pick two colors for the gradient, and change the corner radius and border thickness. When someone subscribes, gifts, cheers or raids, the border speeds up and glows while a small label pops up with their name."
|
|
170
|
+
],
|
|
171
|
+
"features": [
|
|
172
|
+
"Rotating two-color gradient border",
|
|
173
|
+
"Name tag with a live dot",
|
|
174
|
+
"Flashes and shows a label on subs, bits and raids",
|
|
175
|
+
"Position, size, radius and border width from the URL",
|
|
176
|
+
"Transparent inside for your camera",
|
|
177
|
+
"Works with any webcam or capture card"
|
|
178
|
+
],
|
|
179
|
+
"listedAt": "2026-10-28T14:00:41.645Z",
|
|
180
|
+
"style": "Neon",
|
|
181
|
+
"accent": "#c6ff3d",
|
|
182
|
+
"crop": [
|
|
183
|
+
1080,
|
|
184
|
+
560,
|
|
185
|
+
860
|
|
186
|
+
],
|
|
187
|
+
"preview": "/assets/previews/webcam-frame.png",
|
|
188
|
+
"clip": true
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"slug": "subathon-timer",
|
|
192
|
+
"official": true,
|
|
193
|
+
"author": "Dev User (google)",
|
|
194
|
+
"title": "Subathon Timer",
|
|
195
|
+
"kind": "Timer",
|
|
196
|
+
"tier": "free",
|
|
197
|
+
"desc": "Countdown that grows with every sub and cheer. Remembers time between restarts. ?hours=4&sub=300",
|
|
198
|
+
"seoTitle": "Subathon Timer — Free Twitch Subathon Overlay for OBS",
|
|
199
|
+
"metaDesc": "Free Twitch subathon timer for OBS: subs, gifted subs and bits add time automatically, mods can add or pause from chat, and the timer survives OBS restarts.",
|
|
200
|
+
"about": [
|
|
201
|
+
"Subathon Timer is a free subathon overlay for Twitch. Set a starting length, and every new sub, resub, gifted sub and cheer adds time to the clock automatically, with a floating “+5:00” and the supporter’s name each time. Tier 2 subs count double and tier 3 subs count five times.",
|
|
202
|
+
"The end time is saved in the OBS browser source, so the timer keeps running correctly through crashes, scene switches and PC restarts. Mods and the broadcaster can add or remove minutes and pause or resume the timer from chat, and an optional cap keeps a subathon from running forever."
|
|
203
|
+
],
|
|
204
|
+
"features": [
|
|
205
|
+
"Subs, gifted subs and bits add time automatically",
|
|
206
|
+
"Tier 2 and tier 3 subs add more time",
|
|
207
|
+
"Mod chat commands: add, remove, pause, resume",
|
|
208
|
+
"Survives OBS restarts and crashes",
|
|
209
|
+
"Optional maximum length cap",
|
|
210
|
+
"Floating “+time” with the supporter’s name"
|
|
211
|
+
],
|
|
212
|
+
"listedAt": "2026-10-28T14:00:41.663Z",
|
|
213
|
+
"style": "Clean HUD",
|
|
214
|
+
"accent": "#5ce1ff",
|
|
215
|
+
"crop": [
|
|
216
|
+
1200,
|
|
217
|
+
0,
|
|
218
|
+
720
|
|
219
|
+
],
|
|
220
|
+
"preview": "/assets/previews/subathon-timer.png",
|
|
221
|
+
"clip": true
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"slug": "emote-wall",
|
|
225
|
+
"official": true,
|
|
226
|
+
"author": "Dev User (google)",
|
|
227
|
+
"title": "Emote Wall",
|
|
228
|
+
"kind": "Chat effect",
|
|
229
|
+
"tier": "free",
|
|
230
|
+
"desc": "Every emote your chat sends floats, rains or bounces across the screen, with combo banners.",
|
|
231
|
+
"seoTitle": "Emote Wall — Free Twitch Emote Rain & Bounce Overlay for OBS",
|
|
232
|
+
"metaDesc": "Free Twitch emote wall for OBS: emotes and emoji from chat float up, rain down or bounce across your stream, with a combo banner when chat spams the same emote.",
|
|
233
|
+
"about": [
|
|
234
|
+
"Emote Wall is a free Twitch emote overlay that turns your chat into a light show. Every Twitch emote and emoji in a chat message pops onto the screen and floats up, rains down or bounces around DVD-logo style, so hype moments look as loud as they feel.",
|
|
235
|
+
"When chat sends the same emote several messages in a row, a combo banner appears with a running count and a burst of extra emotes. Choose the motion, emote size, how many can be on screen and how many each message can spawn, so it stays fun without covering your gameplay."
|
|
236
|
+
],
|
|
237
|
+
"features": [
|
|
238
|
+
"Twitch emotes and emoji from chat",
|
|
239
|
+
"Three motions: rise, rain and bounce",
|
|
240
|
+
"Combo banner when chat spams one emote",
|
|
241
|
+
"Limits per message and on screen",
|
|
242
|
+
"Adjustable emote size and lifetime",
|
|
243
|
+
"Transparent full-screen browser source"
|
|
244
|
+
],
|
|
245
|
+
"listedAt": "2026-10-28T14:00:41.680Z",
|
|
246
|
+
"style": "Emote chaos",
|
|
247
|
+
"accent": "#ff6b4a",
|
|
248
|
+
"preview": "/assets/previews/emote-wall.png",
|
|
249
|
+
"clip": true
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"slug": "death-counter",
|
|
253
|
+
"official": true,
|
|
254
|
+
"author": "Dev User (google)",
|
|
255
|
+
"title": "Death Counter",
|
|
256
|
+
"kind": "Counter",
|
|
257
|
+
"tier": "free",
|
|
258
|
+
"desc": "On-screen death counter your mods control from chat: !death, !death-, !death set 12.",
|
|
259
|
+
"seoTitle": "Death Counter — Free Twitch Death Counter Overlay for OBS",
|
|
260
|
+
"metaDesc": "Free Twitch death counter for OBS. Mods type !death to add one, !death- to undo or !death set 12. Saves the count between streams. Rename it to count anything.",
|
|
261
|
+
"about": [
|
|
262
|
+
"Death Counter is a free Twitch death counter overlay for souls-likes, roguelikes, horror games and any run where dying is part of the show. Each time a mod or the broadcaster types !death in chat, the count goes up with a shake and a low thud, right in the corner of your stream.",
|
|
263
|
+
"The count is saved in the OBS browser source, so it carries over between streams until you reset it. Rename the command and the label to track anything else — wipes, rage quits, jump scares or wins — and add several counters with different commands side by side."
|
|
264
|
+
],
|
|
265
|
+
"features": [
|
|
266
|
+
"Chat commands: !death, !death-, !death set, !death reset",
|
|
267
|
+
"Only mods and the broadcaster by default",
|
|
268
|
+
"Saves the count between streams",
|
|
269
|
+
"Rename the command, label and icon to count anything",
|
|
270
|
+
"Shake animation and sound on every death",
|
|
271
|
+
"Run several counters at once"
|
|
272
|
+
],
|
|
273
|
+
"listedAt": "2026-10-28T14:00:41.699Z",
|
|
274
|
+
"style": "Gamer HUD",
|
|
275
|
+
"accent": "#ff4f8b",
|
|
276
|
+
"crop": [
|
|
277
|
+
0,
|
|
278
|
+
0,
|
|
279
|
+
640
|
|
280
|
+
],
|
|
281
|
+
"preview": "/assets/previews/death-counter.png",
|
|
282
|
+
"clip": true
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"slug": "newsroom-ticker",
|
|
286
|
+
"official": true,
|
|
287
|
+
"author": "Dev User (google)",
|
|
288
|
+
"title": "Breaking News Ticker",
|
|
289
|
+
"kind": "Ticker",
|
|
290
|
+
"tier": "free",
|
|
291
|
+
"desc": "A slim BREAKING news rail along the bottom scrolls your latest subs, gifts, bits and raids.",
|
|
292
|
+
"seoTitle": "Breaking News Ticker — Free Twitch Event Ticker Overlay for OBS",
|
|
293
|
+
"metaDesc": "Free Twitch event ticker for OBS: a BREAKING news rail scrolls your latest subs, resubs, gifted subs, bits and raids along the bottom, with a flash banner for new events.",
|
|
294
|
+
"about": [
|
|
295
|
+
"Breaking News Ticker is a free Twitch event ticker that runs along the bottom of your stream like a 24-hour news channel. A red “BREAKING:” tag leads a scrolling rail of your latest subs, resubs, gifted subs, bits and raids, written as headlines.",
|
|
296
|
+
"Fresh events flash a red banner above the rail and jump to the front of the list. It keeps the last ten stories by default, so new viewers can see who supported the stream — a nice thank-you for your chat."
|
|
297
|
+
],
|
|
298
|
+
"features": [
|
|
299
|
+
"Scrolling rail of recent subs, gifts, bits and raids",
|
|
300
|
+
"Newsroom-style headlines for each event",
|
|
301
|
+
"Flash banner when news breaks",
|
|
302
|
+
"Seamless loop with no gaps",
|
|
303
|
+
"Adjustable speed and story count"
|
|
304
|
+
],
|
|
305
|
+
"listedAt": "2026-10-28T14:00:41.902Z",
|
|
306
|
+
"style": "Breaking news",
|
|
307
|
+
"accent": "#ff5c66",
|
|
308
|
+
"crop": [
|
|
309
|
+
0,
|
|
310
|
+
596,
|
|
311
|
+
860
|
|
312
|
+
],
|
|
313
|
+
"preview": "/assets/previews/newsroom-ticker.png",
|
|
314
|
+
"clip": true
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
"slug": "hug-command",
|
|
318
|
+
"official": true,
|
|
319
|
+
"author": "Dev User (google)",
|
|
320
|
+
"title": "!hug Blob Hugs",
|
|
321
|
+
"kind": "Chat command",
|
|
322
|
+
"tier": "free",
|
|
323
|
+
"desc": "When a chatter types !hug @name, two cute blobs in their colors slide in and hug with both names above them.",
|
|
324
|
+
"seoTitle": "!hug Command — Free Twitch Hug Overlay for OBS",
|
|
325
|
+
"metaDesc": "Free Twitch !hug command overlay: when a viewer types !hug @name, two cute blobs in their name colors hug on stream with hearts floating up. No bot or login needed.",
|
|
326
|
+
"about": [
|
|
327
|
+
"!hug Blob Hugs is a free Twitch hug command overlay. When a viewer types !hug @name in chat, two round blobs in each person's Twitch name color slide in, squeeze each other and float hearts up, with both names shown above them.",
|
|
328
|
+
"A bare !hug hugs the whole chat. Add a per-chatter cooldown if your community gets too affectionate, or rename the command — !pat, !boop, !highfive — to make it your own."
|
|
329
|
+
],
|
|
330
|
+
"features": [
|
|
331
|
+
"!hug @name shows two blobs hugging",
|
|
332
|
+
"Blobs in each viewer's name color",
|
|
333
|
+
"Plain !hug hugs the whole chat",
|
|
334
|
+
"Optional per-chatter cooldown",
|
|
335
|
+
"Rename the command"
|
|
336
|
+
],
|
|
337
|
+
"listedAt": "2026-10-28T14:00:41.954Z",
|
|
338
|
+
"style": "Cute",
|
|
339
|
+
"accent": "#ff7eb6",
|
|
340
|
+
"crop": [
|
|
341
|
+
440,
|
|
342
|
+
480,
|
|
343
|
+
1040
|
|
344
|
+
],
|
|
345
|
+
"preview": "/assets/previews/hug-command.png",
|
|
346
|
+
"clip": true
|
|
347
|
+
}
|
|
348
|
+
]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<script src="https://www.overlaify.com/sdk/v1/overlaify.js"></script>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>Glass Chat Stack</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;600;800&display=swap" rel="stylesheet">
|
|
9
|
+
<script type="application/json" id="overlaify-manifest">
|
|
10
|
+
{
|
|
11
|
+
"spec": 1,
|
|
12
|
+
"settings": [
|
|
13
|
+
{"key": "max", "label": "Messages on screen", "type": "range", "default": 7, "min": 1, "max": 20, "step": 1, "desc": "Maximum number of messages on screen."},
|
|
14
|
+
{"key": "fade", "label": "Fade after", "type": "range", "default": 30, "min": 0, "max": 300, "step": 5, "unit": "s", "desc": "Seconds before a message fades out. 0 keeps messages until they are pushed off."},
|
|
15
|
+
{"key": "hidebots", "label": "Hide users", "type": "text", "default": "nightbot,streamelements,streamlabs", "desc": "Comma-separated usernames (lowercase, no spaces) whose messages are hidden."}
|
|
16
|
+
],
|
|
17
|
+
"actions": [
|
|
18
|
+
{"name": "clear", "label": "Clear chat", "style": "danger", "wide": true}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
<style>
|
|
23
|
+
html, body { margin: 0; width: 1920px; height: 1080px; overflow: hidden; background: transparent; }
|
|
24
|
+
#chat { position: absolute; left: 40px; bottom: 60px; width: 520px; display: flex; flex-direction: column; gap: 12px; }
|
|
25
|
+
.msg {
|
|
26
|
+
font-family: 'Outfit', system-ui, sans-serif; font-size: 26px; line-height: 1.35; color: #f4f1ff;
|
|
27
|
+
background: rgba(16, 13, 26, .72); backdrop-filter: blur(8px);
|
|
28
|
+
border-left: 5px solid var(--uc, #9b5cff); border-radius: 6px 16px 16px 6px;
|
|
29
|
+
padding: 12px 18px 14px; box-shadow: 0 8px 24px rgba(0,0,0,.35);
|
|
30
|
+
text-shadow: 0 1px 2px rgba(0,0,0,.6);
|
|
31
|
+
transform-origin: left bottom; animation: slide .35s cubic-bezier(.2,1.2,.4,1);
|
|
32
|
+
transition: opacity .6s, transform .6s;
|
|
33
|
+
}
|
|
34
|
+
.msg.gone { opacity: 0; transform: translateX(-40px); }
|
|
35
|
+
.who { font-weight: 800; color: var(--uc, #9b5cff); margin-right: 8px; }
|
|
36
|
+
.badge { display: inline-block; font-size: 15px; font-weight: 600; padding: 1px 8px; border-radius: 99px; margin-right: 6px; vertical-align: 3px; color: #0b0a10; }
|
|
37
|
+
.b-mod { background: #4dffa6; } .b-sub { background: #ffb547; } .b-vip { background: #ff4f8b; } .b-broadcaster { background: #ff6b4a; }
|
|
38
|
+
.bits { font-weight: 800; color: #c6ff3d; margin-left: 6px; }
|
|
39
|
+
@keyframes slide { from { opacity: 0; transform: translateY(20px) scale(.96); } }
|
|
40
|
+
</style>
|
|
41
|
+
</head>
|
|
42
|
+
<body>
|
|
43
|
+
<div id="chat"></div>
|
|
44
|
+
<script>
|
|
45
|
+
const MAX = Overlaify.param('max', 7);
|
|
46
|
+
const FADE = Overlaify.param('fade', 30); // seconds, 0 = never
|
|
47
|
+
const HIDE_BOTS = Overlaify.param('hidebots', 'nightbot,streamelements,streamlabs').split(',');
|
|
48
|
+
const box = document.getElementById('chat');
|
|
49
|
+
|
|
50
|
+
function remove(el) { el.classList.add('gone'); setTimeout(() => el.remove(), 600); }
|
|
51
|
+
|
|
52
|
+
Overlaify.on('chat', e => {
|
|
53
|
+
if (HIDE_BOTS.includes(e.user) || e.message.startsWith('!')) return;
|
|
54
|
+
const el = document.createElement('div');
|
|
55
|
+
el.className = 'msg';
|
|
56
|
+
el.style.setProperty('--uc', e.color || '#9b5cff');
|
|
57
|
+
const badges = [e.isBroadcaster && 'broadcaster', e.isMod && 'mod', e.isVip && 'vip', e.isSub && 'sub'].filter(Boolean)
|
|
58
|
+
.map(b => `<span class="badge b-${b}">${b === 'broadcaster' ? 'LIVE' : b.toUpperCase()}</span>`).join('');
|
|
59
|
+
el.innerHTML = `${badges}<span class="who">${Overlaify.escape(e.name)}</span>${Overlaify.renderMessage(e.message, e.emotes)}${e.bits ? `<span class="bits">◆ ${e.bits}</span>` : ''}`;
|
|
60
|
+
box.appendChild(el);
|
|
61
|
+
while (box.children.length > MAX) box.firstElementChild.remove();
|
|
62
|
+
if (FADE > 0) setTimeout(() => el.isConnected && remove(el), FADE * 1000);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
Overlaify.action('clear', () => { box.replaceChildren(); });
|
|
66
|
+
</script>
|
|
67
|
+
</body>
|
|
68
|
+
</html>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<script src="https://www.overlaify.com/sdk/v1/overlaify.js"></script>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>Death Counter</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link href="https://fonts.googleapis.com/css2?family=Rubik+Mono+One&family=Rubik:wght@600&display=swap" rel="stylesheet">
|
|
9
|
+
<script type="application/json" id="overlaify-manifest">
|
|
10
|
+
{
|
|
11
|
+
"spec": 1,
|
|
12
|
+
"settings": [
|
|
13
|
+
{"key": "label", "label": "Title", "type": "text", "default": "Deaths", "group": "Look"},
|
|
14
|
+
{"key": "icon", "label": "Icon", "type": "text", "default": "💀", "desc": "Emoji or text shown next to the count.", "group": "Look"},
|
|
15
|
+
{"key": "color", "label": "Accent color", "type": "color", "default": "#ff3b5c", "swatches": ["#ff3b5c", "#ffb547", "#c6ff3d", "#5ce1ff", "#a879ff"], "group": "Look"},
|
|
16
|
+
{"key": "start", "label": "Starting count", "type": "number", "default": 0, "desc": "Used when nothing is saved yet.", "group": "Counter"},
|
|
17
|
+
{"key": "cmd", "label": "Chat command", "type": "text", "default": "death", "desc": "So !death, !death- and !death set 3.", "group": "Chat"},
|
|
18
|
+
{"key": "who", "label": "Who can change it from chat", "type": "segmented", "default": "mods", "options": [{"value": "mods", "label": "Mods"}, {"value": "vips", "label": "+ VIPs"}, {"value": "all", "label": "Everyone"}], "group": "Chat"}
|
|
19
|
+
],
|
|
20
|
+
"actions": [
|
|
21
|
+
{"name": "add", "label": "+1"},
|
|
22
|
+
{"name": "remove", "label": "−1", "style": "secondary"},
|
|
23
|
+
{"name": "set", "label": "Set count", "style": "secondary", "input": {"type": "number", "label": "Count", "default": 0, "step": 1, "min": 0}},
|
|
24
|
+
{"name": "reset", "label": "Reset to 0", "style": "danger", "confirm": true, "wide": true}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
<style>
|
|
29
|
+
:root { --c: #ff3b5c; }
|
|
30
|
+
html, body { margin: 0; width: 1920px; height: 1080px; overflow: hidden; background: transparent; }
|
|
31
|
+
.box { position: absolute; left: 50px; top: 50px; display: flex; align-items: center; gap: 20px; padding: 14px 30px 14px 18px; border-radius: 20px;
|
|
32
|
+
background: rgba(12,8,12,.86); border: 3px solid var(--c); box-shadow: 0 0 26px rgba(255,59,92,.35), 0 12px 30px rgba(0,0,0,.45); color: #fff; }
|
|
33
|
+
.icon { font-size: 64px; line-height: 1; filter: drop-shadow(0 0 10px var(--c)); }
|
|
34
|
+
.label { font: 600 22px 'Rubik', system-ui, sans-serif; letter-spacing: .24em; text-transform: uppercase; color: var(--c); }
|
|
35
|
+
.num { font: 400 72px/1 'Rubik Mono One', system-ui, sans-serif; font-variant-numeric: tabular-nums; }
|
|
36
|
+
.hit { animation: hit .6s cubic-bezier(.3,1.6,.5,1); }
|
|
37
|
+
.hit .icon { animation: shake .5s; }
|
|
38
|
+
.down .num { color: #c6ff3d; }
|
|
39
|
+
@keyframes hit { 30% { transform: scale(1.12); box-shadow: 0 0 60px var(--c); } }
|
|
40
|
+
@keyframes shake { 20% { transform: rotate(-18deg); } 40% { transform: rotate(14deg); } 60% { transform: rotate(-8deg); } }
|
|
41
|
+
</style>
|
|
42
|
+
</head>
|
|
43
|
+
<body>
|
|
44
|
+
<div class="box" id="box">
|
|
45
|
+
<div class="icon" id="icon"></div>
|
|
46
|
+
<div><div class="label" id="label"></div><div class="num" id="num">0</div></div>
|
|
47
|
+
</div>
|
|
48
|
+
<script>
|
|
49
|
+
// Chat commands (broadcaster and mods; &who=vips adds VIPs, &who=all lets anyone):
|
|
50
|
+
// !death or !death+ add one !death- remove one
|
|
51
|
+
// !death set 12 set the count !death reset
|
|
52
|
+
// ?cmd=death renames the command &label=Deaths &icon=💀 &start=0 &color=%23ff3b5c
|
|
53
|
+
const CMD = Overlaify.param('cmd', 'death').toLowerCase().replace(/^!/, '');
|
|
54
|
+
const WHO = Overlaify.param('who', 'mods');
|
|
55
|
+
const KEY = `overlaify-counter-${Overlaify.channel || 'preview'}-${CMD}`;
|
|
56
|
+
document.documentElement.style.setProperty('--c', Overlaify.param('color', '#ff3b5c'));
|
|
57
|
+
document.getElementById('label').textContent = Overlaify.param('label', 'Deaths');
|
|
58
|
+
document.getElementById('icon').textContent = Overlaify.param('icon', '💀');
|
|
59
|
+
|
|
60
|
+
let n = Overlaify.param('start', 0);
|
|
61
|
+
if (!Overlaify.isTest) { try { const s = localStorage.getItem(KEY); if (s !== null) n = Number(s) || 0; } catch (e) {} }
|
|
62
|
+
const box = document.getElementById('box'), num = document.getElementById('num');
|
|
63
|
+
function set(v, anim) {
|
|
64
|
+
const up = v > n; n = Math.max(0, v);
|
|
65
|
+
num.textContent = n.toLocaleString();
|
|
66
|
+
if (!Overlaify.isTest) { try { localStorage.setItem(KEY, String(n)); } catch (e) {} }
|
|
67
|
+
if (anim) { box.classList.remove('hit', 'down'); void box.offsetWidth; box.classList.add(up ? 'hit' : 'down'); if (up) Overlaify.chime('low', 0.2); }
|
|
68
|
+
}
|
|
69
|
+
set(n, false);
|
|
70
|
+
|
|
71
|
+
const allowed = e => e.isBroadcaster || e.user === Overlaify.channel || e.isMod || (WHO === 'vips' && e.isVip) || WHO === 'all';
|
|
72
|
+
const esc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
73
|
+
const RE = new RegExp(`^!${esc(CMD)}(\\+|-|\\s+set\\s+(\\d+)|\\s+reset)?\\s*$`, 'i');
|
|
74
|
+
// Actions, from the control panel or from chat.
|
|
75
|
+
Overlaify.action('add', () => set(n + 1, true));
|
|
76
|
+
Overlaify.action('remove', () => set(n - 1, true));
|
|
77
|
+
Overlaify.action('set', v => set(Number(v) || 0, true));
|
|
78
|
+
Overlaify.action('reset', () => set(0, true));
|
|
79
|
+
|
|
80
|
+
Overlaify.on('chat', e => {
|
|
81
|
+
const m = e.message.trim().match(RE);
|
|
82
|
+
if (!m || !allowed(e)) return;
|
|
83
|
+
const op = (m[1] || '+').trim().toLowerCase();
|
|
84
|
+
if (op === '+') Overlaify.dispatch('add');
|
|
85
|
+
else if (op === '-') Overlaify.dispatch('remove');
|
|
86
|
+
else if (op === 'reset') Overlaify.dispatch('reset');
|
|
87
|
+
else Overlaify.dispatch('set', m[2]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Previews: tick the counter up now and then so it looks alive.
|
|
91
|
+
if (Overlaify.isTest) setInterval(() => set(n + 1, true), 4500);
|
|
92
|
+
</script>
|
|
93
|
+
</body>
|
|
94
|
+
</html>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<script src="https://www.overlaify.com/sdk/v1/overlaify.js"></script>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>Emote Wall</title>
|
|
7
|
+
<script type="application/json" id="overlaify-manifest">
|
|
8
|
+
{
|
|
9
|
+
"spec": 1,
|
|
10
|
+
"settings": [
|
|
11
|
+
{"key": "mode", "label": "Motion", "type": "segmented", "default": "rise", "options": [{"value": "rise", "label": "Rise"}, {"value": "rain", "label": "Rain"}, {"value": "bounce", "label": "Bounce"}], "group": "Look"},
|
|
12
|
+
{"key": "size", "label": "Emote size", "type": "range", "default": 112, "min": 32, "max": 256, "step": 4, "unit": "px", "group": "Look"},
|
|
13
|
+
{"key": "life", "label": "Time on screen", "type": "range", "default": 6, "min": 2, "max": 20, "step": 1, "unit": "s", "group": "Look", "desc": "Seconds each emote stays. When not set, Bounce uses 8."},
|
|
14
|
+
{"key": "max", "label": "Max on screen", "type": "range", "default": 80, "min": 10, "max": 300, "step": 10, "group": "Limits", "desc": "Oldest emotes are removed past this."},
|
|
15
|
+
{"key": "per", "label": "Max per message", "type": "range", "default": 6, "min": 1, "max": 20, "step": 1, "group": "Limits"},
|
|
16
|
+
{"key": "combo", "label": "Combo after", "type": "range", "default": 5, "min": 0, "max": 20, "step": 1, "group": "Chat", "desc": "Messages in a row with the same emote before a combo banner. 0 turns it off."},
|
|
17
|
+
{"key": "emoji", "label": "Include emoji", "type": "toggle", "default": true, "group": "Chat", "desc": "Off shows only Twitch emotes."}
|
|
18
|
+
],
|
|
19
|
+
"actions": [
|
|
20
|
+
{"name": "clear", "label": "Clear", "style": "danger", "wide": true}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
<style>
|
|
25
|
+
html, body { margin: 0; width: 1920px; height: 1080px; overflow: hidden; background: transparent; }
|
|
26
|
+
.e { position: absolute; left: 0; top: 0; width: var(--s); height: var(--s); display: grid; place-items: center; will-change: transform, opacity;
|
|
27
|
+
font: var(--s)/1 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif; filter: drop-shadow(0 6px 10px rgba(0,0,0,.35)); }
|
|
28
|
+
.e img { width: 100%; height: 100%; object-fit: contain; }
|
|
29
|
+
.e span { font-size: .8em; }
|
|
30
|
+
.combo { position: absolute; left: 50%; top: 140px; transform: translateX(-50%); display: flex; align-items: center; gap: 18px; padding: 14px 30px;
|
|
31
|
+
border-radius: 99px; background: rgba(12,10,20,.82); border: 3px solid #c6ff3d; box-shadow: 0 0 30px rgba(198,255,61,.5);
|
|
32
|
+
font: 800 46px system-ui, sans-serif; color: #fff; animation: combo .5s cubic-bezier(.2,1.6,.4,1); }
|
|
33
|
+
.combo img, .combo span { height: 72px; font-size: 64px; }
|
|
34
|
+
.combo b { color: #c6ff3d; }
|
|
35
|
+
@keyframes combo { from { transform: translateX(-50%) scale(.4); opacity: 0; } }
|
|
36
|
+
</style>
|
|
37
|
+
</head>
|
|
38
|
+
<body>
|
|
39
|
+
<script>
|
|
40
|
+
// ?mode=rise|rain|bounce &size=112 &max=80 emotes on screen &per=6 emotes per message &emoji=0 ignore emoji
|
|
41
|
+
// &combo=5 shows a combo banner when that many messages in a row use the same emote (0 = off)
|
|
42
|
+
const MODE = Overlaify.param('mode', 'rise');
|
|
43
|
+
const SIZE = Overlaify.param('size', 112);
|
|
44
|
+
const MAX = Overlaify.param('max', 80), PER = Overlaify.param('per', 6);
|
|
45
|
+
const EMOJI = Overlaify.param('emoji', true), COMBO = Overlaify.param('combo', 5);
|
|
46
|
+
const LIFE = Overlaify.param('life', MODE === 'bounce' ? 8 : 6) * 1000;
|
|
47
|
+
document.documentElement.style.setProperty('--s', SIZE + 'px');
|
|
48
|
+
const W = 1920, H = 1080;
|
|
49
|
+
const url = id => `https://static-cdn.jtvnw.net/emoticons/v2/${encodeURIComponent(id)}/default/dark/3.0`;
|
|
50
|
+
// Global Twitch emotes used when a simulated message has no emote data.
|
|
51
|
+
const SAMPLE = ['25', '425618', '86', '41', '30259', '81274', '64138'];
|
|
52
|
+
const EMOJI_RE = /\p{Extended_Pictographic}(️|\p{Extended_Pictographic})*/gu;
|
|
53
|
+
|
|
54
|
+
const live = [];
|
|
55
|
+
function spawn(key) {
|
|
56
|
+
if (live.length >= MAX) live.shift().el.remove();
|
|
57
|
+
const el = document.createElement('div');
|
|
58
|
+
el.className = 'e';
|
|
59
|
+
el.innerHTML = key.id ? `<img src="${url(key.id)}" alt="" onerror="this.parentNode.remove()">` : `<span>${Overlaify.escape(key.emoji)}</span>`;
|
|
60
|
+
document.body.appendChild(el);
|
|
61
|
+
const p = { el, born: performance.now(), rot: (Math.random() - .5) * 40, spin: (Math.random() - .5) * 120 };
|
|
62
|
+
if (MODE === 'rain') Object.assign(p, { x: Math.random() * (W - SIZE), y: -SIZE, vx: (Math.random() - .5) * 60, vy: 260 + Math.random() * 220 });
|
|
63
|
+
else if (MODE === 'bounce') Object.assign(p, { x: Math.random() * (W - SIZE), y: Math.random() * (H - SIZE), vx: (Math.random() < .5 ? -1 : 1) * (220 + Math.random() * 200), vy: (Math.random() < .5 ? -1 : 1) * (180 + Math.random() * 180), spin: 0 });
|
|
64
|
+
else Object.assign(p, { x: Math.random() * (W - SIZE), y: H, vx: 0, vy: -(220 + Math.random() * 180), wob: Math.random() * 6.28 });
|
|
65
|
+
live.push(p);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let last = performance.now();
|
|
69
|
+
(function frame(now) {
|
|
70
|
+
const dt = Math.min(.05, (now - last) / 1000); last = now;
|
|
71
|
+
for (let i = live.length - 1; i >= 0; i--) {
|
|
72
|
+
const p = live[i], age = now - p.born;
|
|
73
|
+
p.x += p.vx * dt; p.y += p.vy * dt; p.rot += p.spin * dt;
|
|
74
|
+
if (MODE === 'bounce') {
|
|
75
|
+
if (p.x < 0 || p.x > W - SIZE) { p.vx *= -1; p.x = Math.max(0, Math.min(W - SIZE, p.x)); }
|
|
76
|
+
if (p.y < 0 || p.y > H - SIZE) { p.vy *= -1; p.y = Math.max(0, Math.min(H - SIZE, p.y)); }
|
|
77
|
+
}
|
|
78
|
+
const x = MODE === 'rise' ? p.x + Math.sin(age / 400 + p.wob) * 30 : p.x;
|
|
79
|
+
const fadeIn = Math.min(1, age / 200), fadeOut = Math.min(1, (LIFE - age) / 600);
|
|
80
|
+
p.el.style.transform = `translate(${x}px, ${p.y}px) rotate(${p.rot}deg) scale(${.6 + .4 * fadeIn})`;
|
|
81
|
+
p.el.style.opacity = Math.max(0, Math.min(fadeIn, fadeOut));
|
|
82
|
+
if (age > LIFE || p.y < -SIZE * 2 || p.y > H + SIZE) { p.el.remove(); live.splice(i, 1); }
|
|
83
|
+
}
|
|
84
|
+
requestAnimationFrame(frame);
|
|
85
|
+
})(last);
|
|
86
|
+
|
|
87
|
+
function keysOf(e) {
|
|
88
|
+
const found = [];
|
|
89
|
+
if (e.emotes) Object.keys(e.emotes).forEach(id => e.emotes[id].forEach(r => found.push({ id, at: r[0] })));
|
|
90
|
+
if (EMOJI) for (const m of e.message.matchAll(EMOJI_RE)) found.push({ emoji: m[0], at: m.index });
|
|
91
|
+
if (!found.length && e.simulated) for (let i = 0, n = 1 + Math.floor(Math.random() * 4); i < n; i++) found.push({ id: SAMPLE[Math.floor(Math.random() * SAMPLE.length)], at: i });
|
|
92
|
+
return found.sort((a, b) => a.at - b.at);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let streak = { k: '', n: 0 }, banner = null;
|
|
96
|
+
function combo(k) {
|
|
97
|
+
if (!COMBO) return;
|
|
98
|
+
const name = k.id || k.emoji;
|
|
99
|
+
streak = streak.k === name ? { k: name, n: streak.n + 1 } : { k: name, n: 1 };
|
|
100
|
+
if (streak.n < COMBO) return;
|
|
101
|
+
if (banner) banner.remove();
|
|
102
|
+
banner = document.createElement('div');
|
|
103
|
+
banner.className = 'combo';
|
|
104
|
+
banner.innerHTML = `${k.id ? `<img src="${url(k.id)}" alt="">` : `<span>${Overlaify.escape(k.emoji)}</span>`} COMBO <b>×${streak.n}</b>`;
|
|
105
|
+
document.body.appendChild(banner);
|
|
106
|
+
const mine = banner; setTimeout(() => mine.isConnected && mine.remove(), 3000);
|
|
107
|
+
if (streak.n === COMBO) Overlaify.chime('coin');
|
|
108
|
+
for (let i = 0; i < 6; i++) spawn(k);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
Overlaify.action('clear', () => {
|
|
112
|
+
live.splice(0).forEach(p => p.el.remove());
|
|
113
|
+
if (banner) banner.remove();
|
|
114
|
+
streak = { k: '', n: 0 };
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
Overlaify.on('chat', e => {
|
|
118
|
+
const keys = keysOf(e);
|
|
119
|
+
if (!keys.length) { streak.n = 0; return; }
|
|
120
|
+
keys.slice(0, PER).forEach((k, i) => setTimeout(() => spawn(k), i * 90));
|
|
121
|
+
combo(keys[0]);
|
|
122
|
+
});
|
|
123
|
+
</script>
|
|
124
|
+
</body>
|
|
125
|
+
</html>
|