fancoolo-fx 1.6.1 → 1.7.1
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/README.md +36 -1
- package/package.json +1 -1
- package/readme.txt +12 -0
- package/src/fx.js +99 -22
- package/.distignore +0 -18
- package/inc/Admin.php +0 -48
- package/inc/AdminPage.php +0 -398
- package/inc/Editor.php +0 -31
- package/inc/Frontend.php +0 -50
- package/inc/SaveHandler.php +0 -66
- package/inc/Settings.php +0 -41
- package/inc/update.php +0 -154
package/README.md
CHANGED
|
@@ -159,11 +159,46 @@ All functions accept `(element, options)`:
|
|
|
159
159
|
|
|
160
160
|
Set `trigger: 'scroll'` to enable ScrollTrigger. Pass `scrollTrigger: { trigger: someEl }` to use a different trigger element.
|
|
161
161
|
|
|
162
|
+
### Utility Methods
|
|
163
|
+
|
|
164
|
+
| Method | Description |
|
|
165
|
+
|--------|-------------|
|
|
166
|
+
| `FX.init()` | Re-scan DOM and apply animations (for dynamic content) |
|
|
167
|
+
| `FX.refresh()` | Re-split text after layout change (sidebar toggle, font load) |
|
|
168
|
+
| `FX.config` | Global config object |
|
|
169
|
+
|
|
170
|
+
**Resize handling:** Text-based effects (`textReveal`, `typeWriter`, `splitWords`) automatically re-split when the browser width changes. After one-shot animations complete, the SplitText DOM is reverted so text reflows naturally.
|
|
171
|
+
|
|
172
|
+
## Preventing Flash of Unstyled Content (FOUC)
|
|
173
|
+
|
|
174
|
+
FX uses GSAP's `autoAlpha` internally, so elements with `visibility: hidden` are revealed automatically when their animation starts. Add this CSS **before** any content renders to prevent the flash where elements appear briefly before JS loads:
|
|
175
|
+
|
|
176
|
+
```css
|
|
177
|
+
.fx-text-reveal-pl,.fx-text-reveal-st,.fx-text-reveal,
|
|
178
|
+
.fx-reveal-pl,.fx-reveal-st,.fx-reveal,
|
|
179
|
+
.fx-spin-reveal-pl,.fx-spin-reveal-st,.fx-spin-reveal,
|
|
180
|
+
.fx-bg-reveal-pl,.fx-bg-reveal-st,.fx-bg-reveal,
|
|
181
|
+
.fx-scale-in-pl,.fx-scale-in-st,.fx-scale-in,
|
|
182
|
+
.fx-fade-in-pl,.fx-fade-in-st,.fx-fade-in,
|
|
183
|
+
.fx-blur-in-pl,.fx-blur-in-st,.fx-blur-in,
|
|
184
|
+
.fx-clip-up-pl,.fx-clip-up-st,.fx-clip-up,
|
|
185
|
+
.fx-clip-down-pl,.fx-clip-down-st,.fx-clip-down,
|
|
186
|
+
.fx-tilt-in-st,.fx-tilt-in,
|
|
187
|
+
.fx-type-writer-pl,.fx-type-writer-st,.fx-type-writer,
|
|
188
|
+
.fx-draw-svg-pl,.fx-draw-svg-st,.fx-draw-svg,.fx-draw-svg-scrub,
|
|
189
|
+
.fx-split-words-pl,.fx-split-words-st,.fx-split-words,
|
|
190
|
+
.fx-slide-left-pl,.fx-slide-left-st,.fx-slide-left,
|
|
191
|
+
.fx-slide-right-pl,.fx-slide-right-st,.fx-slide-right{visibility:hidden}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**WordPress:** The plugin injects this CSS automatically in the `<head>` — no action needed.
|
|
195
|
+
|
|
162
196
|
## Using in a New Project
|
|
163
197
|
|
|
164
198
|
1. Copy this repo (or `npm install`)
|
|
165
199
|
2. Add the 4 script tags (gsap, ScrollTrigger, SplitText, fx.js)
|
|
166
|
-
3. Add
|
|
200
|
+
3. Add the FOUC prevention CSS in your `<head>` (see above)
|
|
201
|
+
4. Add `.fx-*` classes in your HTML
|
|
167
202
|
|
|
168
203
|
For compound sequences, create a project-specific JS file loaded after fx.js:
|
|
169
204
|
|
package/package.json
CHANGED
package/readme.txt
CHANGED
|
@@ -68,6 +68,18 @@ Yes. Use the `fx-start-[top center]` modifier class, or set `scrollStart` in the
|
|
|
68
68
|
|
|
69
69
|
== Changelog ==
|
|
70
70
|
|
|
71
|
+
= 1.7.1 =
|
|
72
|
+
* Fix: FOUC prevention — all effects now use autoAlpha instead of opacity, elements start with visibility:hidden and are revealed by GSAP
|
|
73
|
+
* New: WordPress plugin injects visibility:hidden CSS automatically in the head
|
|
74
|
+
* New: Text-based effects set parent visibility before animating children to prevent flash
|
|
75
|
+
* Enhancement: clipUp/clipDown now include autoAlpha for consistent FOUC handling
|
|
76
|
+
|
|
77
|
+
= 1.7.0 =
|
|
78
|
+
* Fix: Text-based effects (textReveal, typeWriter, splitWords) now re-split on browser resize — line breaks stay correct at every viewport width
|
|
79
|
+
* New: SplitText is reverted after one-shot animations complete — text reflows naturally without extra DOM wrappers
|
|
80
|
+
* New: FX.refresh() public method — manually re-split text after layout changes (sidebar toggle, font load)
|
|
81
|
+
* New: Automatic debounced resize handler (200ms, width-only) for pending scroll-triggered text animations
|
|
82
|
+
|
|
71
83
|
= 1.0.0 =
|
|
72
84
|
* Initial release
|
|
73
85
|
* 5 animation effects
|
package/src/fx.js
CHANGED
|
@@ -157,12 +157,56 @@
|
|
|
157
157
|
return st;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
// ── SplitText resize handling ───────────────
|
|
161
|
+
|
|
162
|
+
var _splitRegistry = [];
|
|
163
|
+
var _lastWidth = window.innerWidth;
|
|
164
|
+
var _resizeTimer;
|
|
165
|
+
|
|
166
|
+
function registerSplit(entry) {
|
|
167
|
+
_splitRegistry.push(entry);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function unregisterSplit(entry) {
|
|
171
|
+
var idx = _splitRegistry.indexOf(entry);
|
|
172
|
+
if (idx > -1) _splitRegistry.splice(idx, 1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function refreshSplits() {
|
|
176
|
+
if (_splitRegistry.length === 0) return;
|
|
177
|
+
|
|
178
|
+
var pending = [];
|
|
179
|
+
|
|
180
|
+
for (var i = _splitRegistry.length - 1; i >= 0; i--) {
|
|
181
|
+
var entry = _splitRegistry[i];
|
|
182
|
+
if (entry.tween) entry.tween.kill();
|
|
183
|
+
if (entry.split) entry.split.revert();
|
|
184
|
+
pending.push(entry);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
_splitRegistry.length = 0;
|
|
188
|
+
|
|
189
|
+
pending.forEach(function (entry) {
|
|
190
|
+
entry.effectFn(entry.el, entry.opts);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
ScrollTrigger.refresh();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
window.addEventListener('resize', function () {
|
|
197
|
+
if (window.innerWidth === _lastWidth) return;
|
|
198
|
+
_lastWidth = window.innerWidth;
|
|
199
|
+
clearTimeout(_resizeTimer);
|
|
200
|
+
_resizeTimer = setTimeout(refreshSplits, 200);
|
|
201
|
+
});
|
|
202
|
+
|
|
160
203
|
// ── Effects ──────────────────────────────────
|
|
161
204
|
|
|
162
205
|
function textReveal(el, opts) {
|
|
163
206
|
opts = opts || {};
|
|
164
207
|
var o = resolveOptions(el, 'textReveal', opts);
|
|
165
208
|
|
|
209
|
+
gsap.set(el, { visibility: 'inherit' });
|
|
166
210
|
var split = new SplitText(el, { type: 'lines', linesClass: 'line-wrapper' });
|
|
167
211
|
|
|
168
212
|
split.lines.forEach(function (line) {
|
|
@@ -172,26 +216,31 @@
|
|
|
172
216
|
wrapper.appendChild(line);
|
|
173
217
|
});
|
|
174
218
|
|
|
219
|
+
var isOneShot = !(opts.trigger === 'scroll' || opts.scrollTrigger) || config.scrollOnce;
|
|
220
|
+
var entry = { el: el, split: split, tween: null, effectFn: textReveal, opts: opts };
|
|
221
|
+
|
|
175
222
|
var tweenVars = {
|
|
176
223
|
y: '100%',
|
|
177
|
-
|
|
224
|
+
autoAlpha: 0,
|
|
178
225
|
duration: o.duration,
|
|
179
226
|
ease: o.ease,
|
|
180
227
|
stagger: o.stagger,
|
|
181
228
|
delay: o.delay,
|
|
182
|
-
onComplete: function () {
|
|
183
|
-
split.lines.forEach(function (line) {
|
|
184
|
-
line.style.transform = '';
|
|
185
|
-
line.style.opacity = '';
|
|
186
|
-
});
|
|
187
|
-
},
|
|
188
229
|
};
|
|
189
230
|
|
|
231
|
+
if (isOneShot) {
|
|
232
|
+
tweenVars.onComplete = function () {
|
|
233
|
+
split.revert();
|
|
234
|
+
unregisterSplit(entry);
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
190
238
|
if (opts.trigger === 'scroll' || opts.scrollTrigger) {
|
|
191
239
|
tweenVars.scrollTrigger = buildScrollTrigger(el, opts.scrollTrigger || {});
|
|
192
240
|
}
|
|
193
241
|
|
|
194
|
-
gsap.from(split.lines, tweenVars);
|
|
242
|
+
entry.tween = gsap.from(split.lines, tweenVars);
|
|
243
|
+
registerSplit(entry);
|
|
195
244
|
}
|
|
196
245
|
|
|
197
246
|
function reveal(el, opts) {
|
|
@@ -200,7 +249,7 @@
|
|
|
200
249
|
|
|
201
250
|
var tweenVars = {
|
|
202
251
|
y: opts.y != null ? opts.y : 80,
|
|
203
|
-
|
|
252
|
+
autoAlpha: 0,
|
|
204
253
|
duration: o.duration,
|
|
205
254
|
ease: o.ease,
|
|
206
255
|
delay: o.delay,
|
|
@@ -220,7 +269,7 @@
|
|
|
220
269
|
var tweenVars = {
|
|
221
270
|
rotation: opts.rotation != null ? opts.rotation : -30,
|
|
222
271
|
scale: opts.scale != null ? opts.scale : 0.9,
|
|
223
|
-
|
|
272
|
+
autoAlpha: 0,
|
|
224
273
|
duration: o.duration,
|
|
225
274
|
ease: o.ease,
|
|
226
275
|
delay: o.delay,
|
|
@@ -239,7 +288,7 @@
|
|
|
239
288
|
|
|
240
289
|
var tweenVars = {
|
|
241
290
|
y: '100%',
|
|
242
|
-
|
|
291
|
+
autoAlpha: 0,
|
|
243
292
|
duration: o.duration,
|
|
244
293
|
ease: o.ease,
|
|
245
294
|
delay: o.delay,
|
|
@@ -258,7 +307,7 @@
|
|
|
258
307
|
|
|
259
308
|
var tweenVars = {
|
|
260
309
|
scale: opts.scale != null ? opts.scale : 0.92,
|
|
261
|
-
|
|
310
|
+
autoAlpha: 0,
|
|
262
311
|
duration: o.duration,
|
|
263
312
|
ease: o.ease,
|
|
264
313
|
delay: o.delay,
|
|
@@ -276,7 +325,7 @@
|
|
|
276
325
|
var o = resolveOptions(el, 'fadeIn', opts);
|
|
277
326
|
|
|
278
327
|
var tweenVars = {
|
|
279
|
-
|
|
328
|
+
autoAlpha: 0,
|
|
280
329
|
scale: opts.scale != null ? opts.scale : 0.95,
|
|
281
330
|
duration: o.duration,
|
|
282
331
|
ease: o.ease,
|
|
@@ -296,7 +345,7 @@
|
|
|
296
345
|
|
|
297
346
|
var tweenVars = {
|
|
298
347
|
filter: 'blur(' + (opts.blur != null ? opts.blur : 12) + 'px)',
|
|
299
|
-
|
|
348
|
+
autoAlpha: 0,
|
|
300
349
|
duration: o.duration,
|
|
301
350
|
ease: o.ease,
|
|
302
351
|
delay: o.delay,
|
|
@@ -315,6 +364,7 @@
|
|
|
315
364
|
|
|
316
365
|
var tweenVars = {
|
|
317
366
|
clipPath: 'inset(100% 0 0 0)',
|
|
367
|
+
autoAlpha: 0,
|
|
318
368
|
duration: o.duration,
|
|
319
369
|
ease: o.ease,
|
|
320
370
|
delay: o.delay,
|
|
@@ -333,6 +383,7 @@
|
|
|
333
383
|
|
|
334
384
|
var tweenVars = {
|
|
335
385
|
clipPath: 'inset(0 0 100% 0)',
|
|
386
|
+
autoAlpha: 0,
|
|
336
387
|
duration: o.duration,
|
|
337
388
|
ease: o.ease,
|
|
338
389
|
delay: o.delay,
|
|
@@ -352,13 +403,13 @@
|
|
|
352
403
|
gsap.fromTo(el, {
|
|
353
404
|
rotationX: opts.rotationX != null ? opts.rotationX : 45,
|
|
354
405
|
scale: opts.scale != null ? opts.scale : 0.8,
|
|
355
|
-
|
|
406
|
+
autoAlpha: opts.opacity != null ? opts.opacity : 0,
|
|
356
407
|
transformPerspective: opts.perspective != null ? opts.perspective : 1000,
|
|
357
408
|
transformOrigin: opts.transformOrigin || 'center bottom',
|
|
358
409
|
}, {
|
|
359
410
|
rotationX: 0,
|
|
360
411
|
scale: 1,
|
|
361
|
-
|
|
412
|
+
autoAlpha: 1,
|
|
362
413
|
transformPerspective: 1000,
|
|
363
414
|
ease: o.ease,
|
|
364
415
|
scrollTrigger: {
|
|
@@ -374,27 +425,40 @@
|
|
|
374
425
|
opts = opts || {};
|
|
375
426
|
var o = resolveOptions(el, 'typeWriter', opts);
|
|
376
427
|
|
|
428
|
+
gsap.set(el, { visibility: 'inherit' });
|
|
377
429
|
var split = new SplitText(el, { type: 'chars' });
|
|
378
|
-
gsap.set(split.chars, {
|
|
430
|
+
gsap.set(split.chars, { autoAlpha: 0 });
|
|
431
|
+
|
|
432
|
+
var isOneShot = !(opts.trigger === 'scroll' || opts.scrollTrigger) || config.scrollOnce;
|
|
433
|
+
var entry = { el: el, split: split, tween: null, effectFn: typeWriter, opts: opts };
|
|
379
434
|
|
|
380
435
|
var tweenVars = {
|
|
381
|
-
|
|
436
|
+
autoAlpha: 1,
|
|
382
437
|
duration: o.duration,
|
|
383
438
|
ease: o.ease,
|
|
384
439
|
stagger: o.stagger,
|
|
385
440
|
delay: o.delay,
|
|
386
441
|
};
|
|
387
442
|
|
|
443
|
+
if (isOneShot) {
|
|
444
|
+
tweenVars.onComplete = function () {
|
|
445
|
+
split.revert();
|
|
446
|
+
unregisterSplit(entry);
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
388
450
|
if (opts.trigger === 'scroll' || opts.scrollTrigger) {
|
|
389
451
|
tweenVars.scrollTrigger = buildScrollTrigger(el, opts.scrollTrigger || {});
|
|
390
452
|
}
|
|
391
453
|
|
|
392
|
-
gsap.to(split.chars, tweenVars);
|
|
454
|
+
entry.tween = gsap.to(split.chars, tweenVars);
|
|
455
|
+
registerSplit(entry);
|
|
393
456
|
}
|
|
394
457
|
|
|
395
458
|
function drawSVG(el, opts) {
|
|
396
459
|
opts = opts || {};
|
|
397
460
|
var o = resolveOptions(el, 'drawSVG', opts);
|
|
461
|
+
gsap.set(el, { visibility: 'inherit' });
|
|
398
462
|
|
|
399
463
|
var paths = el.tagName === 'path' || el.tagName === 'line' || el.tagName === 'circle' || el.tagName === 'polyline'
|
|
400
464
|
? [el]
|
|
@@ -462,22 +526,34 @@
|
|
|
462
526
|
opts = opts || {};
|
|
463
527
|
var o = resolveOptions(el, 'splitWords', opts);
|
|
464
528
|
|
|
529
|
+
gsap.set(el, { visibility: 'inherit' });
|
|
465
530
|
var split = new SplitText(el, { type: 'words' });
|
|
466
531
|
|
|
532
|
+
var isOneShot = !(opts.trigger === 'scroll' || opts.scrollTrigger) || config.scrollOnce;
|
|
533
|
+
var entry = { el: el, split: split, tween: null, effectFn: splitWords, opts: opts };
|
|
534
|
+
|
|
467
535
|
var tweenVars = {
|
|
468
536
|
y: opts.y != null ? opts.y : 30,
|
|
469
|
-
|
|
537
|
+
autoAlpha: 0,
|
|
470
538
|
duration: o.duration,
|
|
471
539
|
ease: o.ease,
|
|
472
540
|
stagger: o.stagger,
|
|
473
541
|
delay: o.delay,
|
|
474
542
|
};
|
|
475
543
|
|
|
544
|
+
if (isOneShot) {
|
|
545
|
+
tweenVars.onComplete = function () {
|
|
546
|
+
split.revert();
|
|
547
|
+
unregisterSplit(entry);
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
476
551
|
if (opts.trigger === 'scroll' || opts.scrollTrigger) {
|
|
477
552
|
tweenVars.scrollTrigger = buildScrollTrigger(el, opts.scrollTrigger || {});
|
|
478
553
|
}
|
|
479
554
|
|
|
480
|
-
gsap.from(split.words, tweenVars);
|
|
555
|
+
entry.tween = gsap.from(split.words, tweenVars);
|
|
556
|
+
registerSplit(entry);
|
|
481
557
|
}
|
|
482
558
|
|
|
483
559
|
function slideIn(el, opts) {
|
|
@@ -488,7 +564,7 @@
|
|
|
488
564
|
|
|
489
565
|
var tweenVars = {
|
|
490
566
|
x: direction === 'left' ? -xVal : xVal,
|
|
491
|
-
|
|
567
|
+
autoAlpha: 0,
|
|
492
568
|
duration: o.duration,
|
|
493
569
|
ease: o.ease,
|
|
494
570
|
delay: o.delay,
|
|
@@ -774,5 +850,6 @@
|
|
|
774
850
|
splitWords: splitWords,
|
|
775
851
|
slideIn: slideIn,
|
|
776
852
|
init: init,
|
|
853
|
+
refresh: refreshSplits,
|
|
777
854
|
};
|
|
778
855
|
})();
|
package/.distignore
DELETED
package/inc/Admin.php
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
namespace FancooloFX;
|
|
4
|
-
|
|
5
|
-
if ( ! defined( 'ABSPATH' ) ) {
|
|
6
|
-
exit;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
class Admin {
|
|
10
|
-
|
|
11
|
-
public static function register_menu() {
|
|
12
|
-
add_theme_page(
|
|
13
|
-
'Fancoolo FX',
|
|
14
|
-
'Fancoolo FX',
|
|
15
|
-
'edit_theme_options',
|
|
16
|
-
'fancoolo-fx',
|
|
17
|
-
array( AdminPage::class, 'render' )
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
public static function enqueue( $hook ) {
|
|
22
|
-
if ( 'appearance_page_fancoolo-fx' !== $hook ) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
wp_enqueue_style( 'fancoolo-fx-admin', FANCOOLO_FX_URL . 'assets/admin.css', array(), FANCOOLO_FX_VERSION );
|
|
27
|
-
wp_enqueue_script( 'fancoolo-fx-admin', FANCOOLO_FX_URL . 'assets/admin.js', array( 'jquery' ), FANCOOLO_FX_VERSION, true );
|
|
28
|
-
|
|
29
|
-
$settings = wp_enqueue_code_editor( array( 'type' => 'text/javascript' ) );
|
|
30
|
-
|
|
31
|
-
if ( false !== $settings ) {
|
|
32
|
-
wp_add_inline_script(
|
|
33
|
-
'code-editor',
|
|
34
|
-
sprintf(
|
|
35
|
-
'jQuery(function($) {
|
|
36
|
-
if ($("#fancoolo-fx-editor").length) {
|
|
37
|
-
var editor = wp.codeEditor.initialize($("#fancoolo-fx-editor"), %s);
|
|
38
|
-
editor.codemirror.on("change", function(cm) {
|
|
39
|
-
cm.refresh();
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
});',
|
|
43
|
-
wp_json_encode( $settings )
|
|
44
|
-
)
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
package/inc/AdminPage.php
DELETED
|
@@ -1,398 +0,0 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
namespace FancooloFX;
|
|
4
|
-
|
|
5
|
-
if ( ! defined( 'ABSPATH' ) ) {
|
|
6
|
-
exit;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
class AdminPage {
|
|
10
|
-
|
|
11
|
-
public static function render() {
|
|
12
|
-
$custom_file = Settings::get_custom_file_path();
|
|
13
|
-
$content = file_exists( $custom_file ) ? file_get_contents( $custom_file ) : '';
|
|
14
|
-
$s = Settings::get();
|
|
15
|
-
|
|
16
|
-
settings_errors( 'fancoolo_fx' );
|
|
17
|
-
?>
|
|
18
|
-
<!-- Styles and scripts enqueued via Admin::enqueue() -->
|
|
19
|
-
|
|
20
|
-
<div class="wrap">
|
|
21
|
-
<h1>Fancoolo FX</h1>
|
|
22
|
-
<p>
|
|
23
|
-
GSAP animation wrapper is active. Add <code>.fx-*</code> classes to blocks in Gutenberg
|
|
24
|
-
and they will animate automatically.
|
|
25
|
-
</p>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<!-- ── Tabs ── -->
|
|
29
|
-
<div class="ffx-tabs">
|
|
30
|
-
<button class="ffx-tab active" data-tab="editor">Editor</button>
|
|
31
|
-
<button class="ffx-tab" data-tab="config">Config Reference</button>
|
|
32
|
-
<button class="ffx-tab" data-tab="classes">Classes Reference</button>
|
|
33
|
-
</div>
|
|
34
|
-
|
|
35
|
-
<!-- ═══ Editor Tab ═══ -->
|
|
36
|
-
<div class="ffx-panel ffx-panel-editor active" data-panel="editor">
|
|
37
|
-
<form method="post">
|
|
38
|
-
<?php wp_nonce_field( 'fancoolo_fx_save_action', 'fancoolo_fx_nonce' ); ?>
|
|
39
|
-
|
|
40
|
-
<!-- Editor main area -->
|
|
41
|
-
<div class="ffx-editor-main">
|
|
42
|
-
<input type="submit" name="fancoolo_fx_save" class="button button-primary ffx-save-btn" value="Save Changes">
|
|
43
|
-
<textarea
|
|
44
|
-
id="fancoolo-fx-editor"
|
|
45
|
-
name="fancoolo_fx_code"
|
|
46
|
-
rows="20"
|
|
47
|
-
style="width: 100%; font-family: monospace;"
|
|
48
|
-
><?php echo esc_textarea( $content ); ?></textarea>
|
|
49
|
-
</div>
|
|
50
|
-
|
|
51
|
-
<!-- Settings sidebar -->
|
|
52
|
-
<div class="ffx-sidebar">
|
|
53
|
-
<h3>Settings</h3>
|
|
54
|
-
|
|
55
|
-
<label for="ffx-scroll-start">Scroll Start</label>
|
|
56
|
-
<input type="text" id="ffx-scroll-start" name="fancoolo_fx_scroll_start" value="<?php echo esc_attr( $s['scroll_start'] ); ?>" placeholder="top 85%">
|
|
57
|
-
<p class="ffx-hint">When scroll animations trigger (e.g. top 85%, top center)</p>
|
|
58
|
-
|
|
59
|
-
<label for="ffx-section-selector">Section Selector</label>
|
|
60
|
-
<input type="text" id="ffx-section-selector" name="fancoolo_fx_section_selector" value="<?php echo esc_attr( $s['section_selector'] ); ?>" placeholder="section">
|
|
61
|
-
<p class="ffx-hint">Containers for bare-class triggering</p>
|
|
62
|
-
|
|
63
|
-
<label for="ffx-exclude-selectors">Exclude Selectors</label>
|
|
64
|
-
<input type="text" id="ffx-exclude-selectors" name="fancoolo_fx_exclude_selectors" value="<?php echo esc_attr( $s['exclude_selectors'] ); ?>" placeholder=".no-fx, .wp-block-navigation">
|
|
65
|
-
<p class="ffx-hint">Elements matching these selectors are never animated</p>
|
|
66
|
-
|
|
67
|
-
<hr>
|
|
68
|
-
|
|
69
|
-
<div class="ffx-toggle">
|
|
70
|
-
<input type="checkbox" id="ffx-scroll-once" name="fancoolo_fx_scroll_once" value="1" <?php checked( $s['scroll_once'], '1' ); ?>>
|
|
71
|
-
<label for="ffx-scroll-once">Play once</label>
|
|
72
|
-
</div>
|
|
73
|
-
<p class="ffx-hint">Uncheck to replay animations on every scroll</p>
|
|
74
|
-
|
|
75
|
-
<div class="ffx-toggle">
|
|
76
|
-
<input type="checkbox" id="ffx-debug-markers" name="fancoolo_fx_debug_markers" value="1" <?php checked( $s['debug_markers'], '1' ); ?>>
|
|
77
|
-
<label for="ffx-debug-markers">Debug markers</label>
|
|
78
|
-
</div>
|
|
79
|
-
<p class="ffx-hint">Show ScrollTrigger markers (admin only, visitors won't see them)</p>
|
|
80
|
-
|
|
81
|
-
<div class="ffx-toggle">
|
|
82
|
-
<input type="checkbox" id="ffx-disable-mobile" name="fancoolo_fx_disable_mobile" value="1" <?php checked( $s['disable_mobile'], '1' ); ?>>
|
|
83
|
-
<label for="ffx-disable-mobile">Disable on mobile</label>
|
|
84
|
-
</div>
|
|
85
|
-
<div class="ffx-mobile-breakpoint" style="<?php echo $s['disable_mobile'] === '1' ? '' : 'display:none;'; ?>">
|
|
86
|
-
<label for="ffx-mobile-breakpoint">Breakpoint (px)</label>
|
|
87
|
-
<input type="text" id="ffx-mobile-breakpoint" name="fancoolo_fx_mobile_breakpoint" value="<?php echo esc_attr( $s['mobile_breakpoint'] ); ?>" placeholder="768" style="width: 80px; margin-bottom: 12px;">
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
<div class="ffx-toggle">
|
|
91
|
-
<input type="checkbox" id="ffx-respect-reduced-motion" name="fancoolo_fx_respect_reduced_motion" value="1" <?php checked( $s['respect_reduced_motion'], '1' ); ?>>
|
|
92
|
-
<label for="ffx-respect-reduced-motion">Respect reduced motion</label>
|
|
93
|
-
</div>
|
|
94
|
-
<p class="ffx-hint">Skip animations when OS has reduced motion enabled</p>
|
|
95
|
-
|
|
96
|
-
<label for="ffx-speed-multiplier">Speed Multiplier</label>
|
|
97
|
-
<select id="ffx-speed-multiplier" name="fancoolo_fx_speed_multiplier" style="width: 100%; margin-bottom: 12px;">
|
|
98
|
-
<?php foreach ( array( '0.5' => '0.5x (faster)', '0.75' => '0.75x', '1' => '1x (default)', '1.25' => '1.25x', '1.5' => '1.5x', '2' => '2x (slower)' ) as $val => $lbl ) : ?>
|
|
99
|
-
<option value="<?php echo esc_attr( $val ); ?>" <?php selected( $s['speed_multiplier'], $val ); ?>><?php echo esc_html( $lbl ); ?></option>
|
|
100
|
-
<?php endforeach; ?>
|
|
101
|
-
</select>
|
|
102
|
-
|
|
103
|
-
<div class="ffx-toggle">
|
|
104
|
-
<input type="checkbox" id="ffx-gutenberg-panel" name="fancoolo_fx_gutenberg_panel" value="1" <?php checked( $s['gutenberg_panel'], '1' ); ?>>
|
|
105
|
-
<label for="ffx-gutenberg-panel">Gutenberg integration</label>
|
|
106
|
-
</div>
|
|
107
|
-
<p class="ffx-hint">Show FX Animation panel in the block editor sidebar</p>
|
|
108
|
-
|
|
109
|
-
<hr>
|
|
110
|
-
|
|
111
|
-
<h3>Global Tag Map</h3>
|
|
112
|
-
<p class="ffx-hint" style="margin-top: -8px;">Auto-animate elements by tag inside sections. No classes needed.</p>
|
|
113
|
-
|
|
114
|
-
<div id="ffx-tagmap-rows">
|
|
115
|
-
<?php foreach ( $s['tag_map'] as $i => $row ) : ?>
|
|
116
|
-
<div class="ffx-tagmap-row">
|
|
117
|
-
<input type="text" name="fancoolo_fx_tag_map[<?php echo $i; ?>][selector]" value="<?php echo esc_attr( $row['selector'] ); ?>" placeholder="h1,h2,h3">
|
|
118
|
-
<select name="fancoolo_fx_tag_map[<?php echo $i; ?>][effect]">
|
|
119
|
-
<?php foreach ( self::get_effects_list() as $value => $label ) : ?>
|
|
120
|
-
<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $row['effect'], $value ); ?>><?php echo esc_html( $label ); ?></option>
|
|
121
|
-
<?php endforeach; ?>
|
|
122
|
-
</select>
|
|
123
|
-
<button type="button" class="ffx-tagmap-remove" title="Remove">×</button>
|
|
124
|
-
</div>
|
|
125
|
-
<?php endforeach; ?>
|
|
126
|
-
</div>
|
|
127
|
-
<button type="button" id="ffx-tagmap-add" class="button button-small">+ Add Rule</button>
|
|
128
|
-
|
|
129
|
-
<hr>
|
|
130
|
-
|
|
131
|
-
<h3>Import / Export</h3>
|
|
132
|
-
<div class="ffx-import-export">
|
|
133
|
-
<button type="button" id="ffx-export" class="button button-small" style="width: 100%; margin-bottom: 6px;">Export Settings</button>
|
|
134
|
-
<button type="button" id="ffx-import-btn" class="button button-small" style="width: 100%; margin-bottom: 6px;">Import Settings</button>
|
|
135
|
-
<input type="file" id="ffx-import-file" accept=".json" style="display: none;">
|
|
136
|
-
<button type="button" id="ffx-reset" class="button button-small" style="width: 100%; color: #a00;">Reset to Defaults</button>
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
139
|
-
</form>
|
|
140
|
-
</div>
|
|
141
|
-
|
|
142
|
-
<!-- ═══ Config Reference Tab ═══ -->
|
|
143
|
-
<div class="ffx-panel" data-panel="config">
|
|
144
|
-
|
|
145
|
-
<h3>Config Options</h3>
|
|
146
|
-
<table class="widefat fixed striped" style="max-width: 700px;">
|
|
147
|
-
<thead>
|
|
148
|
-
<tr><th>Option</th><th>Default</th><th>Description</th></tr>
|
|
149
|
-
</thead>
|
|
150
|
-
<tbody>
|
|
151
|
-
<tr><td><code>FX.config.tagMap</code></td><td><code>null</code></td><td>Auto-animate elements by tag/selector — no classes needed</td></tr>
|
|
152
|
-
<tr><td><code>FX.config.sectionSelector</code></td><td><code>'section'</code></td><td>Containers for bare-class and tagMap triggering</td></tr>
|
|
153
|
-
<tr><td><code>FX.config.scrollStart</code></td><td><code>'top 85%'</code></td><td>When scroll animations trigger (GSAP ScrollTrigger start format)</td></tr>
|
|
154
|
-
<tr><td><code>FX.config.scrollOnce</code></td><td><code>true</code></td><td>Play once or replay on every scroll</td></tr>
|
|
155
|
-
<tr><td><code>FX.config.excludeSelectors</code></td><td><code>''</code></td><td>CSS selectors for elements to never animate</td></tr>
|
|
156
|
-
<tr><td><code>FX.config.disableMobile</code></td><td><code>false</code></td><td>Skip all animations on mobile</td></tr>
|
|
157
|
-
<tr><td><code>FX.config.mobileBreakpoint</code></td><td><code>768</code></td><td>Width threshold for mobile detection (px)</td></tr>
|
|
158
|
-
<tr><td><code>FX.config.speedMultiplier</code></td><td><code>1</code></td><td>Global duration multiplier (0.5 = faster, 2 = slower)</td></tr>
|
|
159
|
-
<tr><td><code>FX.config.respectReducedMotion</code></td><td><code>true</code></td><td>Skip animations when OS prefers reduced motion</td></tr>
|
|
160
|
-
</tbody>
|
|
161
|
-
</table>
|
|
162
|
-
|
|
163
|
-
<h3 style="margin-top: 24px;">JS API Functions</h3>
|
|
164
|
-
<table class="widefat fixed striped" style="max-width: 700px;">
|
|
165
|
-
<thead>
|
|
166
|
-
<tr><th>Function</th><th>Description</th></tr>
|
|
167
|
-
</thead>
|
|
168
|
-
<tbody>
|
|
169
|
-
<tr><td><code>FX.textReveal(el, opts)</code></td><td>Split text lines, masked reveal upward</td></tr>
|
|
170
|
-
<tr><td><code>FX.reveal(el, opts)</code></td><td>Slide up with fade</td></tr>
|
|
171
|
-
<tr><td><code>FX.spinReveal(el, opts)</code></td><td>Rotate and scale in</td></tr>
|
|
172
|
-
<tr><td><code>FX.bgReveal(el, opts)</code></td><td>Background slide up</td></tr>
|
|
173
|
-
<tr><td><code>FX.scaleIn(el, opts)</code></td><td>Scale up with fade</td></tr>
|
|
174
|
-
<tr><td><code>FX.fadeIn(el, opts)</code></td><td>Opacity + subtle scale, no movement</td></tr>
|
|
175
|
-
<tr><td><code>FX.blurIn(el, opts)</code></td><td>Fade in while deblurring</td></tr>
|
|
176
|
-
<tr><td><code>FX.clipUp(el, opts)</code></td><td>Clip-path wipe from bottom</td></tr>
|
|
177
|
-
<tr><td><code>FX.clipDown(el, opts)</code></td><td>Clip-path wipe from top</td></tr>
|
|
178
|
-
<tr><td><code>FX.tiltIn(el, opts)</code></td><td>3D perspective reveal (scrub-based)</td></tr>
|
|
179
|
-
<tr><td><code>FX.typeWriter(el, opts)</code></td><td>Character-by-character typing reveal</td></tr>
|
|
180
|
-
<tr><td><code>FX.drawSVG(el, opts)</code></td><td>SVG stroke drawing animation</td></tr>
|
|
181
|
-
<tr><td><code>FX.parallax(el, opts)</code></td><td>Scroll-linked Y parallax shift</td></tr>
|
|
182
|
-
<tr><td><code>FX.splitWords(el, opts)</code></td><td>Word-by-word fade and slide up</td></tr>
|
|
183
|
-
<tr><td><code>FX.slideIn(el, opts)</code></td><td>Horizontal slide from left or right</td></tr>
|
|
184
|
-
<tr><td><code>FX.init()</code></td><td>Re-scan DOM — call after changing any config</td></tr>
|
|
185
|
-
</tbody>
|
|
186
|
-
</table>
|
|
187
|
-
|
|
188
|
-
<h3 style="margin-top: 24px;">Examples <span style="font-weight:normal;color:#646970;font-size:13px;">(click code to copy)</span></h3>
|
|
189
|
-
|
|
190
|
-
<h4 style="margin-top: 16px;">Auto-animate by tag</h4>
|
|
191
|
-
<div class="ffx-copy-wrap">
|
|
192
|
-
<button class="ffx-copy-btn" data-target="ex1">Copy</button>
|
|
193
|
-
<pre class="ffx-pre" id="ex1">FX.config.tagMap = {
|
|
194
|
-
'h1,h2,h3,h4,h5,h6': 'textReveal',
|
|
195
|
-
'p,blockquote': 'textReveal',
|
|
196
|
-
'img,video': 'reveal',
|
|
197
|
-
};
|
|
198
|
-
FX.config.sectionSelector = 'section, .wp-block-group';
|
|
199
|
-
FX.init();</pre>
|
|
200
|
-
</div>
|
|
201
|
-
|
|
202
|
-
<h4 style="margin-top: 16px;">Change scroll trigger position</h4>
|
|
203
|
-
<div class="ffx-copy-wrap">
|
|
204
|
-
<button class="ffx-copy-btn" data-target="ex2">Copy</button>
|
|
205
|
-
<pre class="ffx-pre" id="ex2">FX.config.scrollStart = 'top center';
|
|
206
|
-
FX.init();</pre>
|
|
207
|
-
</div>
|
|
208
|
-
|
|
209
|
-
<h4 style="margin-top: 16px;">Replay animations on re-scroll</h4>
|
|
210
|
-
<div class="ffx-copy-wrap">
|
|
211
|
-
<button class="ffx-copy-btn" data-target="ex3">Copy</button>
|
|
212
|
-
<pre class="ffx-pre" id="ex3">FX.config.scrollOnce = false;
|
|
213
|
-
FX.init();</pre>
|
|
214
|
-
</div>
|
|
215
|
-
|
|
216
|
-
<h4 style="margin-top: 16px;">Compound sequence</h4>
|
|
217
|
-
<div class="ffx-copy-wrap">
|
|
218
|
-
<button class="ffx-copy-btn" data-target="ex4">Copy</button>
|
|
219
|
-
<pre class="ffx-pre" id="ex4">document.addEventListener('DOMContentLoaded', function () {
|
|
220
|
-
var hero = document.querySelector('.wp-block-cover');
|
|
221
|
-
if (!hero) return;
|
|
222
|
-
|
|
223
|
-
FX.scaleIn(hero, {
|
|
224
|
-
trigger: 'scroll',
|
|
225
|
-
scrollTrigger: { trigger: hero }
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
var heading = hero.querySelector('h2');
|
|
229
|
-
if (heading) {
|
|
230
|
-
FX.textReveal(heading, {
|
|
231
|
-
trigger: 'scroll',
|
|
232
|
-
delay: 0.2,
|
|
233
|
-
scrollTrigger: { trigger: hero }
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
});</pre>
|
|
237
|
-
</div>
|
|
238
|
-
</div>
|
|
239
|
-
|
|
240
|
-
<!-- ═══ Classes Reference Tab ═══ -->
|
|
241
|
-
<div class="ffx-panel" data-panel="classes">
|
|
242
|
-
|
|
243
|
-
<p style="margin-bottom: 16px; color: #646970;">
|
|
244
|
-
Add these in Gutenberg: select a block → Advanced → Additional CSS class(es). Click any class to copy it.
|
|
245
|
-
</p>
|
|
246
|
-
|
|
247
|
-
<?php self::render_classes_reference(); ?>
|
|
248
|
-
</div>
|
|
249
|
-
|
|
250
|
-
<p style="margin-top: 24px; color: #646970;">
|
|
251
|
-
This code loads after fx.js on the frontend. Leave empty to use defaults only.<br>
|
|
252
|
-
<strong style="color:#1d2327;">Important:</strong> Always add <code>FX.init();</code> at the end when changing config — it re-scans the page with your new settings.
|
|
253
|
-
</p>
|
|
254
|
-
<p style="margin-top: 12px;">
|
|
255
|
-
<a href="https://krstivoja.github.io/fancoolo-fx/documentation/" target="_blank">
|
|
256
|
-
Full Documentation →
|
|
257
|
-
</a>
|
|
258
|
-
</p>
|
|
259
|
-
</div>
|
|
260
|
-
<?php
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
public static function get_effects_list() {
|
|
264
|
-
return array(
|
|
265
|
-
'textReveal' => 'Text Reveal',
|
|
266
|
-
'reveal' => 'Reveal',
|
|
267
|
-
'spinReveal' => 'Spin Reveal',
|
|
268
|
-
'bgReveal' => 'BG Reveal',
|
|
269
|
-
'scaleIn' => 'Scale In',
|
|
270
|
-
'fadeIn' => 'Fade In',
|
|
271
|
-
'blurIn' => 'Blur In',
|
|
272
|
-
'clipUp' => 'Clip Up',
|
|
273
|
-
'clipDown' => 'Clip Down',
|
|
274
|
-
'tiltIn' => 'Tilt In',
|
|
275
|
-
'typeWriter' => 'Type Writer',
|
|
276
|
-
'drawSVG' => 'Draw SVG',
|
|
277
|
-
'parallax' => 'Parallax',
|
|
278
|
-
'splitWords' => 'Split Words',
|
|
279
|
-
'slideIn' => 'Slide In',
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
private static function render_classes_reference() {
|
|
284
|
-
$groups = array(
|
|
285
|
-
'Text Reveal' => array(
|
|
286
|
-
'fx-text-reveal-pl' => 'Page load — masked line-by-line reveal',
|
|
287
|
-
'fx-text-reveal-st' => 'Scroll triggered',
|
|
288
|
-
'fx-text-reveal' => 'Auto triggered inside a section',
|
|
289
|
-
),
|
|
290
|
-
'Reveal' => array(
|
|
291
|
-
'fx-reveal-pl' => 'Page load — slide up with fade',
|
|
292
|
-
'fx-reveal-st' => 'Scroll triggered',
|
|
293
|
-
'fx-reveal' => 'Auto triggered inside a section',
|
|
294
|
-
),
|
|
295
|
-
'Spin Reveal' => array(
|
|
296
|
-
'fx-spin-reveal-pl' => 'Page load — rotate and scale in',
|
|
297
|
-
'fx-spin-reveal-st' => 'Scroll triggered',
|
|
298
|
-
'fx-spin-reveal' => 'Auto triggered inside a section',
|
|
299
|
-
),
|
|
300
|
-
'BG Reveal' => array(
|
|
301
|
-
'fx-bg-reveal-pl' => 'Page load — background slide up',
|
|
302
|
-
'fx-bg-reveal-st' => 'Scroll triggered',
|
|
303
|
-
'fx-bg-reveal' => 'Auto triggered inside a section',
|
|
304
|
-
),
|
|
305
|
-
'Scale In' => array(
|
|
306
|
-
'fx-scale-in-pl' => 'Page load — scale up with fade',
|
|
307
|
-
'fx-scale-in-st' => 'Scroll triggered',
|
|
308
|
-
'fx-scale-in' => 'Auto triggered inside a section',
|
|
309
|
-
),
|
|
310
|
-
'Fade In' => array(
|
|
311
|
-
'fx-fade-in-pl' => 'Page load — opacity only, no movement',
|
|
312
|
-
'fx-fade-in-st' => 'Scroll triggered',
|
|
313
|
-
'fx-fade-in' => 'Auto triggered inside a section',
|
|
314
|
-
),
|
|
315
|
-
'Blur In' => array(
|
|
316
|
-
'fx-blur-in-pl' => 'Page load — fade in while deblurring',
|
|
317
|
-
'fx-blur-in-st' => 'Scroll triggered',
|
|
318
|
-
'fx-blur-in' => 'Auto triggered inside a section',
|
|
319
|
-
),
|
|
320
|
-
'Clip Reveal' => array(
|
|
321
|
-
'fx-clip-up-pl' => 'Page load — clip-path wipe from bottom',
|
|
322
|
-
'fx-clip-up-st' => 'Scroll triggered',
|
|
323
|
-
'fx-clip-up' => 'Auto triggered inside a section',
|
|
324
|
-
'fx-clip-down-pl' => 'Page load — clip-path wipe from top',
|
|
325
|
-
'fx-clip-down-st' => 'Scroll triggered',
|
|
326
|
-
'fx-clip-down' => 'Auto triggered inside a section',
|
|
327
|
-
),
|
|
328
|
-
'Type Writer' => array(
|
|
329
|
-
'fx-type-writer-pl' => 'Page load — character-by-character typing',
|
|
330
|
-
'fx-type-writer-st' => 'Scroll triggered',
|
|
331
|
-
'fx-type-writer' => 'Auto triggered inside a section',
|
|
332
|
-
),
|
|
333
|
-
'Draw SVG' => array(
|
|
334
|
-
'fx-draw-svg-pl' => 'Page load — SVG stroke drawing animation',
|
|
335
|
-
'fx-draw-svg-st' => 'Scroll triggered',
|
|
336
|
-
'fx-draw-svg' => 'Auto triggered inside a section',
|
|
337
|
-
'fx-draw-svg-scrub' => 'Draws progressively as you scroll (scrub-based)',
|
|
338
|
-
),
|
|
339
|
-
'Split Words' => array(
|
|
340
|
-
'fx-split-words-pl' => 'Page load — word-by-word fade and slide',
|
|
341
|
-
'fx-split-words-st' => 'Scroll triggered',
|
|
342
|
-
'fx-split-words' => 'Auto triggered inside a section',
|
|
343
|
-
),
|
|
344
|
-
'Slide In' => array(
|
|
345
|
-
'fx-slide-left-pl' => 'Page load — slide in from left',
|
|
346
|
-
'fx-slide-left-st' => 'Scroll triggered — from left',
|
|
347
|
-
'fx-slide-left' => 'Auto triggered inside a section — from left',
|
|
348
|
-
'fx-slide-right-pl' => 'Page load — slide in from right',
|
|
349
|
-
'fx-slide-right-st' => 'Scroll triggered — from right',
|
|
350
|
-
'fx-slide-right' => 'Auto triggered inside a section — from right',
|
|
351
|
-
),
|
|
352
|
-
'Tilt In|scrub — tied to scroll position' => array(
|
|
353
|
-
'fx-tilt-in-st' => '3D perspective reveal linked to scroll',
|
|
354
|
-
'fx-tilt-in' => 'Auto triggered inside a section',
|
|
355
|
-
),
|
|
356
|
-
'Parallax|scrub — tied to scroll position' => array(
|
|
357
|
-
'fx-parallax-st' => 'Parallax Y-shift linked to scroll',
|
|
358
|
-
'fx-parallax' => 'Auto triggered inside a section',
|
|
359
|
-
),
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
foreach ( $groups as $title => $classes ) {
|
|
363
|
-
$parts = explode( '|', $title );
|
|
364
|
-
$label = $parts[0];
|
|
365
|
-
$sub = isset( $parts[1] ) ? ' <span style="font-weight:normal;color:#646970;font-size:12px;">(' . esc_html( $parts[1] ) . ')</span>' : '';
|
|
366
|
-
echo '<div class="ffx-group-title">' . esc_html( $label ) . $sub . '</div>';
|
|
367
|
-
foreach ( $classes as $cls => $desc ) {
|
|
368
|
-
echo '<div class="ffx-class-row"><code data-copy>' . esc_html( $cls ) . '</code><span class="ffx-desc">' . esc_html( $desc ) . '</span></div>';
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Stagger Children
|
|
373
|
-
echo '<div class="ffx-group-title" style="margin-top: 20px;">Stagger Children <span style="font-weight:normal;color:#646970;font-size:12px;">(pair with an effect class)</span></div>';
|
|
374
|
-
$stagger = array(
|
|
375
|
-
'fx-stagger-all-[img]' => 'Target all img children — requires effect class',
|
|
376
|
-
'fx-stagger-all-[img,p]' => 'Target img and p children',
|
|
377
|
-
'fx-stagger-all-[.card]' => 'Target children by CSS class',
|
|
378
|
-
);
|
|
379
|
-
foreach ( $stagger as $cls => $desc ) {
|
|
380
|
-
echo '<div class="ffx-class-row"><code data-copy>' . esc_html( $cls ) . '</code><span class="ffx-desc">' . esc_html( $desc ) . '</span></div>';
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Modifiers
|
|
384
|
-
echo '<div class="ffx-group-title" style="margin-top: 20px;">Modifiers <span style="font-weight:normal;color:#646970;font-size:12px;">(combine with any effect class)</span></div>';
|
|
385
|
-
$modifiers = array(
|
|
386
|
-
'fx-duration-[1.5]' => 'Custom duration (seconds)',
|
|
387
|
-
'fx-delay-[0.3]' => 'Delay before animating (seconds)',
|
|
388
|
-
'fx-stagger-[0.25]' => 'Delay between staggered siblings',
|
|
389
|
-
'fx-ease-[power2.inOut]' => 'GSAP easing function',
|
|
390
|
-
'fx-start-[top center]' => 'Scroll trigger start position',
|
|
391
|
-
'fx-y-[80]' => 'Parallax Y-shift intensity (parallax only)',
|
|
392
|
-
'fx-scrub-[0.6]' => 'Scrub smoothing (drawSVG scrub mode)',
|
|
393
|
-
);
|
|
394
|
-
foreach ( $modifiers as $cls => $desc ) {
|
|
395
|
-
echo '<div class="ffx-class-row"><code data-copy>' . esc_html( $cls ) . '</code><span class="ffx-desc">' . esc_html( $desc ) . '</span></div>';
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
package/inc/Editor.php
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
namespace FancooloFX;
|
|
4
|
-
|
|
5
|
-
if ( ! defined( 'ABSPATH' ) ) {
|
|
6
|
-
exit;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
class Editor {
|
|
10
|
-
|
|
11
|
-
public static function enqueue() {
|
|
12
|
-
$s = Settings::get();
|
|
13
|
-
if ( ! $s['gutenberg_panel'] ) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
$asset_file = FANCOOLO_FX_PATH . 'assets/editor/index.asset.php';
|
|
18
|
-
if ( ! file_exists( $asset_file ) ) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
$asset = include $asset_file;
|
|
23
|
-
wp_enqueue_script(
|
|
24
|
-
'fancoolo-fx-editor',
|
|
25
|
-
FANCOOLO_FX_URL . 'assets/editor/index.js',
|
|
26
|
-
$asset['dependencies'],
|
|
27
|
-
$asset['version'],
|
|
28
|
-
true
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
}
|
package/inc/Frontend.php
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
namespace FancooloFX;
|
|
4
|
-
|
|
5
|
-
if ( ! defined( 'ABSPATH' ) ) {
|
|
6
|
-
exit;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
class Frontend {
|
|
10
|
-
|
|
11
|
-
public static function enqueue() {
|
|
12
|
-
wp_enqueue_script( 'gsap', FANCOOLO_FX_URL . 'assets/gsap.min.js', array(), '3.14.2', true );
|
|
13
|
-
wp_enqueue_script( 'gsap-scrolltrigger', FANCOOLO_FX_URL . 'assets/ScrollTrigger.min.js', array( 'gsap' ), '3.14.2', true );
|
|
14
|
-
wp_enqueue_script( 'gsap-splittext', FANCOOLO_FX_URL . 'assets/SplitText.min.js', array( 'gsap' ), '3.14.2', true );
|
|
15
|
-
wp_enqueue_script( 'fancoolo-fx', FANCOOLO_FX_URL . 'assets/fx.js', array( 'gsap', 'gsap-scrolltrigger', 'gsap-splittext' ), FANCOOLO_FX_VERSION, true );
|
|
16
|
-
|
|
17
|
-
$s = Settings::get();
|
|
18
|
-
$config = array(
|
|
19
|
-
'scrollStart' => $s['scroll_start'],
|
|
20
|
-
'scrollOnce' => (bool) $s['scroll_once'],
|
|
21
|
-
'sectionSelector' => $s['section_selector'],
|
|
22
|
-
'disableMobile' => (bool) $s['disable_mobile'],
|
|
23
|
-
'mobileBreakpoint' => (int) $s['mobile_breakpoint'],
|
|
24
|
-
'speedMultiplier' => (float) $s['speed_multiplier'],
|
|
25
|
-
'respectReducedMotion' => (bool) $s['respect_reduced_motion'],
|
|
26
|
-
'excludeSelectors' => $s['exclude_selectors'],
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
if ( ! empty( $s['tag_map'] ) ) {
|
|
30
|
-
$tag_map_obj = array();
|
|
31
|
-
foreach ( $s['tag_map'] as $row ) {
|
|
32
|
-
$tag_map_obj[ $row['selector'] ] = $row['effect'];
|
|
33
|
-
}
|
|
34
|
-
$config['tagMap'] = (object) $tag_map_obj;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
wp_add_inline_script( 'fancoolo-fx', 'window.__FX_CONFIG__ = ' . wp_json_encode( $config ) . ';', 'before' );
|
|
38
|
-
|
|
39
|
-
if ( $s['debug_markers'] && is_user_logged_in() && current_user_can( 'manage_options' ) ) {
|
|
40
|
-
wp_add_inline_script( 'fancoolo-fx', 'window.__FX_DEBUG_MARKERS__ = true;', 'before' );
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
$custom_file = Settings::get_custom_file_path();
|
|
44
|
-
if ( file_exists( $custom_file ) && filesize( $custom_file ) > 0 ) {
|
|
45
|
-
$upload_dir = wp_upload_dir();
|
|
46
|
-
$custom_url = $upload_dir['baseurl'] . '/fancoolo-fx/custom.js';
|
|
47
|
-
wp_enqueue_script( 'fancoolo-fx-custom', $custom_url, array( 'fancoolo-fx' ), filemtime( $custom_file ), true );
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
package/inc/SaveHandler.php
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
namespace FancooloFX;
|
|
4
|
-
|
|
5
|
-
if ( ! defined( 'ABSPATH' ) ) {
|
|
6
|
-
exit;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
class SaveHandler {
|
|
10
|
-
|
|
11
|
-
public static function handle() {
|
|
12
|
-
if ( ! isset( $_POST['fancoolo_fx_save'] ) ) {
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if ( ! check_admin_referer( 'fancoolo_fx_save_action', 'fancoolo_fx_nonce' ) ) {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if ( ! current_user_can( 'edit_theme_options' ) ) {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Save custom JS file.
|
|
25
|
-
$content = isset( $_POST['fancoolo_fx_code'] ) ? wp_unslash( $_POST['fancoolo_fx_code'] ) : '';
|
|
26
|
-
$upload_dir = wp_upload_dir();
|
|
27
|
-
$dir = $upload_dir['basedir'] . '/fancoolo-fx';
|
|
28
|
-
|
|
29
|
-
if ( ! file_exists( $dir ) ) {
|
|
30
|
-
wp_mkdir_p( $dir );
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
file_put_contents( $dir . '/custom.js', $content );
|
|
34
|
-
|
|
35
|
-
// Build tag map.
|
|
36
|
-
$tag_map = array();
|
|
37
|
-
if ( ! empty( $_POST['fancoolo_fx_tag_map'] ) && is_array( $_POST['fancoolo_fx_tag_map'] ) ) {
|
|
38
|
-
foreach ( $_POST['fancoolo_fx_tag_map'] as $row ) {
|
|
39
|
-
$selector = sanitize_text_field( $row['selector'] ?? '' );
|
|
40
|
-
$effect = sanitize_text_field( $row['effect'] ?? '' );
|
|
41
|
-
if ( '' !== $selector && '' !== $effect ) {
|
|
42
|
-
$tag_map[] = array( 'selector' => $selector, 'effect' => $effect );
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Save settings.
|
|
48
|
-
$settings = array(
|
|
49
|
-
'scroll_start' => sanitize_text_field( $_POST['fancoolo_fx_scroll_start'] ?? 'top 85%' ),
|
|
50
|
-
'scroll_once' => isset( $_POST['fancoolo_fx_scroll_once'] ) ? '1' : '0',
|
|
51
|
-
'section_selector' => sanitize_text_field( $_POST['fancoolo_fx_section_selector'] ?? 'section' ),
|
|
52
|
-
'debug_markers' => isset( $_POST['fancoolo_fx_debug_markers'] ) ? '1' : '0',
|
|
53
|
-
'disable_mobile' => isset( $_POST['fancoolo_fx_disable_mobile'] ) ? '1' : '0',
|
|
54
|
-
'mobile_breakpoint' => sanitize_text_field( $_POST['fancoolo_fx_mobile_breakpoint'] ?? '768' ),
|
|
55
|
-
'speed_multiplier' => sanitize_text_field( $_POST['fancoolo_fx_speed_multiplier'] ?? '1' ),
|
|
56
|
-
'respect_reduced_motion' => isset( $_POST['fancoolo_fx_respect_reduced_motion'] ) ? '1' : '0',
|
|
57
|
-
'exclude_selectors' => sanitize_text_field( $_POST['fancoolo_fx_exclude_selectors'] ?? '' ),
|
|
58
|
-
'gutenberg_panel' => isset( $_POST['fancoolo_fx_gutenberg_panel'] ) ? '1' : '0',
|
|
59
|
-
'tag_map' => $tag_map,
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
Settings::save( $settings );
|
|
63
|
-
|
|
64
|
-
add_settings_error( 'fancoolo_fx', 'fancoolo_fx_saved', 'Settings saved.', 'success' );
|
|
65
|
-
}
|
|
66
|
-
}
|
package/inc/Settings.php
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
namespace FancooloFX;
|
|
4
|
-
|
|
5
|
-
if ( ! defined( 'ABSPATH' ) ) {
|
|
6
|
-
exit;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
class Settings {
|
|
10
|
-
|
|
11
|
-
private static $defaults = array(
|
|
12
|
-
'scroll_start' => 'top 85%',
|
|
13
|
-
'scroll_once' => '1',
|
|
14
|
-
'section_selector' => 'section',
|
|
15
|
-
'debug_markers' => '0',
|
|
16
|
-
'disable_mobile' => '0',
|
|
17
|
-
'mobile_breakpoint' => '768',
|
|
18
|
-
'speed_multiplier' => '1',
|
|
19
|
-
'respect_reduced_motion' => '1',
|
|
20
|
-
'exclude_selectors' => '',
|
|
21
|
-
'gutenberg_panel' => '1',
|
|
22
|
-
'tag_map' => array(),
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
public static function get_defaults() {
|
|
26
|
-
return self::$defaults;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public static function get() {
|
|
30
|
-
return wp_parse_args( get_option( 'fancoolo_fx_settings', array() ), self::$defaults );
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public static function save( $settings ) {
|
|
34
|
-
update_option( 'fancoolo_fx_settings', $settings );
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
public static function get_custom_file_path() {
|
|
38
|
-
$upload_dir = wp_upload_dir();
|
|
39
|
-
return $upload_dir['basedir'] . '/fancoolo-fx/custom.js';
|
|
40
|
-
}
|
|
41
|
-
}
|
package/inc/update.php
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
// ************************************************************************************************
|
|
3
|
-
// Updater Version: 1.0.2
|
|
4
|
-
// ************************************************************************************************
|
|
5
|
-
|
|
6
|
-
if (!defined('ABSPATH')) exit; // Exit if accessed directly
|
|
7
|
-
|
|
8
|
-
if (!class_exists('FANCOOLOFX_DPUpdateChecker')) {
|
|
9
|
-
|
|
10
|
-
class FANCOOLOFX_DPUpdateChecker {
|
|
11
|
-
|
|
12
|
-
public $slug;
|
|
13
|
-
public $version;
|
|
14
|
-
public $cache_key;
|
|
15
|
-
public $remote_url;
|
|
16
|
-
public $type;
|
|
17
|
-
|
|
18
|
-
public function __construct($slug, $version, $cache_key, $remote_url, $type = 'plugin') {
|
|
19
|
-
$this->slug = $slug;
|
|
20
|
-
$this->version = $version;
|
|
21
|
-
$this->cache_key = $cache_key;
|
|
22
|
-
$this->remote_url = $remote_url;
|
|
23
|
-
$this->type = $type;
|
|
24
|
-
add_action('admin_init', array($this, 'check_update_conditions'));
|
|
25
|
-
add_action('plugin_row_meta', array($this, 'add_check_updates_button'), 10, 2);add_action('admin_post_check_for_updates', array($this, 'check_for_updates'));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
public function check_update_conditions() {
|
|
29
|
-
add_filter('plugins_api', array($this, 'info'), 20, 3);add_filter('site_transient_update_plugins', array($this, 'update_plugins'));
|
|
30
|
-
add_action('upgrader_process_complete', array($this, 'purge'), 10, 2);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public function request() {
|
|
34
|
-
$remote = get_transient($this->cache_key);
|
|
35
|
-
|
|
36
|
-
if (false === $remote) {
|
|
37
|
-
$response = wp_remote_get(
|
|
38
|
-
$this->remote_url,
|
|
39
|
-
array(
|
|
40
|
-
'timeout' => 10,
|
|
41
|
-
'headers' => array('Accept' => 'application/json')
|
|
42
|
-
)
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
if (
|
|
46
|
-
is_wp_error($response)
|
|
47
|
-
|| 200 !== wp_remote_retrieve_response_code($response)
|
|
48
|
-
|| empty(wp_remote_retrieve_body($response))
|
|
49
|
-
) {
|
|
50
|
-
error_log('Hoster update check error: ' . wp_remote_retrieve_body($response) ?: $response->get_error_message());
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
$remote = wp_remote_retrieve_body($response);
|
|
55
|
-
set_transient($this->cache_key, $remote, 6 * HOUR_IN_SECONDS);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if(!is_array($remote)){
|
|
59
|
-
$remote = json_decode($remote);
|
|
60
|
-
}
|
|
61
|
-
return $remote;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
public function info($res, $action, $args) {
|
|
66
|
-
if ('plugin_information' !== $action || $this->slug !== $args->slug) {
|
|
67
|
-
return $res;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
$remote = $this->request();
|
|
71
|
-
if (!$remote) return $res;
|
|
72
|
-
|
|
73
|
-
$res = new stdClass();
|
|
74
|
-
$res->name = $remote->name;
|
|
75
|
-
$res->slug = $remote->slug;
|
|
76
|
-
$res->version = $remote->version;
|
|
77
|
-
$res->tested = $remote->tested;
|
|
78
|
-
$res->requires = $remote->requires;
|
|
79
|
-
$res->author = $remote->author;
|
|
80
|
-
$res->author_profile = $remote->author_profile;
|
|
81
|
-
$res->download_link = $remote->download_url;
|
|
82
|
-
$res->trunk = $remote->download_url;
|
|
83
|
-
$res->requires_php = $remote->requires_php;
|
|
84
|
-
$res->last_updated = $remote->last_updated;
|
|
85
|
-
$res->sections = (array)$remote->sections;
|
|
86
|
-
|
|
87
|
-
if (!empty($remote->banners)) {
|
|
88
|
-
$res->banners = (array)$remote->banners;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!empty($remote->icons)) {
|
|
92
|
-
$res->icons = (array)$remote->icons;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return $res;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
public function update_plugins($transient) {
|
|
99
|
-
if (empty($transient->checked)) return $transient;
|
|
100
|
-
|
|
101
|
-
$remote = $this->request();
|
|
102
|
-
if (!$remote) return $transient;
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
version_compare($this->version, $remote->version, '<')
|
|
106
|
-
&& version_compare($remote->requires, get_bloginfo('version'), '<=')
|
|
107
|
-
&& version_compare($remote->requires_php, PHP_VERSION, '<=')
|
|
108
|
-
) {
|
|
109
|
-
$res = new stdClass();
|
|
110
|
-
$res->slug = $this->slug;
|
|
111
|
-
$res->new_version = $remote->version;
|
|
112
|
-
$res->tested = $remote->tested;
|
|
113
|
-
$res->package = $remote->download_url;
|
|
114
|
-
|
|
115
|
-
if (!empty($remote->icons)) {
|
|
116
|
-
$res->icons = (array)$remote->icons;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
$res->plugin = $this->slug;
|
|
120
|
-
$transient->response[$res->plugin] = $res;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return $transient;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
public function add_check_updates_button($plugin_meta, $plugin_file) {
|
|
127
|
-
if ($plugin_file === $this->slug) {
|
|
128
|
-
$url = wp_nonce_url(admin_url('admin-post.php?action=check_for_updates&plugin=' . $this->slug), 'check_for_updates');
|
|
129
|
-
$plugin_meta[] = '<a href="' . esc_url($url) . '">Check for updates</a>';
|
|
130
|
-
}
|
|
131
|
-
return $plugin_meta;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
public function check_for_updates() {
|
|
135
|
-
if (!current_user_can('update_plugins')) {
|
|
136
|
-
wp_die(__('You do not have sufficient permissions to update plugins.'));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
check_admin_referer('check_for_updates');
|
|
140
|
-
delete_transient($this->cache_key);
|
|
141
|
-
$this->request();
|
|
142
|
-
wp_redirect(admin_url('plugins.php'));
|
|
143
|
-
exit;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
public function purge($upgrader, $options) {
|
|
149
|
-
if ('update' === $options['action'] && $this->type === $options['type']) {
|
|
150
|
-
delete_transient($this->cache_key);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|