use-scroll-animate 5.5.0 → 5.6.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/CHANGELOG.md +13 -0
- package/dist/components/effects.cjs +341 -0
- package/dist/components/effects.cjs.map +1 -1
- package/dist/components/effects.d.cts +92 -2
- package/dist/components/effects.d.ts +92 -2
- package/dist/components/effects.js +335 -3
- package/dist/components/effects.js.map +1 -1
- package/dist/components.umd.js +2 -2
- package/dist/components.umd.js.map +1 -1
- package/docs/ROADMAP.md +1 -1
- package/docs/components.md +21 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [5.6.0] - 2026-10-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Sound-reactive pack** (`use-scroll-animate/components/effects`, `registerAudioEffects()`, `AUDIO_FX`, kind `background`): `spectrum-bars` (`bars`, `gap`, `mirror`), `pulse-ring` (`rings`, `color`), `wave-ring` (`color`, `amplitude`) — Canvas 2D on the 5.5 `canvasBackground()` runner (visible-only, adaptive quality); they idle gently until audio is enabled.
|
|
14
|
+
- `enableAudio(input)` — analyse the microphone (`'mic'`), an `<audio>` / `<video>` element or selector, or a `MediaStream` through one shared Web Audio analyser. Must be called from a user gesture (the context is resumed there). The microphone is never routed to the speakers and is released on `stop()`; media stays audible after `stop()`. Also `disableAudio()`, `getAudio()`, `sample()` → `{ level, bass, freq, wave }`.
|
|
15
|
+
- Beat detection: `createBeatDetector({ threshold, cooldown, history, floor })` (pure), `onBeat(cb)`, and `bindBeat(el, effect, options)` — plays **any registered effect** on every beat.
|
|
16
|
+
- `<usa-audio source="#track | mic" label="…">` — renders (or uses your `[data-audio-toggle]`) an `aria-pressed` toggle button; children with `data-usa-beat="effect"` (`data-usa-beat-options` JSON) play that effect on beats; events `usa-beat`, `usa-audio-error` (`data-audio-error` on the host).
|
|
17
|
+
- While audio runs, `--usa-audio-level` / `--usa-audio-bass` (0–1) are set on `<html>` for CSS-driven reactions.
|
|
18
|
+
- Showcase: **Sound-reactive backgrounds** and **Beat-triggered effects** cards.
|
|
19
|
+
|
|
20
|
+
### Accessibility
|
|
21
|
+
- Audio only starts from a user gesture. Under reduced motion the visual effects are skipped, beats trigger no effects and the CSS variables stay at 0 (sound keeps playing).
|
|
22
|
+
|
|
10
23
|
## [5.5.0] - 2026-10-08
|
|
11
24
|
|
|
12
25
|
### Added
|
|
@@ -1115,6 +1115,332 @@ const GENERATIVE_FX = [
|
|
|
1115
1115
|
}),
|
|
1116
1116
|
];
|
|
1117
1117
|
|
|
1118
|
+
let actx = null;
|
|
1119
|
+
let current = null;
|
|
1120
|
+
const mediaSources = new WeakMap();
|
|
1121
|
+
/** The running analyser, if `enableAudio()` was called. */
|
|
1122
|
+
const getAudio = () => current;
|
|
1123
|
+
function audioContext() {
|
|
1124
|
+
const AC = globalThis.AudioContext || globalThis.webkitAudioContext;
|
|
1125
|
+
if (!AC)
|
|
1126
|
+
throw new Error('[use-scroll-animate] Web Audio is not available');
|
|
1127
|
+
if (!actx || actx.state === 'closed')
|
|
1128
|
+
actx = new AC();
|
|
1129
|
+
return actx;
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Start analysing `input` and make it the source of every sound-reactive
|
|
1133
|
+
* effect. Call from a user gesture. Replaces a previous source.
|
|
1134
|
+
*/
|
|
1135
|
+
async function enableAudio(input = 'mic', opts = {}) {
|
|
1136
|
+
current?.stop();
|
|
1137
|
+
const ac = audioContext();
|
|
1138
|
+
if (ac.state === 'suspended')
|
|
1139
|
+
await ac.resume().catch(() => undefined);
|
|
1140
|
+
const analyser = ac.createAnalyser();
|
|
1141
|
+
analyser.fftSize = opts.fftSize || 512;
|
|
1142
|
+
analyser.smoothingTimeConstant = opts.smoothing ?? 0.8;
|
|
1143
|
+
let src;
|
|
1144
|
+
let release = () => { };
|
|
1145
|
+
const media = typeof input === 'string' && input !== 'mic' ? document.querySelector(input) : input;
|
|
1146
|
+
if (media === 'mic') {
|
|
1147
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
1148
|
+
src = ac.createMediaStreamSource(stream);
|
|
1149
|
+
src.connect(analyser); // never to the speakers: no feedback
|
|
1150
|
+
release = () => stream.getTracks().forEach((t) => t.stop());
|
|
1151
|
+
}
|
|
1152
|
+
else if (media && typeof media.play === 'function') {
|
|
1153
|
+
const m = media;
|
|
1154
|
+
let node = mediaSources.get(m);
|
|
1155
|
+
if (!node)
|
|
1156
|
+
mediaSources.set(m, (node = ac.createMediaElementSource(m)));
|
|
1157
|
+
try {
|
|
1158
|
+
node.disconnect();
|
|
1159
|
+
}
|
|
1160
|
+
catch {
|
|
1161
|
+
/* not connected */
|
|
1162
|
+
}
|
|
1163
|
+
src = node;
|
|
1164
|
+
src.connect(analyser);
|
|
1165
|
+
analyser.connect(ac.destination);
|
|
1166
|
+
release = () => {
|
|
1167
|
+
try {
|
|
1168
|
+
node.disconnect();
|
|
1169
|
+
}
|
|
1170
|
+
catch {
|
|
1171
|
+
/* already */
|
|
1172
|
+
}
|
|
1173
|
+
node.connect(ac.destination); // keep the media audible
|
|
1174
|
+
};
|
|
1175
|
+
if (m.paused)
|
|
1176
|
+
await m.play()?.catch?.(() => undefined);
|
|
1177
|
+
}
|
|
1178
|
+
else if (media && typeof media.getTracks === 'function') {
|
|
1179
|
+
src = ac.createMediaStreamSource(media);
|
|
1180
|
+
src.connect(analyser);
|
|
1181
|
+
}
|
|
1182
|
+
else
|
|
1183
|
+
throw new Error(`[use-scroll-animate] enableAudio: no audio source for ${String(input)}`);
|
|
1184
|
+
const freq = new Uint8Array(analyser.frequencyBinCount);
|
|
1185
|
+
const wave = new Uint8Array(analyser.fftSize);
|
|
1186
|
+
const self = {
|
|
1187
|
+
context: ac,
|
|
1188
|
+
analyser,
|
|
1189
|
+
sample: () => {
|
|
1190
|
+
analyser.getByteFrequencyData(freq);
|
|
1191
|
+
analyser.getByteTimeDomainData(wave);
|
|
1192
|
+
let sq = 0;
|
|
1193
|
+
for (let i = 0; i < wave.length; i++)
|
|
1194
|
+
sq += ((wave[i] - 128) / 128) ** 2;
|
|
1195
|
+
const n = Math.max(1, Math.round(freq.length * 0.08));
|
|
1196
|
+
let b = 0;
|
|
1197
|
+
for (let i = 0; i < n; i++)
|
|
1198
|
+
b += freq[i];
|
|
1199
|
+
return { level: Math.min(1, Math.sqrt(sq / wave.length) * 2), bass: b / n / 255, freq, wave };
|
|
1200
|
+
},
|
|
1201
|
+
stop: () => {
|
|
1202
|
+
if (current === self) {
|
|
1203
|
+
current = null;
|
|
1204
|
+
if (raf && typeof cancelAnimationFrame === 'function')
|
|
1205
|
+
cancelAnimationFrame(raf);
|
|
1206
|
+
raf = 0;
|
|
1207
|
+
}
|
|
1208
|
+
try {
|
|
1209
|
+
src.disconnect();
|
|
1210
|
+
analyser.disconnect();
|
|
1211
|
+
}
|
|
1212
|
+
catch {
|
|
1213
|
+
/* already */
|
|
1214
|
+
}
|
|
1215
|
+
release();
|
|
1216
|
+
setVars(0, 0);
|
|
1217
|
+
},
|
|
1218
|
+
};
|
|
1219
|
+
current = self;
|
|
1220
|
+
kick();
|
|
1221
|
+
return self;
|
|
1222
|
+
}
|
|
1223
|
+
/** Stop the current audio source (if any). */
|
|
1224
|
+
function disableAudio() {
|
|
1225
|
+
current?.stop();
|
|
1226
|
+
}
|
|
1227
|
+
/** A pure beat detector: feed it energy (0–1) and a timestamp per frame; it answers "beat?". */
|
|
1228
|
+
function createBeatDetector(o = {}) {
|
|
1229
|
+
const { threshold = 1.35, cooldown = 250, history = 43, floor = 0.08 } = o;
|
|
1230
|
+
const hist = [];
|
|
1231
|
+
let last = -Infinity;
|
|
1232
|
+
return (energy, now) => {
|
|
1233
|
+
const avg = hist.length ? hist.reduce((a, b) => a + b, 0) / hist.length : energy;
|
|
1234
|
+
const ready = hist.length >= 8;
|
|
1235
|
+
hist.push(energy);
|
|
1236
|
+
if (hist.length > history)
|
|
1237
|
+
hist.shift();
|
|
1238
|
+
if (ready && energy > floor && energy > avg * threshold && now - last >= cooldown) {
|
|
1239
|
+
last = now;
|
|
1240
|
+
return true;
|
|
1241
|
+
}
|
|
1242
|
+
return false;
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
const listeners = new Set();
|
|
1246
|
+
let raf = 0;
|
|
1247
|
+
function setVars(level, bass) {
|
|
1248
|
+
if (typeof document === 'undefined')
|
|
1249
|
+
return;
|
|
1250
|
+
const s = document.documentElement.style;
|
|
1251
|
+
s.setProperty('--usa-audio-level', level.toFixed(3));
|
|
1252
|
+
s.setProperty('--usa-audio-bass', bass.toFixed(3));
|
|
1253
|
+
}
|
|
1254
|
+
function loop(now) {
|
|
1255
|
+
raf = 0;
|
|
1256
|
+
if (!current)
|
|
1257
|
+
return;
|
|
1258
|
+
const { level, bass } = current.sample();
|
|
1259
|
+
const reduced = base.prefersReducedMotion();
|
|
1260
|
+
setVars(reduced ? 0 : level, reduced ? 0 : bass);
|
|
1261
|
+
for (const l of Array.from(listeners))
|
|
1262
|
+
if (l.detect(bass, now))
|
|
1263
|
+
l.cb({ energy: bass, time: now });
|
|
1264
|
+
kick();
|
|
1265
|
+
}
|
|
1266
|
+
function kick() {
|
|
1267
|
+
if (!raf && current && typeof requestAnimationFrame === 'function')
|
|
1268
|
+
raf = requestAnimationFrame(loop);
|
|
1269
|
+
}
|
|
1270
|
+
/** Call `cb` on every detected beat of the current audio source. Returns an unsubscribe. */
|
|
1271
|
+
function onBeat(cb, o = {}) {
|
|
1272
|
+
const l = { detect: createBeatDetector(o), cb };
|
|
1273
|
+
listeners.add(l);
|
|
1274
|
+
kick();
|
|
1275
|
+
return () => void listeners.delete(l);
|
|
1276
|
+
}
|
|
1277
|
+
/** Play the registered effect `name` on `el` at every beat (not under reduced motion). Returns an unbind. */
|
|
1278
|
+
function bindBeat(el, name, options = {}) {
|
|
1279
|
+
const { threshold, cooldown, history, floor, ...fx } = options;
|
|
1280
|
+
return onBeat(() => {
|
|
1281
|
+
if (!base.prefersReducedMotion())
|
|
1282
|
+
components_fx.playEffect(el, name, fx).catch(() => undefined);
|
|
1283
|
+
}, { threshold, cooldown, history, floor });
|
|
1284
|
+
}
|
|
1285
|
+
// --- visual effects ----------------------------------------------------------
|
|
1286
|
+
/** The current sample, or a gentle synthetic one while no audio is enabled. */
|
|
1287
|
+
function read(t, bins) {
|
|
1288
|
+
if (current)
|
|
1289
|
+
return { ...current.sample(), idle: false };
|
|
1290
|
+
const freq = new Uint8Array(bins);
|
|
1291
|
+
const wave = new Uint8Array(bins * 2);
|
|
1292
|
+
for (let i = 0; i < bins; i++)
|
|
1293
|
+
freq[i] = 70 + 50 * Math.sin(t * 2 + i * 0.35) * Math.cos(t * 0.7 + i * 0.11) * (1 - i / bins);
|
|
1294
|
+
for (let i = 0; i < wave.length; i++)
|
|
1295
|
+
wave[i] = 128 + 22 * Math.sin(t * 3 + i * 0.12);
|
|
1296
|
+
return { level: 0.25 + 0.1 * Math.sin(t * 2), bass: 0.35 + 0.2 * Math.sin(t * 2.4), freq, wave, idle: true };
|
|
1297
|
+
}
|
|
1298
|
+
const audioFx = (name, description, defaults, spec) => ({
|
|
1299
|
+
name,
|
|
1300
|
+
kind: 'background',
|
|
1301
|
+
description,
|
|
1302
|
+
reduced: 'skip',
|
|
1303
|
+
defaults: { colors: PALETTE, background: '#0b0d12', speed: 1, quality: 1, ...defaults },
|
|
1304
|
+
run: (el, o, ctx) => canvasBackground(el, ctx, spec, o),
|
|
1305
|
+
});
|
|
1306
|
+
const AUDIO_FX = [
|
|
1307
|
+
audioFx('spectrum-bars', 'Frequency bars dance to the audio (mirror them with `mirror: true`).', { bars: 48, gap: 2, mirror: false }, {
|
|
1308
|
+
draw: ({ ctx, w, h, t, o }) => {
|
|
1309
|
+
ctx.fillStyle = o.background;
|
|
1310
|
+
ctx.fillRect(0, 0, w, h);
|
|
1311
|
+
const s = read(t, o.bars);
|
|
1312
|
+
const per = Math.max(1, Math.floor((s.freq.length * 0.7) / o.bars));
|
|
1313
|
+
const bw = w / o.bars;
|
|
1314
|
+
for (let i = 0; i < o.bars; i++) {
|
|
1315
|
+
let v = 0;
|
|
1316
|
+
for (let k = 0; k < per; k++)
|
|
1317
|
+
v += s.freq[Math.min(s.freq.length - 1, i * per + k)];
|
|
1318
|
+
const bh = (v / per / 255) * h * (o.mirror ? 0.5 : 0.9);
|
|
1319
|
+
ctx.fillStyle = o.colors[i % o.colors.length];
|
|
1320
|
+
ctx.fillRect(i * bw + o.gap / 2, o.mirror ? h / 2 - bh : h - bh, Math.max(1, bw - o.gap), o.mirror ? bh * 2 : bh);
|
|
1321
|
+
}
|
|
1322
|
+
},
|
|
1323
|
+
}),
|
|
1324
|
+
audioFx('pulse-ring', 'Glowing rings pulse with the bass.', { rings: 3, color: '#7c5cff' }, {
|
|
1325
|
+
draw: ({ ctx, w, h, t, o }) => {
|
|
1326
|
+
ctx.fillStyle = o.background;
|
|
1327
|
+
ctx.fillRect(0, 0, w, h);
|
|
1328
|
+
const s = read(t, 32);
|
|
1329
|
+
const [r, g, b] = hexRgb(o.color);
|
|
1330
|
+
const base = Math.min(w, h) * 0.18;
|
|
1331
|
+
for (let i = 0; i < o.rings; i++) {
|
|
1332
|
+
const rad = base * (1 + i * 0.55) * (1 + s.bass * 0.6);
|
|
1333
|
+
ctx.strokeStyle = `rgba(${r},${g},${b},${(0.9 - i * 0.25) * (0.4 + s.level)})`;
|
|
1334
|
+
ctx.lineWidth = 3 + s.bass * 10 - i;
|
|
1335
|
+
ctx.beginPath();
|
|
1336
|
+
ctx.arc(w / 2, h / 2, rad, 0, Math.PI * 2);
|
|
1337
|
+
ctx.stroke();
|
|
1338
|
+
}
|
|
1339
|
+
},
|
|
1340
|
+
}),
|
|
1341
|
+
audioFx('wave-ring', 'The live waveform wrapped into a circle.', { color: '#22d3ee', amplitude: 0.35 }, {
|
|
1342
|
+
draw: ({ ctx, w, h, t, o }) => {
|
|
1343
|
+
ctx.fillStyle = o.background;
|
|
1344
|
+
ctx.fillRect(0, 0, w, h);
|
|
1345
|
+
const s = read(t, 64);
|
|
1346
|
+
const n = s.wave.length;
|
|
1347
|
+
const base = Math.min(w, h) * 0.28;
|
|
1348
|
+
ctx.strokeStyle = o.color;
|
|
1349
|
+
ctx.lineWidth = 2;
|
|
1350
|
+
ctx.beginPath();
|
|
1351
|
+
for (let i = 0; i <= n; i++) {
|
|
1352
|
+
const a = (i / n) * Math.PI * 2;
|
|
1353
|
+
const rad = base * (1 + ((s.wave[i % n] - 128) / 128) * o.amplitude * 2);
|
|
1354
|
+
const x = w / 2 + Math.cos(a) * rad;
|
|
1355
|
+
const y = h / 2 + Math.sin(a) * rad;
|
|
1356
|
+
if (i)
|
|
1357
|
+
ctx.lineTo(x, y);
|
|
1358
|
+
else
|
|
1359
|
+
ctx.moveTo(x, y);
|
|
1360
|
+
}
|
|
1361
|
+
ctx.closePath();
|
|
1362
|
+
ctx.stroke();
|
|
1363
|
+
},
|
|
1364
|
+
}),
|
|
1365
|
+
];
|
|
1366
|
+
/**
|
|
1367
|
+
* `<usa-audio source="#track | mic" label="…">` — a toggle button (yours, as
|
|
1368
|
+
* `[data-audio-toggle]`, or one it renders) that enables the audio source on
|
|
1369
|
+
* click; children with `data-usa-beat="effect"` play that effect on every
|
|
1370
|
+
* beat (`data-usa-beat-options` JSON; `threshold` / `cooldown` attributes).
|
|
1371
|
+
* Emits `usa-beat` and `usa-audio-error`.
|
|
1372
|
+
*/
|
|
1373
|
+
function defineAudio(tag = 'usa-audio') {
|
|
1374
|
+
return base.defineElement(tag, (Base) => class UsaAudio extends Base {
|
|
1375
|
+
constructor() {
|
|
1376
|
+
super(...arguments);
|
|
1377
|
+
this.active = false;
|
|
1378
|
+
this.audio = null;
|
|
1379
|
+
this.off = null;
|
|
1380
|
+
}
|
|
1381
|
+
static get observedAttributes() {
|
|
1382
|
+
return ['source'];
|
|
1383
|
+
}
|
|
1384
|
+
button() {
|
|
1385
|
+
let b = this.querySelector('[data-audio-toggle]');
|
|
1386
|
+
if (!b) {
|
|
1387
|
+
b = document.createElement('button');
|
|
1388
|
+
b.type = 'button';
|
|
1389
|
+
b.setAttribute('data-audio-toggle', '');
|
|
1390
|
+
b.textContent = this.str('label', '🔊 Enable sound-reactive effects');
|
|
1391
|
+
this.prepend(b);
|
|
1392
|
+
this.onCleanup(() => b.remove());
|
|
1393
|
+
}
|
|
1394
|
+
return b;
|
|
1395
|
+
}
|
|
1396
|
+
async toggle() {
|
|
1397
|
+
const b = this.button();
|
|
1398
|
+
if (this.active)
|
|
1399
|
+
return this.stop();
|
|
1400
|
+
try {
|
|
1401
|
+
this.audio = await enableAudio(this.str('source', 'mic'));
|
|
1402
|
+
this.active = true;
|
|
1403
|
+
this.removeAttribute('data-audio-error');
|
|
1404
|
+
b.setAttribute('aria-pressed', 'true');
|
|
1405
|
+
this.off = onBeat((d) => {
|
|
1406
|
+
this.dispatchEvent(new CustomEvent('usa-beat', { detail: d, bubbles: true }));
|
|
1407
|
+
if (base.prefersReducedMotion())
|
|
1408
|
+
return;
|
|
1409
|
+
this.querySelectorAll('[data-usa-beat]').forEach((el) => {
|
|
1410
|
+
let opts = {};
|
|
1411
|
+
try {
|
|
1412
|
+
opts = JSON.parse(el.dataset.usaBeatOptions || '{}') || {};
|
|
1413
|
+
}
|
|
1414
|
+
catch {
|
|
1415
|
+
/* ignore bad JSON */
|
|
1416
|
+
}
|
|
1417
|
+
components_fx.playEffect(el, el.dataset.usaBeat || 'pulse', opts).catch(() => undefined);
|
|
1418
|
+
});
|
|
1419
|
+
}, { threshold: this.num('threshold', 1.35), cooldown: this.num('cooldown', 250) });
|
|
1420
|
+
}
|
|
1421
|
+
catch (err) {
|
|
1422
|
+
this.setAttribute('data-audio-error', '');
|
|
1423
|
+
this.dispatchEvent(new CustomEvent('usa-audio-error', { detail: { error: err }, bubbles: true }));
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
stop() {
|
|
1427
|
+
this.off?.();
|
|
1428
|
+
this.off = null;
|
|
1429
|
+
if (this.audio && current === this.audio)
|
|
1430
|
+
this.audio.stop();
|
|
1431
|
+
this.audio = null;
|
|
1432
|
+
this.active = false;
|
|
1433
|
+
this.querySelector('[data-audio-toggle]')?.setAttribute('aria-pressed', 'false');
|
|
1434
|
+
}
|
|
1435
|
+
mount() {
|
|
1436
|
+
const b = this.button();
|
|
1437
|
+
b.setAttribute('aria-pressed', 'false');
|
|
1438
|
+
this.listen(b, 'click', () => void this.toggle());
|
|
1439
|
+
this.onCleanup(() => this.stop());
|
|
1440
|
+
}
|
|
1441
|
+
}, { id: 'usa-audio', text: 'usa-audio{display:block}usa-audio[data-audio-error] [data-audio-toggle]{outline:2px solid #ff5c8a}' });
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1118
1444
|
/**
|
|
1119
1445
|
* use-scroll-animate/components/effects — the 5.x effect packs, all
|
|
1120
1446
|
* registered through `registerEffect()` (5.0) and playable with
|
|
@@ -1133,6 +1459,7 @@ const EFFECT_PACKS = {
|
|
|
1133
1459
|
physics: PHYSICS_FX,
|
|
1134
1460
|
page: PAGE_FX,
|
|
1135
1461
|
generative: GENERATIVE_FX,
|
|
1462
|
+
audio: AUDIO_FX,
|
|
1136
1463
|
};
|
|
1137
1464
|
/** 5.1: card & click effects 2.0. */
|
|
1138
1465
|
function registerCardClickEffects() {
|
|
@@ -1150,9 +1477,14 @@ function registerPageEffects() {
|
|
|
1150
1477
|
function registerGenerativeEffects() {
|
|
1151
1478
|
components_fx.registerEffects(EFFECT_PACKS.generative);
|
|
1152
1479
|
}
|
|
1480
|
+
/** 5.6: sound-reactive (Web Audio) backgrounds. */
|
|
1481
|
+
function registerAudioEffects() {
|
|
1482
|
+
components_fx.registerEffects(EFFECT_PACKS.audio);
|
|
1483
|
+
}
|
|
1153
1484
|
/** Define the 5.x elements of this entry (`<usa-story>`, …) under their default tags. */
|
|
1154
1485
|
function defineEffectElements() {
|
|
1155
1486
|
defineStory();
|
|
1487
|
+
defineAudio();
|
|
1156
1488
|
}
|
|
1157
1489
|
/** Register the built-ins and every pack (idempotent). */
|
|
1158
1490
|
function registerAllEffects() {
|
|
@@ -1161,6 +1493,7 @@ function registerAllEffects() {
|
|
|
1161
1493
|
components_fx.registerEffects(defs);
|
|
1162
1494
|
}
|
|
1163
1495
|
|
|
1496
|
+
exports.AUDIO_FX = AUDIO_FX;
|
|
1164
1497
|
exports.CARD_FX = CARD_FX;
|
|
1165
1498
|
exports.CLICK_FX = CLICK_FX;
|
|
1166
1499
|
exports.EFFECT_PACKS = EFFECT_PACKS;
|
|
@@ -1168,15 +1501,23 @@ exports.GENERATIVE_FX = GENERATIVE_FX;
|
|
|
1168
1501
|
exports.PAGE_FX = PAGE_FX;
|
|
1169
1502
|
exports.PHYSICS_FX = PHYSICS_FX;
|
|
1170
1503
|
exports.STORY_TEMPLATES = STORY_TEMPLATES;
|
|
1504
|
+
exports.bindBeat = bindBeat;
|
|
1171
1505
|
exports.bounceKeyframes = bounceKeyframes;
|
|
1172
1506
|
exports.canvasBackground = canvasBackground;
|
|
1507
|
+
exports.createBeatDetector = createBeatDetector;
|
|
1508
|
+
exports.defineAudio = defineAudio;
|
|
1173
1509
|
exports.defineEffectElements = defineEffectElements;
|
|
1174
1510
|
exports.defineStory = defineStory;
|
|
1511
|
+
exports.disableAudio = disableAudio;
|
|
1512
|
+
exports.enableAudio = enableAudio;
|
|
1175
1513
|
exports.formatCount = formatCount;
|
|
1176
1514
|
exports.fxLayer = fxLayer;
|
|
1515
|
+
exports.getAudio = getAudio;
|
|
1177
1516
|
exports.hexRgb = hexRgb;
|
|
1178
1517
|
exports.noise2 = noise2;
|
|
1518
|
+
exports.onBeat = onBeat;
|
|
1179
1519
|
exports.registerAllEffects = registerAllEffects;
|
|
1520
|
+
exports.registerAudioEffects = registerAudioEffects;
|
|
1180
1521
|
exports.registerCardClickEffects = registerCardClickEffects;
|
|
1181
1522
|
exports.registerGenerativeEffects = registerGenerativeEffects;
|
|
1182
1523
|
exports.registerPageEffects = registerPageEffects;
|