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 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 `.fx-*` classes in your HTML
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fancoolo-fx",
3
- "version": "1.6.1",
3
+ "version": "1.7.1",
4
4
  "description": "A class-driven GSAP animation wrapper for WordPress and static sites.",
5
5
  "main": "src/fx.js",
6
6
  "homepage": "https://krstivoja.github.io/fancoolo-fx/",
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
- opacity: 0,
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
- opacity: 0,
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
- opacity: 0,
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
- opacity: 0,
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
- opacity: 0,
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
- opacity: 0,
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
- opacity: 0,
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
- opacity: opts.opacity != null ? opts.opacity : 0,
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
- opacity: 1,
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, { opacity: 0 });
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
- opacity: 1,
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
- opacity: 0,
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
- opacity: 0,
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
@@ -1,18 +0,0 @@
1
- node_modules/
2
- src/
3
-
4
- **/.DS_Store
5
-
6
- .distignore
7
- .git/
8
- .github/
9
- .gitignore
10
- .claude/
11
- .nojekyll
12
-
13
- docs/
14
- skills/
15
- package-lock.json
16
- package.json
17
- CLAUDE.md
18
- README.md
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">&times;</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 &rarr; Advanced &rarr; 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 &rarr;
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
- }
@@ -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
- }