fancoolo-fx 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +196 -0
  2. package/package.json +12 -0
  3. package/src/fx.js +386 -0
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # @fancoolo/fx — Fancoolo FX
2
+
3
+ A class-driven GSAP animation wrapper for WordPress and static sites. Add a CSS class to any element and it animates — no JavaScript needed per page.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Load GSAP + plugins + FX as separate script tags — no build step needed:
14
+
15
+ ```html
16
+ <!-- 1. GSAP core -->
17
+ <script src="node_modules/gsap/dist/gsap.min.js"></script>
18
+ <!-- 2. GSAP plugins -->
19
+ <script src="node_modules/gsap/dist/ScrollTrigger.min.js"></script>
20
+ <script src="node_modules/gsap/dist/SplitText.min.js"></script>
21
+ <!-- 3. Fancoolo FX -->
22
+ <script src="src/fx.js"></script>
23
+ ```
24
+
25
+ Then add classes in your HTML. Done. FX auto-initializes on DOMContentLoaded.
26
+
27
+ ## Quick Start
28
+
29
+ ```html
30
+ <h1 class="fx-text-reveal-pl">Hello World</h1>
31
+ ```
32
+
33
+ The heading animates with a masked line-reveal on page load.
34
+
35
+ ## Available Effects
36
+
37
+ | Effect | Page Load | Scroll Trigger | Description |
38
+ |--------|-----------|----------------|-------------|
39
+ | Text Reveal | `.fx-text-reveal-pl` | `.fx-text-reveal-st` | Split lines, mask, slide up |
40
+ | Reveal | `.fx-reveal-pl` | `.fx-reveal-st` | Slide up + fade |
41
+ | Spin Reveal | `.fx-spin-reveal-pl` | `.fx-spin-reveal-st` | Rotate + scale in |
42
+ | BG Reveal | `.fx-bg-reveal-pl` | `.fx-bg-reveal-st` | Background slide up |
43
+ | Scale In | `.fx-scale-in-pl` | `.fx-scale-in-st` | Scale up + fade |
44
+
45
+ **Three trigger modes:**
46
+ - `-pl` — **Page load**: animates when the DOM is ready
47
+ - `-st` — **Scroll trigger**: animates when the element enters the viewport
48
+ - **No suffix** — **Section trigger**: bare `.fx-text-reveal` inside a `<section>` is auto scroll-triggered using the section as the trigger
49
+
50
+ ## How Scroll Triggering Works
51
+
52
+ When FX sees a scroll-triggered element (`-st` suffix or bare class inside a section), it creates a GSAP ScrollTrigger with these defaults:
53
+
54
+ - **`start: 'top 85%'`** — the animation fires when the top of the element (or its section) reaches 85% down from the top of the viewport
55
+ - **`once: true`** — plays once, doesn't replay on re-scroll
56
+
57
+ For grouped siblings (same class, same parent), the parent is used as the shared trigger — so all items animate together with stagger, rather than each triggering independently.
58
+
59
+ ## Section Auto-Trigger
60
+
61
+ Elements with bare `.fx-*` classes (no `-pl`/`-st` suffix) inside a `<section>` are automatically scroll-triggered using the section as the trigger:
62
+
63
+ ```html
64
+ <section>
65
+ <h2 class="fx-text-reveal">This auto-triggers on scroll</h2>
66
+ <p class="fx-text-reveal">No suffix needed inside a section</p>
67
+ <img src="photo.jpg" class="fx-reveal" />
68
+ </section>
69
+ ```
70
+
71
+ Change the container selector via config:
72
+
73
+ ```js
74
+ FX.config.sectionSelector = '.animate-section'; // only sections with this class
75
+ FX.config.sectionSelector = 'section, .wp-block-group'; // multiple selectors
76
+ ```
77
+
78
+ ## Tag-Based Auto-Animation
79
+
80
+ For zero-class animation, configure `tagMap` to automatically animate elements by their tag name inside sections:
81
+
82
+ ```html
83
+ <!-- Set config BEFORE the FX script loads -->
84
+ <script>
85
+ window.__FX_CONFIG__ = {
86
+ tagMap: {
87
+ 'h1,h2,h3,h4,h5,h6': 'textReveal',
88
+ 'p,blockquote': 'textReveal',
89
+ 'img,video': 'reveal',
90
+ }
91
+ };
92
+ </script>
93
+ <script src="dist/fx.min.js"></script>
94
+ ```
95
+
96
+ Or configure after load and re-init:
97
+
98
+ ```js
99
+ FX.config.tagMap = { 'h1,h2,h3': 'textReveal', 'img': 'reveal' };
100
+ FX.init();
101
+ ```
102
+
103
+ Elements already animated by explicit `.fx-*` classes are skipped — tagMap only picks up unhandled elements.
104
+
105
+ ## Auto-Stagger
106
+
107
+ Sibling elements with the same class are automatically staggered (0.15s between each):
108
+
109
+ ```html
110
+ <div>
111
+ <p class="fx-text-reveal-st">First paragraph</p>
112
+ <p class="fx-text-reveal-st">Second paragraph</p>
113
+ <p class="fx-text-reveal-st">Third paragraph</p>
114
+ </div>
115
+ ```
116
+
117
+ ## Modifier Classes
118
+
119
+ Override timing per-element using modifier classes (Gutenberg-friendly — no inline styles needed):
120
+
121
+ | Class | Default | Description |
122
+ |-------|---------|-------------|
123
+ | `fx-duration-[n]` | `1.2` (text) / `1` (others) | Animation duration in seconds |
124
+ | `fx-delay-[n]` | `0` | Start delay in seconds |
125
+ | `fx-stagger-[n]` | `0.1` | Delay between staggered items |
126
+ | `fx-ease-[name]` | `power3.out` | GSAP easing function |
127
+
128
+ ```html
129
+ <h2 class="fx-text-reveal-st fx-duration-[2] fx-stagger-[0.25]">
130
+ Slower and wider stagger
131
+ </h2>
132
+ ```
133
+
134
+ Add these in Gutenberg via the "Additional CSS class(es)" field alongside the effect class.
135
+
136
+ ## JavaScript API
137
+
138
+ For compound sequences or dynamic content, use the `FX` global:
139
+
140
+ ```js
141
+ FX.textReveal(document.querySelector('.hero-title'), {
142
+ trigger: 'scroll',
143
+ delay: 0.3,
144
+ scrollTrigger: { trigger: '.hero-section' }
145
+ });
146
+ ```
147
+
148
+ ### API Reference
149
+
150
+ All functions accept `(element, options)`:
151
+
152
+ | Function | Options |
153
+ |----------|---------|
154
+ | `textReveal(el, opts)` | `duration`, `ease`, `stagger`, `delay`, `trigger`, `scrollTrigger` |
155
+ | `reveal(el, opts)` | `y` (default 80), `duration`, `ease`, `delay`, `trigger`, `scrollTrigger` |
156
+ | `spinReveal(el, opts)` | `rotation` (default -30), `scale` (default 0.9), `duration`, `ease`, `delay`, `trigger`, `scrollTrigger` |
157
+ | `bgReveal(el, opts)` | `duration`, `ease`, `delay`, `trigger`, `scrollTrigger` |
158
+ | `scaleIn(el, opts)` | `scale` (default 0.92), `duration`, `ease`, `delay`, `trigger`, `scrollTrigger` |
159
+
160
+ Set `trigger: 'scroll'` to enable ScrollTrigger. Pass `scrollTrigger: { trigger: someEl }` to use a different trigger element.
161
+
162
+ ## Using in a New Project
163
+
164
+ 1. Copy this repo (or `npm install`)
165
+ 2. Add the 4 script tags (gsap, ScrollTrigger, SplitText, fx.js)
166
+ 3. Add `.fx-*` classes in your HTML
167
+
168
+ For compound sequences, create a project-specific JS file loaded after fx.js:
169
+
170
+ ```js
171
+ // animations.js — loaded after fx.js
172
+ document.addEventListener('DOMContentLoaded', function () {
173
+ var hero = document.querySelector('.hero');
174
+ if (hero) {
175
+ FX.scaleIn(hero.querySelector('.card'), { trigger: 'scroll', scrollTrigger: { trigger: hero } });
176
+ FX.textReveal(hero.querySelector('h2'), { trigger: 'scroll', delay: 0.2, scrollTrigger: { trigger: hero } });
177
+ }
178
+ });
179
+ ```
180
+
181
+ ## File Structure
182
+
183
+ ```
184
+ ├── package.json ← npm deps (gsap)
185
+ ├── node_modules/gsap/dist/ ← GSAP core + plugins (loaded via script tags)
186
+ ├── src/fx.js ← Fancoolo FX
187
+ ├── example/
188
+ │ ├── index.html ← Demo page
189
+ │ └── src/animations.js ← Sample project-specific code
190
+ ├── CLAUDE.md ← Project context for Claude
191
+ └── README.md
192
+ ```
193
+
194
+ ## WordPress / Gutenberg
195
+
196
+ Fancoolo FX uses CSS classes which you can add via the "Additional CSS class(es)" field in the block sidebar. No data attributes or inline styles needed.
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "fancoolo-fx",
3
+ "version": "1.0.0",
4
+ "description": "A class-driven GSAP animation wrapper for WordPress and static sites.",
5
+ "main": "src/fx.js",
6
+ "keywords": ["gsap", "animation", "scrolltrigger", "splittext", "wordpress", "gutenberg"],
7
+ "author": "",
8
+ "license": "ISC",
9
+ "dependencies": {
10
+ "gsap": "^3.14.2"
11
+ }
12
+ }
package/src/fx.js ADDED
@@ -0,0 +1,386 @@
1
+ /**
2
+ * Fancoolo FX — A class-driven GSAP animation wrapper
3
+ *
4
+ * Load GSAP + plugins BEFORE this file:
5
+ * <script src="node_modules/gsap/dist/gsap.min.js"></script>
6
+ * <script src="node_modules/gsap/dist/ScrollTrigger.min.js"></script>
7
+ * <script src="node_modules/gsap/dist/SplitText.min.js"></script>
8
+ * <script src="src/fx.js"></script>
9
+ *
10
+ * Three ways to trigger animations:
11
+ *
12
+ * 1. Explicit classes with trigger suffix:
13
+ * .fx-text-reveal-pl (page load)
14
+ * .fx-text-reveal-st (scroll trigger)
15
+ *
16
+ * 2. Bare classes inside <section> — auto scroll-triggered:
17
+ * <section>
18
+ * <h2 class="fx-text-reveal">Auto scroll-triggered by section</h2>
19
+ * </section>
20
+ *
21
+ * 3. Tag-based auto-animation (zero classes):
22
+ * FX.config.tagMap = { 'h1,h2,h3': 'textReveal', 'img': 'reveal' }
23
+ *
24
+ * Modifier classes (Gutenberg-friendly):
25
+ * .fx-duration-[1.5] .fx-delay-[0.3] .fx-stagger-[0.2] .fx-ease-[power2.inOut]
26
+ *
27
+ * JS API:
28
+ * FX.textReveal(el, { trigger: 'scroll', delay: 0.2 })
29
+ */
30
+ (function () {
31
+ 'use strict';
32
+
33
+ if (typeof gsap === 'undefined' || typeof SplitText === 'undefined' || typeof ScrollTrigger === 'undefined') {
34
+ console.error('[FX] Missing dependencies. Load gsap, ScrollTrigger, and SplitText before fx.js');
35
+ return;
36
+ }
37
+
38
+ gsap.registerPlugin(ScrollTrigger, SplitText);
39
+
40
+ // ── Config ──────────────────────────────────
41
+
42
+ var config = {
43
+ /**
44
+ * CSS selector for section containers.
45
+ * Elements with bare .fx-* classes (no -pl/-st suffix) inside matching
46
+ * containers are auto scroll-triggered using the container as trigger.
47
+ */
48
+ sectionSelector: 'section',
49
+
50
+ /**
51
+ * Default ScrollTrigger start position.
52
+ * Format: "triggerPosition viewportPosition"
53
+ * Examples: 'top 85%', 'top center', 'top 90%', 'center center'
54
+ * See: https://gsap.com/docs/v3/Plugins/ScrollTrigger/
55
+ */
56
+ scrollStart: 'top 85%',
57
+
58
+ /**
59
+ * Whether scroll-triggered animations play only once.
60
+ * Set to false to replay every time the element enters the viewport.
61
+ */
62
+ scrollOnce: true,
63
+
64
+ /**
65
+ * Map of CSS selectors → effect names for zero-class auto-animation.
66
+ * Elements matching these selectors inside sections get animated automatically.
67
+ * Set to null/false to disable. Override before DOMContentLoaded or call FX.init().
68
+ *
69
+ * Example:
70
+ * FX.config.tagMap = {
71
+ * 'h1,h2,h3,h4,h5,h6': 'textReveal',
72
+ * 'p,blockquote': 'textReveal',
73
+ * 'img,video': 'reveal',
74
+ * }
75
+ */
76
+ tagMap: null,
77
+ };
78
+
79
+ // ── Defaults ────────────────────────────────
80
+
81
+ var EFFECT_DEFAULTS = {
82
+ textReveal: { duration: 1.2, ease: 'power3.out', stagger: 0.1 },
83
+ reveal: { duration: 1, ease: 'power3.out' },
84
+ spinReveal: { duration: 1.4, ease: 'power3.out' },
85
+ bgReveal: { duration: 1, ease: 'power3.out' },
86
+ scaleIn: { duration: 1, ease: 'power3.out' },
87
+ };
88
+
89
+ // ── Helpers ──────────────────────────────────
90
+
91
+ function getClassModifier(el, name, fallback) {
92
+ var prefix = 'fx-' + name + '-[';
93
+ for (var i = 0; i < el.classList.length; i++) {
94
+ var cls = el.classList[i];
95
+ if (cls.indexOf(prefix) === 0 && cls.charAt(cls.length - 1) === ']') {
96
+ var val = cls.slice(prefix.length, -1);
97
+ var num = parseFloat(val);
98
+ return isNaN(num) ? val : num;
99
+ }
100
+ }
101
+ return fallback;
102
+ }
103
+
104
+ function resolveOptions(el, effectName, overrides) {
105
+ var d = EFFECT_DEFAULTS[effectName];
106
+ return {
107
+ duration: getClassModifier(el, 'duration', overrides.duration != null ? overrides.duration : d.duration),
108
+ ease: getClassModifier(el, 'ease', overrides.ease != null ? overrides.ease : d.ease),
109
+ stagger: getClassModifier(el, 'stagger', overrides.stagger != null ? overrides.stagger : (d.stagger || 0)),
110
+ delay: getClassModifier(el, 'delay', overrides.delay != null ? overrides.delay : 0),
111
+ };
112
+ }
113
+
114
+ function buildScrollTrigger(el, scrollTriggerOpts) {
115
+ var defaults = {
116
+ start: config.scrollStart,
117
+ once: config.scrollOnce,
118
+ };
119
+ var st = { trigger: scrollTriggerOpts.trigger || el };
120
+ for (var key in defaults) st[key] = defaults[key];
121
+ for (var key2 in scrollTriggerOpts) st[key2] = scrollTriggerOpts[key2];
122
+
123
+ // Per-element override: fx-start-[top center] or fx-start-[top 70%]
124
+ var startOverride = getClassModifier(el, 'start', null);
125
+ if (startOverride !== null) st.start = startOverride;
126
+
127
+ return st;
128
+ }
129
+
130
+ // ── Effects ──────────────────────────────────
131
+
132
+ function textReveal(el, opts) {
133
+ opts = opts || {};
134
+ var o = resolveOptions(el, 'textReveal', opts);
135
+
136
+ var split = new SplitText(el, { type: 'lines', linesClass: 'line-wrapper' });
137
+
138
+ split.lines.forEach(function (line) {
139
+ var wrapper = document.createElement('div');
140
+ wrapper.style.overflow = 'hidden';
141
+ line.parentNode.insertBefore(wrapper, line);
142
+ wrapper.appendChild(line);
143
+ });
144
+
145
+ var tweenVars = {
146
+ y: '100%',
147
+ opacity: 0,
148
+ duration: o.duration,
149
+ ease: o.ease,
150
+ stagger: o.stagger,
151
+ delay: o.delay,
152
+ onComplete: function () {
153
+ split.lines.forEach(function (line) {
154
+ line.style.transform = '';
155
+ line.style.opacity = '';
156
+ });
157
+ },
158
+ };
159
+
160
+ if (opts.trigger === 'scroll' || opts.scrollTrigger) {
161
+ tweenVars.scrollTrigger = buildScrollTrigger(el, opts.scrollTrigger || {});
162
+ }
163
+
164
+ gsap.from(split.lines, tweenVars);
165
+ }
166
+
167
+ function reveal(el, opts) {
168
+ opts = opts || {};
169
+ var o = resolveOptions(el, 'reveal', opts);
170
+
171
+ var tweenVars = {
172
+ y: opts.y != null ? opts.y : 80,
173
+ opacity: 0,
174
+ duration: o.duration,
175
+ ease: o.ease,
176
+ delay: o.delay,
177
+ };
178
+
179
+ if (opts.trigger === 'scroll' || opts.scrollTrigger) {
180
+ tweenVars.scrollTrigger = buildScrollTrigger(el, opts.scrollTrigger || {});
181
+ }
182
+
183
+ gsap.from(el, tweenVars);
184
+ }
185
+
186
+ function spinReveal(el, opts) {
187
+ opts = opts || {};
188
+ var o = resolveOptions(el, 'spinReveal', opts);
189
+
190
+ var tweenVars = {
191
+ rotation: opts.rotation != null ? opts.rotation : -30,
192
+ scale: opts.scale != null ? opts.scale : 0.9,
193
+ opacity: 0,
194
+ duration: o.duration,
195
+ ease: o.ease,
196
+ delay: o.delay,
197
+ };
198
+
199
+ if (opts.trigger === 'scroll' || opts.scrollTrigger) {
200
+ tweenVars.scrollTrigger = buildScrollTrigger(el, opts.scrollTrigger || {});
201
+ }
202
+
203
+ gsap.from(el, tweenVars);
204
+ }
205
+
206
+ function bgReveal(el, opts) {
207
+ opts = opts || {};
208
+ var o = resolveOptions(el, 'bgReveal', opts);
209
+
210
+ var tweenVars = {
211
+ y: '100%',
212
+ opacity: 0,
213
+ duration: o.duration,
214
+ ease: o.ease,
215
+ delay: o.delay,
216
+ };
217
+
218
+ if (opts.trigger === 'scroll' || opts.scrollTrigger) {
219
+ tweenVars.scrollTrigger = buildScrollTrigger(el, opts.scrollTrigger || {});
220
+ }
221
+
222
+ gsap.from(el, tweenVars);
223
+ }
224
+
225
+ function scaleIn(el, opts) {
226
+ opts = opts || {};
227
+ var o = resolveOptions(el, 'scaleIn', opts);
228
+
229
+ var tweenVars = {
230
+ scale: opts.scale != null ? opts.scale : 0.92,
231
+ opacity: 0,
232
+ duration: o.duration,
233
+ ease: o.ease,
234
+ delay: o.delay,
235
+ };
236
+
237
+ if (opts.trigger === 'scroll' || opts.scrollTrigger) {
238
+ tweenVars.scrollTrigger = buildScrollTrigger(el, opts.scrollTrigger || {});
239
+ }
240
+
241
+ gsap.from(el, tweenVars);
242
+ }
243
+
244
+ // ── Class-to-effect mapping ─────────────────
245
+
246
+ var effects = {
247
+ 'fx-text-reveal': textReveal,
248
+ 'fx-reveal': reveal,
249
+ 'fx-spin-reveal': spinReveal,
250
+ 'fx-bg-reveal': bgReveal,
251
+ 'fx-scale-in': scaleIn,
252
+ };
253
+
254
+ var effectsByName = {
255
+ textReveal: textReveal,
256
+ reveal: reveal,
257
+ spinReveal: spinReveal,
258
+ bgReveal: bgReveal,
259
+ scaleIn: scaleIn,
260
+ };
261
+
262
+ // ── Helpers ──────────────────────────────────
263
+
264
+ function groupByParent(nodeList) {
265
+ var map = new Map();
266
+ nodeList.forEach(function (el) {
267
+ var parent = el.parentElement;
268
+ if (!map.has(parent)) map.set(parent, []);
269
+ map.get(parent).push(el);
270
+ });
271
+ var groups = [];
272
+ map.forEach(function (arr) { groups.push(arr); });
273
+ return groups;
274
+ }
275
+
276
+ function applyScrollGroup(fn, group, triggerEl) {
277
+ group.forEach(function (el, i) {
278
+ fn(el, {
279
+ trigger: 'scroll',
280
+ delay: i * 0.15,
281
+ scrollTrigger: { trigger: triggerEl },
282
+ });
283
+ });
284
+ }
285
+
286
+ function camelToKebab(str) {
287
+ return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
288
+ }
289
+
290
+ // ── Init ────────────────────────────────────
291
+
292
+ function init() {
293
+ var processed = new Set();
294
+
295
+ Object.keys(effects).forEach(function (name) {
296
+ var fn = effects[name];
297
+
298
+ // 1. Page-load variant: .fx-<name>-pl
299
+ var plGroups = groupByParent(document.querySelectorAll('.' + name + '-pl'));
300
+ plGroups.forEach(function (group) {
301
+ group.forEach(function (el, i) {
302
+ fn(el, { delay: i * 0.15 });
303
+ processed.add(el);
304
+ });
305
+ });
306
+
307
+ // 2. Explicit scroll-trigger variant: .fx-<name>-st
308
+ var stGroups = groupByParent(document.querySelectorAll('.' + name + '-st'));
309
+ stGroups.forEach(function (group) {
310
+ var triggerEl = group[0].parentElement || group[0];
311
+ applyScrollGroup(fn, group, triggerEl);
312
+ group.forEach(function (el) { processed.add(el); });
313
+ });
314
+
315
+ // 3. Bare class inside a section: .fx-<name> (no suffix)
316
+ if (config.sectionSelector) {
317
+ document.querySelectorAll(config.sectionSelector).forEach(function (section) {
318
+ var bareEls = Array.from(section.querySelectorAll('.' + name))
319
+ .filter(function (el) { return !processed.has(el); });
320
+ if (bareEls.length === 0) return;
321
+
322
+ var groups = groupByParent(bareEls);
323
+ groups.forEach(function (group) {
324
+ applyScrollGroup(fn, group, section);
325
+ group.forEach(function (el) { processed.add(el); });
326
+ });
327
+ });
328
+ }
329
+ });
330
+
331
+ // 4. Tag-based auto-animation inside sections
332
+ if (config.tagMap && config.sectionSelector) {
333
+ document.querySelectorAll(config.sectionSelector).forEach(function (section) {
334
+ Object.keys(config.tagMap).forEach(function (selector) {
335
+ var effectName = config.tagMap[selector];
336
+ var fn = effects['fx-' + camelToKebab(effectName)] || effectsByName[effectName];
337
+ if (!fn) return;
338
+
339
+ var els = Array.from(section.querySelectorAll(selector))
340
+ .filter(function (el) { return !processed.has(el); });
341
+ if (els.length === 0) return;
342
+
343
+ var groups = groupByParent(els);
344
+ groups.forEach(function (group) {
345
+ applyScrollGroup(fn, group, section);
346
+ group.forEach(function (el) { processed.add(el); });
347
+ });
348
+ });
349
+ });
350
+ }
351
+ }
352
+
353
+ // ── Boot ────────────────────────────────────
354
+
355
+ function applyPreConfig() {
356
+ var pre = window.__FX_CONFIG__;
357
+ if (!pre) return;
358
+ if (pre.sectionSelector !== undefined) config.sectionSelector = pre.sectionSelector;
359
+ if (pre.scrollStart !== undefined) config.scrollStart = pre.scrollStart;
360
+ if (pre.scrollOnce !== undefined) config.scrollOnce = pre.scrollOnce;
361
+ if (pre.tagMap !== undefined) config.tagMap = pre.tagMap;
362
+ }
363
+
364
+ function boot() {
365
+ applyPreConfig();
366
+ init();
367
+ }
368
+
369
+ if (document.readyState === 'loading') {
370
+ document.addEventListener('DOMContentLoaded', boot);
371
+ } else {
372
+ boot();
373
+ }
374
+
375
+ // ── Public API ──────────────────────────────
376
+
377
+ window.FX = {
378
+ config: config,
379
+ textReveal: textReveal,
380
+ reveal: reveal,
381
+ spinReveal: spinReveal,
382
+ bgReveal: bgReveal,
383
+ scaleIn: scaleIn,
384
+ init: init,
385
+ };
386
+ })();