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.
@@ -0,0 +1,89 @@
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>Liquid Goal Bar</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;700&display=swap" rel="stylesheet">
9
+ <script type="application/json" id="overlaify-manifest">
10
+ {
11
+ "spec": 1,
12
+ "settings": [
13
+ {"key": "type", "label": "What to count", "type": "segmented", "default": "subs", "options": [{"value": "subs", "label": "Subs"}, {"value": "bits", "label": "Bits"}], "group": "Goal"},
14
+ {"key": "goal", "label": "Goal", "type": "number", "default": 50, "desc": "Defaults to 50 subs or 10,000 bits.", "group": "Goal"},
15
+ {"key": "start", "label": "Starting value", "type": "number", "default": 0, "desc": "E.g. subs you already have this month.", "group": "Goal"},
16
+ {"key": "label", "label": "Title", "type": "text", "default": "Sub goal", "group": "Look"},
17
+ {"key": "color", "label": "Fill color", "type": "color", "default": "#ff4f8b", "swatches": ["#ff4f8b", "#ffb547", "#c6ff3d", "#5ce1ff", "#a879ff"], "group": "Look"}
18
+ ],
19
+ "actions": [
20
+ {"name": "add", "label": "Add", "input": {"type": "number", "label": "Amount", "default": 1, "step": 1, "min": 1}},
21
+ {"name": "set", "label": "Set progress", "style": "secondary", "input": {"type": "number", "label": "Value", "default": 0, "step": 1, "min": 0}},
22
+ {"name": "reset", "label": "Reset to 0", "style": "danger", "confirm": true, "wide": true}
23
+ ]
24
+ }
25
+ </script>
26
+ <style>
27
+ :root { --c: #ff4f8b; --c2: #ffb547; }
28
+ html, body { margin: 0; width: 1920px; height: 1080px; overflow: hidden; background: transparent; }
29
+ .wrap { position: absolute; left: 50%; top: 36px; transform: translateX(-50%); width: 900px; font-family: 'Space Grotesk', system-ui, sans-serif; color: #fff; }
30
+ .row { display: flex; justify-content: space-between; align-items: baseline; margin: 0 6px 10px; text-shadow: 0 2px 6px rgba(0,0,0,.7); }
31
+ .label { font-size: 28px; font-weight: 700; letter-spacing: .02em; }
32
+ .count { font-size: 26px; font-weight: 500; font-variant-numeric: tabular-nums; }
33
+ .track { position: relative; height: 34px; border-radius: 99px; background: rgba(12,10,20,.75); border: 2px solid rgba(255,255,255,.18); overflow: hidden; box-shadow: 0 6px 24px rgba(0,0,0,.4); }
34
+ .fill { position: absolute; inset: 0 auto 0 0; width: 0; border-radius: 99px;
35
+ background: linear-gradient(90deg, var(--c), var(--c2)); transition: width 1.2s cubic-bezier(.2,1,.3,1);
36
+ box-shadow: 0 0 20px var(--c); }
37
+ .fill::after { content: ''; position: absolute; inset: 0; border-radius: inherit;
38
+ background: repeating-linear-gradient(115deg, rgba(255,255,255,.22) 0 14px, transparent 14px 28px); animation: flow 1s linear infinite; }
39
+ .bump { animation: bump .5s; }
40
+ .done .fill { background: linear-gradient(90deg, #c6ff3d, #4dffa6); box-shadow: 0 0 28px #c6ff3d; }
41
+ @keyframes flow { to { background-position: 28px 0; } }
42
+ @keyframes bump { 40% { transform: scaleY(1.25); } }
43
+ </style>
44
+ </head>
45
+ <body>
46
+ <div class="wrap" id="w">
47
+ <div class="row"><span class="label" id="label"></span><span class="count" id="count"></span></div>
48
+ <div class="track" id="track"><div class="fill" id="fill"></div></div>
49
+ </div>
50
+ <script>
51
+ // ?type=subs|bits|follows &goal=50 &start=0 &label=Sub%20goal &color=%23ff4f8b
52
+ const TYPE = Overlaify.param('type', 'subs');
53
+ const GOAL = Overlaify.param('goal', TYPE === 'bits' ? 10000 : 50);
54
+ const KEY = `overlaify-goal-${Overlaify.channel}-${TYPE}-${GOAL}`;
55
+ const defaultLabel = { subs: 'Sub goal', bits: 'Bits goal', follows: 'Follower goal' }[TYPE] || 'Goal';
56
+ document.getElementById('label').textContent = Overlaify.param('label', defaultLabel);
57
+ document.documentElement.style.setProperty('--c', Overlaify.param('color', '#ff4f8b'));
58
+
59
+ let value = Overlaify.param('start', 0);
60
+ try { const saved = Number(localStorage.getItem(KEY)); if (saved > value) value = saved; } catch (e) {}
61
+
62
+ const fill = document.getElementById('fill'), count = document.getElementById('count'), track = document.getElementById('track');
63
+ function render(bump) {
64
+ const pct = Math.min(100, value / GOAL * 100);
65
+ fill.style.width = pct + '%';
66
+ count.textContent = `${value.toLocaleString()} / ${GOAL.toLocaleString()}`;
67
+ document.getElementById('w').classList.toggle('done', value >= GOAL);
68
+ if (bump) { track.classList.remove('bump'); void track.offsetWidth; track.classList.add('bump'); }
69
+ try { localStorage.setItem(KEY, String(value)); } catch (e) {}
70
+ }
71
+ function add(n) { if (n > 0) { const was = value < GOAL; value += n; render(true); if (was && value >= GOAL) Overlaify.chime('fanfare'); } }
72
+ Overlaify.action('add', n => add(Number(n) || 0));
73
+ Overlaify.action('set', n => { value = Math.max(0, Number(n) || 0); render(true); });
74
+ Overlaify.action('reset', () => { value = 0; render(true); });
75
+
76
+ if (TYPE === 'subs') {
77
+ Overlaify.on('sub', () => add(1));
78
+ Overlaify.on('gift', () => add(1));
79
+ // giftbomb is followed by individual 'gift' events from Twitch; count it only for simulations
80
+ Overlaify.on('giftbomb', e => e.simulated && add(e.count));
81
+ } else if (TYPE === 'bits') {
82
+ Overlaify.on('cheer', e => add(e.bits));
83
+ } else if (TYPE === 'follows') {
84
+ Overlaify.on('follow', () => add(1));
85
+ }
86
+ render(false);
87
+ </script>
88
+ </body>
89
+ </html>
@@ -0,0 +1,84 @@
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>Neon Pulse Alerts</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link href="https://fonts.googleapis.com/css2?family=Unbounded:wght@500;800&display=swap" rel="stylesheet">
9
+ <script type="application/json" id="overlaify-manifest">
10
+ {
11
+ "spec": 1,
12
+ "settings": [
13
+ {"key":"color","label":"Accent color","type":"color","default":"#c6ff3d","swatches":["#c6ff3d","#ff4f8b","#5ce1ff","#a879ff","#ffb547"],"desc":"Border, glow and kicker text."},
14
+ {"key":"duration","label":"Duration","type":"range","default":5,"min":2,"max":20,"step":0.5,"unit":"s","desc":"Seconds each alert stays on screen."}
15
+ ]
16
+ }
17
+ </script>
18
+ <style>
19
+ :root { --c: #c6ff3d; --c2: #9b5cff; }
20
+ html, body { margin: 0; width: 1920px; height: 1080px; overflow: hidden; background: transparent; }
21
+ #stage { position: absolute; left: 50%; top: 120px; transform: translateX(-50%); }
22
+ .alert {
23
+ position: relative; min-width: 640px; padding: 34px 56px 38px; text-align: center;
24
+ font-family: 'Unbounded', system-ui, sans-serif; color: #fff;
25
+ background: rgba(10, 8, 18, .82); border-radius: 22px;
26
+ border: 3px solid var(--c);
27
+ box-shadow: 0 0 24px var(--c), inset 0 0 24px rgba(198,255,61,.25), 0 0 80px var(--c2);
28
+ opacity: 0; transform: scale(.6) translateY(-30px);
29
+ }
30
+ .alert.in { animation: pop .55s cubic-bezier(.2,1.6,.4,1) forwards; }
31
+ .alert.out { animation: leave .45s ease-in forwards; }
32
+ .kicker { font-size: 22px; font-weight: 500; letter-spacing: .32em; text-transform: uppercase; color: var(--c); text-shadow: 0 0 12px var(--c); }
33
+ .name { font-size: 64px; font-weight: 800; margin: 10px 0 6px; line-height: 1.05;
34
+ background: linear-gradient(90deg, #fff, var(--c), #fff); background-size: 200% 100%;
35
+ -webkit-background-clip: text; background-clip: text; color: transparent; animation: shine 1.6s linear infinite; }
36
+ .detail { font-size: 26px; font-weight: 500; color: #e9e4ff; }
37
+ .msg { margin-top: 14px; font: 500 22px system-ui, sans-serif; color: #cfc8e6; max-width: 760px; }
38
+ .ring { position: absolute; inset: -3px; border-radius: 22px; border: 3px solid var(--c); animation: ring 1.2s ease-out infinite; pointer-events: none; }
39
+ @keyframes pop { to { opacity: 1; transform: scale(1) translateY(0); } }
40
+ @keyframes leave { to { opacity: 0; transform: scale(.9) translateY(-40px); } }
41
+ @keyframes shine { to { background-position: -200% 0; } }
42
+ @keyframes ring { from { opacity: .9; transform: scale(1); } to { opacity: 0; transform: scale(1.12); } }
43
+ </style>
44
+ </head>
45
+ <body>
46
+ <div id="stage"></div>
47
+ <script>
48
+ const accent = Overlaify.param('color', '#c6ff3d');
49
+ document.documentElement.style.setProperty('--c', accent);
50
+ const DURATION = Overlaify.param('duration', 5) * 1000;
51
+ const stage = document.getElementById('stage');
52
+ const esc = Overlaify.escape;
53
+ const tierLabel = t => t === 'prime' ? 'with Prime' : `Tier ${t}`;
54
+
55
+ function content(e) {
56
+ switch (e.type) {
57
+ case 'follow': return ['New follower', e.name, 'welcome to the squad', '', 'pop'];
58
+ case 'sub': return [e.months > 1 ? 'Resub' : 'New sub', e.name, `${tierLabel(e.tier)}${e.months > 1 ? ` · ${e.months} months` : ''}`, e.message, 'fanfare'];
59
+ case 'gift': return ['Gifted sub', e.name, `→ ${e.recipientName || e.recipient}`, '', 'alert'];
60
+ case 'giftbomb': return ['Gift bomb', e.name, `${e.count} subs to the community!`, '', 'fanfare'];
61
+ case 'cheer': return ['Cheer', e.name, `${e.bits.toLocaleString()} bits`, e.message, 'coin'];
62
+ case 'raid': return ['Raid incoming', e.name, `with ${e.viewers.toLocaleString()} raiders`, '', 'fanfare'];
63
+ }
64
+ }
65
+
66
+ const enqueue = Overlaify.queue(async (e) => {
67
+ const c = content(e); if (!c) return;
68
+ const [kicker, name, detail, msg, sound] = c;
69
+ const el = document.createElement('div');
70
+ el.className = 'alert';
71
+ el.innerHTML = `<div class="ring"></div><div class="kicker">${esc(kicker)}</div><div class="name">${esc(name)}</div><div class="detail">${esc(detail)}</div>${msg ? `<div class="msg">${esc(msg)}</div>` : ''}`;
72
+ stage.appendChild(el);
73
+ requestAnimationFrame(() => el.classList.add('in'));
74
+ Overlaify.chime(sound);
75
+ await Overlaify.sleep(DURATION);
76
+ el.classList.add('out');
77
+ await Overlaify.sleep(450);
78
+ el.remove();
79
+ }, { gap: 400 });
80
+
81
+ ['follow', 'sub', 'gift', 'giftbomb', 'cheer', 'raid'].forEach(t => Overlaify.on(t, enqueue));
82
+ </script>
83
+ </body>
84
+ </html>
@@ -0,0 +1,196 @@
1
+ # Overlaify Overlay Spec — v1
2
+
3
+ An Overlaify overlay is one HTML document that runs as an OBS Browser Source. Anything that follows this spec
4
+ — written in Overlaify Studio, by another AI, or by hand — can be hosted on Overlaify, previewed with simulated
5
+ events, configured from a settings panel and controlled live from a control page.
6
+
7
+ The spec has three parts:
8
+
9
+ 1. **The document**: what the HTML file may contain.
10
+ 2. **The runtime**: the `Overlaify` SDK the overlay talks to, and the events it receives.
11
+ 3. **The manifest**: a JSON block in which the overlay declares its settings and live actions.
12
+
13
+ `src/lib/validate.js` checks an overlay against this spec. Each rule below names the code it reports.
14
+ **Errors** break the overlay on Overlaify and must be fixed. **Warnings** are quality problems.
15
+
16
+ ---
17
+
18
+ ## 1. The document
19
+
20
+ | Rule | Code |
21
+ |---|---|
22
+ | One complete HTML document (`<!doctype html><html>…</html>`). | `not_html` (warning) |
23
+ | A short overlay name in `<title>`. | `title_missing` (warning) |
24
+ | At most 80 KB (80,000 bytes), so that Studio can still edit it. | `too_large` |
25
+ | All CSS and JS inline. The one `<script src>` allowed is the SDK, `https://www.overlaify.com/sdk/v1/overlaify.js` (or `/sdk/v1/overlaify.js`), so the file also runs on its own; Overlaify replaces it with its own copy when hosting. | `external_script` |
26
+ | No external files: stylesheets, images, audio, video, iframes, CSS `url()`/`@import`. The only hosts allowed are Google Fonts (`fonts.googleapis.com`, `fonts.gstatic.com`) and Twitch emotes (`static-cdn.jtvnw.net`). Use `data:` URLs, inline SVG, canvas or emoji instead. | `external_resource` |
27
+ | No `<iframe>`, `<object>` or `<embed>`. | `embedded_frame` |
28
+ | Other URLs in the code (outside tags) point only at those hosts. | `external_url` (warning) |
29
+ | No network calls (`fetch`, `XMLHttpRequest`, `WebSocket`, `EventSource`): events come from the SDK. | `network_access` (warning) |
30
+ | No user interaction or dialogs (`alert`, `confirm`, `prompt`): the overlay just runs. | `blocking_dialog` (warning) |
31
+
32
+ Guidelines (not checked):
33
+
34
+ - The canvas is 1920×1080. `html` and `body`: `margin: 0; width: 1920px; height: 1080px; overflow: hidden; background: transparent`.
35
+ Paint a full-screen background only for full scenes (Starting soon, BRB).
36
+ - Never insert user text as raw HTML. Use `Overlaify.escape(str)` or `textContent`, and `Overlaify.renderMessage(text, emotes)` for chat with emotes.
37
+ - Wrap `localStorage`/`sessionStorage` in `try/catch`: the page runs in a sandboxed, opaque origin.
38
+ - Animate with `transform` and `opacity`. Keep text readable over gameplay (contrast, outline or shadow, large sizes).
39
+
40
+ ## 2. The runtime
41
+
42
+ Overlaify injects the SDK before the overlay's own scripts, so a global `Overlaify` object is always there.
43
+ Each spec version has its own SDK URL, `/sdk/v1/overlaify.js` for this one, so overlays written for it keep
44
+ working when a later spec changes the runtime. `Overlaify.spec` is the version the loaded SDK implements.
45
+
46
+ ```
47
+ Overlaify.on(type, handler) subscribe; returns an unsubscribe function. '*' receives every event once.
48
+ Overlaify.off(type, handler)
49
+ Overlaify.queue(handler, { gap }) returns enqueue(event); runs handler one event at a time. handler may be
50
+ async or return ms to wait. Use it for alerts so they never overlap.
51
+ Overlaify.param(key, fallback) a setting's value; number and boolean fallbacks coerce the type.
52
+ Overlaify.action(name, fn) handle a manifest action; fn(value, event) gets the action's input value.
53
+ Overlaify.dispatch(name, value?) run an action from the overlay's own code (e.g. a chat command).
54
+ Overlaify.escape(str) HTML-escape.
55
+ Overlaify.renderMessage(text, emotes) safe HTML with Twitch emote <img class="ov-emote">.
56
+ Overlaify.chime(kind, volume?) built-in sound: 'alert' | 'pop' | 'coin' | 'fanfare' | 'low'. Respects mute.
57
+ Overlaify.sleep(ms) Promise delay.
58
+ Overlaify.simulate(type, overrides?) emit a fake event (used by previews).
59
+ Overlaify.channel Twitch channel login ('' in previews).
60
+ Overlaify.sources the connected sources: [{ platform, channel, connected }].
61
+ Overlaify.isTest true when random test events are running.
62
+ ```
63
+
64
+ ### Events
65
+
66
+ Events have the same shape whatever platform they come from, so an overlay works on every platform
67
+ Overlaify connects to without knowing which one it is on. Today that is Twitch; more will follow without a new
68
+ spec version.
69
+
70
+ Every event has `type`, `id`, `timestamp`, `platform` (`'twitch'`), `user` (login), `userId` (`''` when
71
+ unknown), `name` (display name), `color` (hex or `null`) and `simulated` (boolean). Simulated events look like
72
+ Twitch events.
73
+
74
+ | Type | Extra fields |
75
+ |---|---|
76
+ | `chat` | `message`, `emotes`, `badges: string[]`, `isMod`, `isSub`, `isVip`, `isBroadcaster`, `bits`, `action` |
77
+ | `follow` | — |
78
+ | `sub` | `months`, `tier: 'prime' \| '1' \| '2' \| '3'`, `message` |
79
+ | `gift` | `recipient`, `recipientName`, `tier`, `anonymous` |
80
+ | `giftbomb` | `count`, `tier` (Twitch also sends one `gift` per recipient afterwards) |
81
+ | `tip` | `amount`, `currency`, `kind`, `usd`, `display`, `message`, `emotes` |
82
+ | `cheer` | `bits`, `message`, `emotes` (Twitch only) |
83
+ | `raid` | `viewers` |
84
+
85
+ `tip` is money from a viewer on any platform. `amount` is in `currency`, which is `'bits'` for Twitch bits or an
86
+ ISO code such as `'USD'`; `kind` names the platform's feature (`'bits'` on Twitch); `usd` is the approximate
87
+ value in US dollars, for thresholds and sizing; `display` is ready to show, such as `"500 bits"`. Twitch bits
88
+ fire both `cheer` and `tip`. Handle `tip` in new overlays so they also count tips from other platforms, and
89
+ never both, or bits count twice (`cheer_and_tip`, warning). `'*'` gets Twitch bits once, as the `cheer`.
90
+
91
+ Lifecycle events, not included in `'*'`: `connected`, `disconnected`, with `platform` and `channel`; they fire
92
+ for each source. Subscribing to any other name is reported as `unknown_event` (warning).
93
+
94
+ ### Where values come from
95
+
96
+ - **Settings** saved by the streamer arrive as `window.OVERLAIFY_CONFIG`. URL query parameters override them.
97
+ Both are read with `Overlaify.param`.
98
+ - Reserved keys, always provided by the platform: `channel`, `sources`, `volume`, `mute`, `test`.
99
+ - **Sources** say where events come from: `OVERLAIFY_CONFIG.sources`, a list of `{ platform, channel }`,
100
+ plus `?channel=` as a Twitch source. One overlay can listen to several at once.
101
+ - **Actions** come from the control page (`/c/:id`), the preview's keys, or `Overlaify.dispatch`.
102
+
103
+ ## 3. The manifest
104
+
105
+ The overlay declares itself in a JSON block in `<head>`, before its own scripts:
106
+
107
+ ```html
108
+ <script type="application/json" id="overlaify-manifest">
109
+ {
110
+ "spec": 1,
111
+ "settings": [ ... ],
112
+ "actions": [ ... ]
113
+ }
114
+ </script>
115
+ ```
116
+
117
+ | Rule | Code |
118
+ |---|---|
119
+ | Overlays that read settings or handle actions have a manifest. Without one, the streamer gets no settings panel or keys. | `manifest_missing` (warning) |
120
+ | At most one manifest block. | `manifest_duplicate` |
121
+ | `type="application/json"`, so the browser does not run it. | `manifest_type` |
122
+ | Valid JSON object: double quotes, no comments, no trailing commas. | `manifest_json` |
123
+ | `"spec": 1`. | `spec_missing` (warning), `spec_version` |
124
+ | `settings` and `actions` are arrays when present. | `manifest_shape` |
125
+ | No other top-level keys. | `manifest_unknown_key` (warning) |
126
+
127
+ ### Settings
128
+
129
+ Settings are the overlay's starting state. The streamer edits them in a panel; the overlay reads each one with
130
+ `Overlaify.param(key, fallback)`, using the manifest's default as the fallback.
131
+
132
+ ```json
133
+ { "key": "color", "label": "Fill color", "type": "color", "default": "#ff4f8b",
134
+ "swatches": ["#ff4f8b", "#ffb547", "#c6ff3d"], "group": "Look", "desc": "Bar and glow." }
135
+ ```
136
+
137
+ | Field | Rule | Code |
138
+ |---|---|---|
139
+ | `key` | Required. Letters, digits, `_` or `-`, starting with a letter, at most 40 characters. Unique. | `setting_key`, `setting_duplicate` |
140
+ | | Not a reserved key (`channel`, `volume`, `mute`, `test`). | `setting_reserved` |
141
+ | `label` | Short name shown in the panel. | `setting_label` (warning) |
142
+ | `type` | `text`, `number`, `toggle`, `color`, `select`, `segmented` or `range`. | `setting_type` |
143
+ | `default` | Required, and matching the type (see below). | `setting_default_missing` (warning), `setting_default` |
144
+ | `desc`, `group` | Optional strings. `group` puts settings under a heading; use groups once there are more than about 4 settings. | `setting_field` (warning) |
145
+
146
+ By type:
147
+
148
+ | Type | Default | Other fields |
149
+ |---|---|---|
150
+ | `text` | string | — |
151
+ | `number` | number | — |
152
+ | `toggle` | boolean; read with a boolean fallback | — |
153
+ | `color` | `#rrggbb` | `swatches`: optional array of `#rrggbb` presets (`setting_swatches`) |
154
+ | `select` | one of the option values | `options`: non-empty array (`setting_options`) |
155
+ | `segmented` | one of the option values | `options`: 2–4 short choices (`setting_options`; more than 4 is a warning) |
156
+ | `range` | number within `min`–`max` | `min` and `max` required, `min < max`, `step > 0`, optional `unit` (`setting_range`) |
157
+
158
+ Options are plain values (`"subs"`) or `{ "value": "subs", "label": "Subs" }`.
159
+
160
+ Consistency with the code:
161
+
162
+ - Every setting is read with `Overlaify.param('key', …)`. → `setting_unused` (warning)
163
+ - Every `Overlaify.param('key', …)`, other than reserved keys, is declared as a setting. → `param_undeclared` (warning)
164
+
165
+ ### Actions
166
+
167
+ Actions change the overlay while it runs (pause, add time, reset, show a message). They appear as keys on the
168
+ control page. Add them only when the overlay has state worth controlling live: timers, counters, goals, queues
169
+ or toggled scenes.
170
+
171
+ ```json
172
+ { "name": "add", "label": "Add", "input": { "type": "number", "label": "Amount", "default": 1, "step": 1, "min": 1 } }
173
+ ```
174
+
175
+ | Field | Rule | Code |
176
+ |---|---|---|
177
+ | `name` | Required, same format as setting keys, unique. | `action_name`, `action_duplicate` |
178
+ | `label` | The key's text. | `action_label` (warning) |
179
+ | `group` | Optional heading. | `action_field` (warning) |
180
+ | `style` | `primary` (default), `secondary` (less used half of a pair) or `danger` (resets and clears). | `action_style` |
181
+ | `confirm` | `true` or a short question. The key asks for a second press. Use it on anything that throws away progress. | `action_confirm` |
182
+ | `wide` | `true` makes the key take a full row. | `action_field` (warning) |
183
+ | `input` | `{ type: number \| text \| select, label, default, min?, max?, step?, options? }`: a value sent with the action. `select` needs `options`, and a `number` default must be a number. | `action_input` |
184
+
185
+ Consistency with the code:
186
+
187
+ - Every action has a handler, `Overlaify.action('name', fn)`. Otherwise its key does nothing. → `action_unhandled`
188
+ - Every handler is declared in the manifest, unless it is only dispatched from the overlay's own code. → `action_undeclared` (warning)
189
+
190
+ ---
191
+
192
+ ## Versioning
193
+
194
+ This is spec version 1. Additions that old overlays can ignore (new event types, new setting types, new
195
+ optional fields) keep the version number. Changes that would break an existing overlay raise it and
196
+ get a new SDK URL (`/sdk/v2/…`); `/sdk/v1/overlaify.js` stays as it is for v1 overlays.