PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.49
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.49
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / widgets / Interactive_Gradient_Mesh / Interactive_Gradient_Mesh.php

Interactive_Gradient_Mesh.php in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.49, at includes/widgets/Interactive_Gradient_Mesh/Interactive_Gradient_Mesh.php

1,407 lines 50.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Interactive Gradient Mesh Widget (Free).
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons;
9
10 use Elementor\Controls_Manager;
11 use Elementor\Group_Control_Background;
12 use Elementor\Group_Control_Border;
13 use Elementor\Group_Control_Box_Shadow;
14 use Elementor\Group_Control_Typography;
15 use Elementor\Plugin;
16 use Elementor\Widget_Base;
17
18 if (!defined('ABSPATH')) {
19 exit;
20 }
21
22 /**
23 * Renders an interactive gradient mesh background with optional overlay content.
24 */
25 class Interactive_Gradient_Mesh extends Widget_Base
26 {
27 public function get_name(): string
28 {
29 return 'king-addons-interactive-gradient-mesh';
30 }
31
32 public function get_title(): string
33 {
34 return esc_html__('Interactive Gradient Mesh', 'king-addons');
35 }
36
37 public function get_icon(): string
38 {
39 return 'king-addons-icon king-addons-interactive-gradient-mesh';
40 }
41
42 public function get_style_depends(): array
43 {
44 return [
45 KING_ADDONS_ASSETS_UNIQUE_KEY . '-interactive-gradient-mesh-style',
46 ];
47 }
48
49 public function get_script_depends(): array
50 {
51 return [
52 KING_ADDONS_ASSETS_UNIQUE_KEY . '-interactive-gradient-mesh-script',
53 ];
54 }
55
56 public function get_categories(): array
57 {
58 return ['king-addons'];
59 }
60
61 public function get_keywords(): array
62 {
63 return ['gradient', 'mesh', 'background', 'interactive', 'hero'];
64 }
65
66 public function register_controls(): void
67 {
68 $this->register_general_controls();
69 $this->register_overlay_controls();
70 $this->register_animation_controls();
71 $this->register_baseline_controls();
72 $this->register_style_container_controls();
73 $this->register_style_overlay_controls();
74 $this->register_pro_notice_controls();
75 }
76
77 public function render(): void
78 {
79 $settings = $this->get_settings_for_display();
80 $this->render_output($settings);
81 }
82
83 protected function register_general_controls(): void
84 {
85 $this->start_controls_section(
86 'kng_mesh_general_section',
87 [
88 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('General', 'king-addons'),
89 'tab' => Controls_Manager::TAB_CONTENT,
90 ]
91 );
92
93 $this->add_control(
94 'kng_mesh_preset',
95 [
96 'label' => esc_html__('Preset', 'king-addons'),
97 'type' => Controls_Manager::SELECT,
98 'default' => 'aurora',
99 'options' => $this->get_preset_options(),
100 ]
101 );
102
103 $this->add_control(
104 'kng_mesh_base_color',
105 [
106 'label' => esc_html__('Base Color', 'king-addons'),
107 'type' => Controls_Manager::COLOR,
108 'description' => esc_html__('Overrides the preset base color.', 'king-addons'),
109 ]
110 );
111
112 $this->add_control(
113 'kng_mesh_engine',
114 [
115 'label' => esc_html__('Engine', 'king-addons'),
116 'type' => Controls_Manager::SELECT,
117 'default' => 'auto',
118 'options' => [
119 'auto' => esc_html__('Auto', 'king-addons'),
120 'css' => esc_html__('CSS Gradients', 'king-addons'),
121 'canvas' => esc_html__('Canvas 2D', 'king-addons'),
122 ],
123 ]
124 );
125
126 $this->add_responsive_control(
127 'kng_mesh_height',
128 [
129 'label' => esc_html__('Height', 'king-addons'),
130 'type' => Controls_Manager::SLIDER,
131 'size_units' => ['px', 'vh'],
132 'range' => [
133 'px' => ['min' => 120, 'max' => 1200],
134 'vh' => ['min' => 20, 'max' => 100],
135 ],
136 'default' => [
137 'size' => 420,
138 'unit' => 'px',
139 ],
140 'selectors' => [
141 '{{WRAPPER}} .king-addons-gradient-mesh' => 'height: {{SIZE}}{{UNIT}};',
142 ],
143 ]
144 );
145
146 $this->add_responsive_control(
147 'kng_mesh_radius',
148 [
149 'label' => esc_html__('Border Radius', 'king-addons'),
150 'type' => Controls_Manager::DIMENSIONS,
151 'size_units' => ['px', '%'],
152 'selectors' => [
153 '{{WRAPPER}} .king-addons-gradient-mesh' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
154 ],
155 ]
156 );
157
158 $this->add_control(
159 'kng_mesh_alignment',
160 [
161 'label' => esc_html__('Overlay Alignment', 'king-addons'),
162 'type' => Controls_Manager::CHOOSE,
163 'options' => [
164 'left' => [
165 'title' => esc_html__('Left', 'king-addons'),
166 'icon' => 'eicon-text-align-left',
167 ],
168 'center' => [
169 'title' => esc_html__('Center', 'king-addons'),
170 'icon' => 'eicon-text-align-center',
171 ],
172 'right' => [
173 'title' => esc_html__('Right', 'king-addons'),
174 'icon' => 'eicon-text-align-right',
175 ],
176 ],
177 'default' => 'center',
178 'toggle' => false,
179 'prefix_class' => 'king-addons-gradient-mesh--align-',
180 ]
181 );
182
183 $this->add_control(
184 'kng_mesh_vertical_align',
185 [
186 'label' => esc_html__('Overlay Vertical Align', 'king-addons'),
187 'type' => Controls_Manager::CHOOSE,
188 'options' => [
189 'top' => [
190 'title' => esc_html__('Top', 'king-addons'),
191 'icon' => 'eicon-v-align-top',
192 ],
193 'center' => [
194 'title' => esc_html__('Center', 'king-addons'),
195 'icon' => 'eicon-v-align-middle',
196 ],
197 'bottom' => [
198 'title' => esc_html__('Bottom', 'king-addons'),
199 'icon' => 'eicon-v-align-bottom',
200 ],
201 ],
202 'default' => 'center',
203 'toggle' => false,
204 'prefix_class' => 'king-addons-gradient-mesh--valign-',
205 ]
206 );
207
208 $this->add_responsive_control(
209 'kng_mesh_padding',
210 [
211 'label' => esc_html__('Padding', 'king-addons'),
212 'type' => Controls_Manager::DIMENSIONS,
213 'size_units' => ['px', 'em', '%'],
214 'selectors' => [
215 '{{WRAPPER}} .king-addons-gradient-mesh__overlay' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
216 ],
217 ]
218 );
219
220 $this->end_controls_section();
221 }
222
223 protected function register_overlay_controls(): void
224 {
225 $this->start_controls_section(
226 'kng_mesh_overlay_section',
227 [
228 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Overlay Content', 'king-addons'),
229 'tab' => Controls_Manager::TAB_CONTENT,
230 ]
231 );
232
233 $this->add_control(
234 'kng_mesh_show_overlay',
235 [
236 'label' => esc_html__('Show Overlay', 'king-addons'),
237 'type' => Controls_Manager::SWITCHER,
238 'return_value' => 'yes',
239 'default' => 'yes',
240 ]
241 );
242
243 if ($this->is_pro()) {
244 $this->add_control(
245 'kng_mesh_overlay_source',
246 [
247 'label' => esc_html__('Overlay Source', 'king-addons'),
248 'type' => Controls_Manager::SELECT,
249 'default' => 'basic',
250 'options' => [
251 'basic' => esc_html__('Basic Content', 'king-addons'),
252 'template' => esc_html__('Elementor Template', 'king-addons'),
253 ],
254 'condition' => [
255 'kng_mesh_show_overlay' => 'yes',
256 ],
257 ]
258 );
259 } else {
260 $this->add_control(
261 'kng_mesh_overlay_source',
262 [
263 'type' => Controls_Manager::HIDDEN,
264 'default' => 'basic',
265 ]
266 );
267 }
268
269 if ($this->is_pro()) {
270 $this->add_control(
271 'kng_mesh_overlay_template',
272 [
273 'label' => esc_html__('Choose Template', 'king-addons'),
274 'type' => Controls_Manager::SELECT2,
275 'options' => $this->get_elementor_templates_options(),
276 'label_block' => true,
277 'condition' => [
278 'kng_mesh_show_overlay' => 'yes',
279 'kng_mesh_overlay_source' => 'template',
280 ],
281 ]
282 );
283 }
284
285 $this->add_control(
286 'kng_mesh_title',
287 [
288 'label' => esc_html__('Title', 'king-addons'),
289 'type' => Controls_Manager::TEXT,
290 'default' => esc_html__('Interactive Gradient Mesh', 'king-addons'),
291 'label_block' => true,
292 'condition' => [
293 'kng_mesh_show_overlay' => 'yes',
294 'kng_mesh_overlay_source' => 'basic',
295 ],
296 ]
297 );
298
299 $this->add_control(
300 'kng_mesh_subtitle',
301 [
302 'label' => esc_html__('Subtitle', 'king-addons'),
303 'type' => Controls_Manager::TEXT,
304 'default' => esc_html__('Soft motion, premium atmosphere.', 'king-addons'),
305 'label_block' => true,
306 'condition' => [
307 'kng_mesh_show_overlay' => 'yes',
308 'kng_mesh_overlay_source' => 'basic',
309 ],
310 ]
311 );
312
313 $this->add_control(
314 'kng_mesh_text',
315 [
316 'label' => esc_html__('Text', 'king-addons'),
317 'type' => Controls_Manager::TEXTAREA,
318 'rows' => 3,
319 'default' => esc_html__('Use gradient meshes to add depth to heroes, highlights, and feature blocks.', 'king-addons'),
320 'condition' => [
321 'kng_mesh_show_overlay' => 'yes',
322 'kng_mesh_overlay_source' => 'basic',
323 ],
324 ]
325 );
326
327 $this->add_control(
328 'kng_mesh_button_text',
329 [
330 'label' => esc_html__('Button Text', 'king-addons'),
331 'type' => Controls_Manager::TEXT,
332 'default' => esc_html__('Learn More', 'king-addons'),
333 'condition' => [
334 'kng_mesh_show_overlay' => 'yes',
335 'kng_mesh_overlay_source' => 'basic',
336 ],
337 ]
338 );
339
340 $this->add_control(
341 'kng_mesh_button_url',
342 [
343 'label' => esc_html__('Button Link', 'king-addons'),
344 'type' => Controls_Manager::URL,
345 'placeholder' => esc_html__('https://', 'king-addons'),
346 'condition' => [
347 'kng_mesh_show_overlay' => 'yes',
348 'kng_mesh_overlay_source' => 'basic',
349 ],
350 ]
351 );
352
353 $this->end_controls_section();
354 }
355
356 protected function register_animation_controls(): void
357 {
358 $this->start_controls_section(
359 'kng_mesh_animation_section',
360 [
361 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Animation', 'king-addons'),
362 'tab' => Controls_Manager::TAB_CONTENT,
363 ]
364 );
365
366 $this->add_control(
367 'kng_mesh_enable_animation',
368 [
369 'label' => esc_html__('Enable Animation', 'king-addons'),
370 'type' => Controls_Manager::SWITCHER,
371 'return_value' => 'yes',
372 'default' => 'yes',
373 ]
374 );
375
376 $this->add_responsive_control(
377 'kng_mesh_speed',
378 [
379 'label' => esc_html__('Speed', 'king-addons'),
380 'type' => Controls_Manager::SLIDER,
381 'size_units' => ['custom'],
382 'range' => [
383 'custom' => [
384 'min' => 0,
385 'max' => 100,
386 'step' => 1,
387 ],
388 ],
389 'default' => [
390 'size' => 80,
391 'unit' => 'custom',
392 ],
393 'condition' => [
394 'kng_mesh_enable_animation' => 'yes',
395 ],
396 'selectors' => [
397 '{{WRAPPER}} .king-addons-gradient-mesh' => '--kng-mesh-speed: {{SIZE}};',
398 ],
399 ]
400 );
401
402 $this->add_control(
403 'kng_mesh_motion',
404 [
405 'label' => esc_html__('Motion Style', 'king-addons'),
406 'type' => Controls_Manager::SELECT,
407 'default' => 'drift',
408 'options' => [
409 'drift' => esc_html__('Smooth Drift', 'king-addons'),
410 'pulse' => esc_html__('Slow Pulse', 'king-addons'),
411 'wave' => esc_html__('Calm Wave', 'king-addons'),
412 ],
413 'condition' => [
414 'kng_mesh_enable_animation' => 'yes',
415 ],
416 ]
417 );
418
419 $this->end_controls_section();
420 }
421
422 protected function register_baseline_controls(): void
423 {
424 $this->start_controls_section(
425 'kng_mesh_baseline_section',
426 [
427 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Baseline', 'king-addons'),
428 'tab' => Controls_Manager::TAB_CONTENT,
429 ]
430 );
431
432 $this->add_responsive_control(
433 'kng_mesh_saturation',
434 [
435 'label' => esc_html__('Saturation', 'king-addons'),
436 'type' => Controls_Manager::SLIDER,
437 'size_units' => ['%'],
438 'range' => [
439 '%' => ['min' => 50, 'max' => 160],
440 ],
441 'default' => [
442 'size' => 110,
443 'unit' => '%',
444 ],
445 'selectors' => [
446 '{{WRAPPER}} .king-addons-gradient-mesh' => '--kng-mesh-saturate: {{SIZE}}{{UNIT}};',
447 ],
448 ]
449 );
450
451 $this->add_responsive_control(
452 'kng_mesh_contrast',
453 [
454 'label' => esc_html__('Contrast', 'king-addons'),
455 'type' => Controls_Manager::SLIDER,
456 'size_units' => ['%'],
457 'range' => [
458 '%' => ['min' => 70, 'max' => 160],
459 ],
460 'default' => [
461 'size' => 105,
462 'unit' => '%',
463 ],
464 'selectors' => [
465 '{{WRAPPER}} .king-addons-gradient-mesh' => '--kng-mesh-contrast: {{SIZE}}{{UNIT}};',
466 ],
467 ]
468 );
469
470 $this->add_responsive_control(
471 'kng_mesh_blur',
472 [
473 'label' => esc_html__('Blur', 'king-addons'),
474 'type' => Controls_Manager::SLIDER,
475 'size_units' => ['px'],
476 'range' => [
477 'px' => ['min' => 0, 'max' => 60],
478 ],
479 'default' => [
480 'size' => 0,
481 'unit' => 'px',
482 ],
483 'selectors' => [
484 '{{WRAPPER}} .king-addons-gradient-mesh' => '--kng-mesh-blur: {{SIZE}}{{UNIT}};',
485 ],
486 ]
487 );
488
489 $this->add_control(
490 'kng_mesh_noise',
491 [
492 'label' => esc_html__('Noise Layer', 'king-addons'),
493 'type' => Controls_Manager::SWITCHER,
494 'return_value' => 'yes',
495 'default' => '',
496 ]
497 );
498
499 $this->add_control(
500 'kng_mesh_noise_opacity',
501 [
502 'label' => esc_html__('Noise Opacity', 'king-addons'),
503 'type' => Controls_Manager::SLIDER,
504 'size_units' => ['custom'],
505 'range' => [
506 'custom' => ['min' => 0, 'max' => 0.4, 'step' => 0.02],
507 ],
508 'default' => [
509 'size' => 0.12,
510 'unit' => 'custom',
511 ],
512 'condition' => [
513 'kng_mesh_noise' => 'yes',
514 ],
515 'selectors' => [
516 '{{WRAPPER}} .king-addons-gradient-mesh' => '--kng-mesh-noise-opacity: {{SIZE}};',
517 ],
518 ]
519 );
520
521 $this->end_controls_section();
522 }
523
524 protected function register_style_overlay_controls(): void
525 {
526 $this->start_controls_section(
527 'kng_mesh_style_overlay',
528 [
529 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Overlay Style', 'king-addons'),
530 'tab' => Controls_Manager::TAB_STYLE,
531 'condition' => [
532 'kng_mesh_show_overlay' => 'yes',
533 ],
534 ]
535 );
536
537 $this->add_control(
538 'kng_mesh_content_box_heading',
539 [
540 'label' => esc_html__('Content Box', 'king-addons'),
541 'type' => Controls_Manager::HEADING,
542 'condition' => [
543 'kng_mesh_overlay_source' => 'basic',
544 ],
545 ]
546 );
547
548 $this->add_group_control(
549 Group_Control_Background::get_type(),
550 [
551 'name' => 'kng_mesh_content_background',
552 'selector' => '{{WRAPPER}} .king-addons-gradient-mesh__content',
553 'types' => ['classic', 'gradient'],
554 'condition' => [
555 'kng_mesh_overlay_source' => 'basic',
556 ],
557 ]
558 );
559
560 $this->add_group_control(
561 Group_Control_Border::get_type(),
562 [
563 'name' => 'kng_mesh_content_border',
564 'selector' => '{{WRAPPER}} .king-addons-gradient-mesh__content',
565 'condition' => [
566 'kng_mesh_overlay_source' => 'basic',
567 ],
568 ]
569 );
570
571 $this->add_group_control(
572 Group_Control_Box_Shadow::get_type(),
573 [
574 'name' => 'kng_mesh_content_shadow',
575 'selector' => '{{WRAPPER}} .king-addons-gradient-mesh__content',
576 'condition' => [
577 'kng_mesh_overlay_source' => 'basic',
578 ],
579 ]
580 );
581
582 $this->add_responsive_control(
583 'kng_mesh_content_radius',
584 [
585 'label' => esc_html__('Content Radius', 'king-addons'),
586 'type' => Controls_Manager::DIMENSIONS,
587 'size_units' => ['px', '%'],
588 'selectors' => [
589 '{{WRAPPER}} .king-addons-gradient-mesh__content' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
590 ],
591 'condition' => [
592 'kng_mesh_overlay_source' => 'basic',
593 ],
594 ]
595 );
596
597 $this->add_responsive_control(
598 'kng_mesh_content_padding',
599 [
600 'label' => esc_html__('Content Padding', 'king-addons'),
601 'type' => Controls_Manager::DIMENSIONS,
602 'size_units' => ['px', 'em'],
603 'selectors' => [
604 '{{WRAPPER}} .king-addons-gradient-mesh__content' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
605 ],
606 'condition' => [
607 'kng_mesh_overlay_source' => 'basic',
608 ],
609 ]
610 );
611
612 $this->add_responsive_control(
613 'kng_mesh_content_width',
614 [
615 'label' => esc_html__('Content Width', 'king-addons'),
616 'type' => Controls_Manager::SLIDER,
617 'size_units' => ['px', '%'],
618 'range' => [
619 'px' => ['min' => 240, 'max' => 960],
620 '%' => ['min' => 50, 'max' => 100],
621 ],
622 'selectors' => [
623 '{{WRAPPER}} .king-addons-gradient-mesh__content' => 'max-width: {{SIZE}}{{UNIT}};',
624 ],
625 'condition' => [
626 'kng_mesh_overlay_source' => 'basic',
627 ],
628 ]
629 );
630
631 $this->add_responsive_control(
632 'kng_mesh_content_gap',
633 [
634 'label' => esc_html__('Content Gap', 'king-addons'),
635 'type' => Controls_Manager::SLIDER,
636 'size_units' => ['px'],
637 'range' => [
638 'px' => ['min' => 0, 'max' => 40],
639 ],
640 'selectors' => [
641 '{{WRAPPER}} .king-addons-gradient-mesh__content' => 'gap: {{SIZE}}{{UNIT}};',
642 ],
643 'condition' => [
644 'kng_mesh_overlay_source' => 'basic',
645 ],
646 ]
647 );
648
649 $this->add_group_control(
650 Group_Control_Typography::get_type(),
651 [
652 'name' => 'kng_mesh_title_typography',
653 'selector' => '{{WRAPPER}} .king-addons-gradient-mesh__title',
654 ]
655 );
656
657 $this->add_control(
658 'kng_mesh_title_color',
659 [
660 'label' => esc_html__('Title Color', 'king-addons'),
661 'type' => Controls_Manager::COLOR,
662 'selectors' => [
663 '{{WRAPPER}} .king-addons-gradient-mesh__title' => 'color: {{VALUE}};',
664 ],
665 ]
666 );
667
668 $this->add_group_control(
669 Group_Control_Typography::get_type(),
670 [
671 'name' => 'kng_mesh_subtitle_typography',
672 'selector' => '{{WRAPPER}} .king-addons-gradient-mesh__subtitle',
673 ]
674 );
675
676 $this->add_control(
677 'kng_mesh_subtitle_color',
678 [
679 'label' => esc_html__('Subtitle Color', 'king-addons'),
680 'type' => Controls_Manager::COLOR,
681 'selectors' => [
682 '{{WRAPPER}} .king-addons-gradient-mesh__subtitle' => 'color: {{VALUE}};',
683 ],
684 ]
685 );
686
687 $this->add_group_control(
688 Group_Control_Typography::get_type(),
689 [
690 'name' => 'kng_mesh_text_typography',
691 'selector' => '{{WRAPPER}} .king-addons-gradient-mesh__text',
692 ]
693 );
694
695 $this->add_control(
696 'kng_mesh_text_color',
697 [
698 'label' => esc_html__('Text Color', 'king-addons'),
699 'type' => Controls_Manager::COLOR,
700 'selectors' => [
701 '{{WRAPPER}} .king-addons-gradient-mesh__text' => 'color: {{VALUE}};',
702 ],
703 ]
704 );
705
706 $this->add_control(
707 'kng_mesh_button_heading',
708 [
709 'label' => esc_html__('Button', 'king-addons'),
710 'type' => Controls_Manager::HEADING,
711 'separator' => 'before',
712 ]
713 );
714
715 $this->add_responsive_control(
716 'kng_mesh_button_padding',
717 [
718 'label' => esc_html__('Button Padding', 'king-addons'),
719 'type' => Controls_Manager::DIMENSIONS,
720 'size_units' => ['px', 'em'],
721 'selectors' => [
722 '{{WRAPPER}} .king-addons-gradient-mesh__button' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
723 ],
724 ]
725 );
726
727 $this->add_group_control(
728 Group_Control_Typography::get_type(),
729 [
730 'name' => 'kng_mesh_button_typography',
731 'selector' => '{{WRAPPER}} .king-addons-gradient-mesh__button',
732 ]
733 );
734
735 $this->add_control(
736 'kng_mesh_button_color',
737 [
738 'label' => esc_html__('Button Text Color', 'king-addons'),
739 'type' => Controls_Manager::COLOR,
740 'selectors' => [
741 '{{WRAPPER}} .king-addons-gradient-mesh__button' => 'color: {{VALUE}};',
742 ],
743 ]
744 );
745
746 $this->add_control(
747 'kng_mesh_button_background',
748 [
749 'label' => esc_html__('Button Background', 'king-addons'),
750 'type' => Controls_Manager::COLOR,
751 'selectors' => [
752 '{{WRAPPER}} .king-addons-gradient-mesh__button' => 'background-color: {{VALUE}};',
753 ],
754 ]
755 );
756
757 $this->add_group_control(
758 Group_Control_Border::get_type(),
759 [
760 'name' => 'kng_mesh_button_border',
761 'selector' => '{{WRAPPER}} .king-addons-gradient-mesh__button',
762 ]
763 );
764
765 $this->add_control(
766 'kng_mesh_button_hover_heading',
767 [
768 'label' => esc_html__('Button Hover', 'king-addons'),
769 'type' => Controls_Manager::HEADING,
770 'separator' => 'before',
771 ]
772 );
773
774 $this->add_control(
775 'kng_mesh_button_hover_color',
776 [
777 'label' => esc_html__('Hover Text Color', 'king-addons'),
778 'type' => Controls_Manager::COLOR,
779 'selectors' => [
780 '{{WRAPPER}} .king-addons-gradient-mesh__button:hover' => 'color: {{VALUE}};',
781 ],
782 ]
783 );
784
785 $this->add_control(
786 'kng_mesh_button_hover_background',
787 [
788 'label' => esc_html__('Hover Background', 'king-addons'),
789 'type' => Controls_Manager::COLOR,
790 'selectors' => [
791 '{{WRAPPER}} .king-addons-gradient-mesh__button:hover' => 'background-color: {{VALUE}};',
792 ],
793 ]
794 );
795
796 $this->add_control(
797 'kng_mesh_button_hover_border_color',
798 [
799 'label' => esc_html__('Hover Border Color', 'king-addons'),
800 'type' => Controls_Manager::COLOR,
801 'selectors' => [
802 '{{WRAPPER}} .king-addons-gradient-mesh__button:hover' => 'border-color: {{VALUE}};',
803 ],
804 ]
805 );
806
807 $this->add_responsive_control(
808 'kng_mesh_button_radius',
809 [
810 'label' => esc_html__('Button Border Radius', 'king-addons'),
811 'type' => Controls_Manager::SLIDER,
812 'size_units' => ['px', '%'],
813 'range' => [
814 'px' => ['min' => 0, 'max' => 50],
815 '%' => ['min' => 0, 'max' => 100],
816 ],
817 'selectors' => [
818 '{{WRAPPER}} .king-addons-gradient-mesh__button' => 'border-radius: {{SIZE}}{{UNIT}};',
819 ],
820 ]
821 );
822
823 $this->add_group_control(
824 Group_Control_Box_Shadow::get_type(),
825 [
826 'name' => 'kng_mesh_button_shadow',
827 'selector' => '{{WRAPPER}} .king-addons-gradient-mesh__button',
828 ]
829 );
830
831 $this->end_controls_section();
832 }
833
834 protected function register_style_container_controls(): void
835 {
836 $this->start_controls_section(
837 'kng_mesh_style_container',
838 [
839 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Container', 'king-addons'),
840 'tab' => Controls_Manager::TAB_STYLE,
841 ]
842 );
843
844 $this->add_group_control(
845 Group_Control_Border::get_type(),
846 [
847 'name' => 'kng_mesh_container_border',
848 'selector' => '{{WRAPPER}} .king-addons-gradient-mesh',
849 ]
850 );
851
852 $this->add_group_control(
853 Group_Control_Box_Shadow::get_type(),
854 [
855 'name' => 'kng_mesh_container_shadow',
856 'selector' => '{{WRAPPER}} .king-addons-gradient-mesh',
857 ]
858 );
859
860 $this->end_controls_section();
861 }
862
863 protected function register_pro_notice_controls(): void
864 {
865 if (!king_addons_freemius()->can_use_premium_code__premium_only()) {
866 Core::renderProFeaturesSection($this, '', Controls_Manager::RAW_HTML, 'interactive-gradient-mesh', [
867 'Per-point controls (positions, colors, blend modes, motion)',
868 'Scroll-driven animation with easing',
869 'Export CSS (static or keyframes) with copy button',
870 'Fallback image options for legacy browsers',
871 'Performance modes and adaptive limits',
872 ]);
873 }
874 }
875
876 protected function render_output(array $settings): void
877 {
878 $preset = $this->get_preset($settings);
879 $points = $this->get_points($settings, $preset);
880 if (empty($points)) {
881 return;
882 }
883
884 $mesh_payload = $this->build_mesh_payload($settings, $preset, $points);
885 $mesh_data = esc_attr(wp_json_encode($mesh_payload));
886
887 $background_css = $this->build_gradient_css($points);
888 $blend_modes = $this->build_blend_modes($points);
889 $base_color = $this->get_base_color($settings, $preset);
890
891 $wrapper_classes = [
892 'king-addons-gradient-mesh',
893 ($settings['kng_mesh_enable_animation'] ?? 'yes') === 'yes' ? 'is-animated' : 'is-static',
894 ($settings['kng_mesh_noise'] ?? '') === 'yes' ? 'has-noise' : '',
895 ];
896
897 $style_vars = $this->build_point_variables($points);
898
899 $motion = $settings['kng_mesh_motion'] ?? 'drift';
900 $engine = $settings['kng_mesh_engine'] ?? 'auto';
901 $animate = ($settings['kng_mesh_enable_animation'] ?? 'yes') === 'yes' ? 'yes' : 'no';
902
903 $wrapper_attr = sprintf(
904 'class="%1$s" data-mesh="%2$s" data-motion="%3$s" data-engine="%4$s" data-animate="%5$s" style="%6$s"',
905 esc_attr(trim(implode(' ', array_filter($wrapper_classes)))),
906 $mesh_data,
907 esc_attr($motion),
908 esc_attr($engine),
909 esc_attr($animate),
910 esc_attr($style_vars)
911 );
912
913 $bg_attr = sprintf(
914 'style="background-color:%1$s;background-image:%2$s;background-blend-mode:%3$s;"',
915 esc_attr($base_color),
916 esc_attr($background_css),
917 esc_attr($blend_modes)
918 );
919
920 $overlay_html = $this->get_overlay_content($settings);
921
922 ?>
923 <div <?php echo $wrapper_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> aria-label="<?php echo esc_attr__('Interactive gradient mesh', 'king-addons'); ?>">
924 <div class="king-addons-gradient-mesh__bg" <?php echo $bg_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>></div>
925 <canvas class="king-addons-gradient-mesh__canvas" aria-hidden="true"></canvas>
926 <?php if (!empty($overlay_html)) : ?>
927 <div class="king-addons-gradient-mesh__overlay">
928 <?php echo $overlay_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
929 </div>
930 <?php endif; ?>
931 <?php $this->render_export_panel($settings, $preset, $points); ?>
932 </div>
933 <?php
934 }
935
936 protected function get_overlay_content(array $settings): string
937 {
938 if (($settings['kng_mesh_show_overlay'] ?? 'yes') !== 'yes') {
939 return '';
940 }
941
942 if (($settings['kng_mesh_overlay_source'] ?? 'basic') === 'template' && $this->is_pro()) {
943 $template_id = absint($settings['kng_mesh_overlay_template'] ?? 0);
944 if ($template_id) {
945 $template_content = $this->get_template_content($template_id);
946 if ($template_content !== '') {
947 return '<div class="king-addons-gradient-mesh__template">' . $template_content . '</div>';
948 }
949 }
950 }
951
952 $title = trim((string) ($settings['kng_mesh_title'] ?? ''));
953 $subtitle = trim((string) ($settings['kng_mesh_subtitle'] ?? ''));
954 $text = trim((string) ($settings['kng_mesh_text'] ?? ''));
955 $button_text = trim((string) ($settings['kng_mesh_button_text'] ?? ''));
956 $button_url = $settings['kng_mesh_button_url'] ?? [];
957
958 if ($title === '' && $subtitle === '' && $text === '' && $button_text === '') {
959 return '';
960 }
961
962 $button_html = '';
963 if ($button_text !== '') {
964 $url = !empty($button_url['url']) ? esc_url($button_url['url']) : '#';
965 $target = !empty($button_url['is_external']) ? ' target="_blank"' : '';
966 $nofollow = !empty($button_url['nofollow']) ? ' rel="nofollow"' : '';
967 $button_html = sprintf(
968 '<a class="king-addons-gradient-mesh__button" href="%1$s"%2$s%3$s>%4$s</a>',
969 $url,
970 $target,
971 $nofollow,
972 esc_html($button_text)
973 );
974 }
975
976 ob_start();
977 ?>
978 <div class="king-addons-gradient-mesh__content">
979 <?php if ($subtitle !== '') : ?>
980 <div class="king-addons-gradient-mesh__subtitle"><?php echo esc_html($subtitle); ?></div>
981 <?php endif; ?>
982 <?php if ($title !== '') : ?>
983 <h3 class="king-addons-gradient-mesh__title"><?php echo esc_html($title); ?></h3>
984 <?php endif; ?>
985 <?php if ($text !== '') : ?>
986 <div class="king-addons-gradient-mesh__text"><?php echo esc_html($text); ?></div>
987 <?php endif; ?>
988 <?php if ($button_html !== '') : ?>
989 <div class="king-addons-gradient-mesh__actions">
990 <?php echo $button_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
991 </div>
992 <?php endif; ?>
993 </div>
994 <?php
995 return ob_get_clean();
996 }
997
998 protected function render_export_panel(array $settings, array $preset, array $points): void
999 {
1000 if (!$this->is_pro()) {
1001 return;
1002 }
1003
1004 if (!class_exists(Plugin::class) || !Plugin::$instance->editor->is_edit_mode()) {
1005 return;
1006 }
1007
1008 $css = $this->build_export_css($settings, $preset, $points);
1009 if ($css === '') {
1010 return;
1011 }
1012 ?>
1013 <div class="king-addons-gradient-mesh__export" data-export-css="<?php echo esc_attr($css); ?>">
1014 <button type="button" class="king-addons-gradient-mesh__export-copy">
1015 <?php echo esc_html__('Copy CSS', 'king-addons'); ?>
1016 </button>
1017 <textarea class="king-addons-gradient-mesh__export-text" rows="6" readonly><?php echo esc_textarea($css); ?></textarea>
1018 </div>
1019 <?php
1020 }
1021
1022 protected function build_mesh_payload(array $settings, array $preset, array $points): array
1023 {
1024 $speed = (int) ($settings['kng_mesh_speed']['size'] ?? 80);
1025 $payload_points = [];
1026
1027 foreach ($points as $point) {
1028 $payload_points[] = [
1029 'x' => (float) $point['x'],
1030 'y' => (float) $point['y'],
1031 'radius' => (float) $point['radius'],
1032 'intensity' => (float) $point['intensity'],
1033 'feather' => (float) $point['feather'],
1034 'color' => $point['color'],
1035 'blend' => $point['blend'] ?? 'screen',
1036 'motion' => $point['motion'] ?? 'drift',
1037 'amplitude' => (float) ($point['amplitude'] ?? 18),
1038 'frequency' => (float) ($point['frequency'] ?? 0.25),
1039 'phase' => (float) ($point['phase'] ?? 0),
1040 'direction' => (float) ($point['direction'] ?? 0),
1041 'speed' => (float) ($point['speed'] ?? 1),
1042 ];
1043 }
1044
1045 return [
1046 'engine' => $settings['kng_mesh_engine'] ?? 'auto',
1047 'animate' => ($settings['kng_mesh_enable_animation'] ?? 'yes') === 'yes',
1048 'motion' => $settings['kng_mesh_motion'] ?? 'drift',
1049 'speed' => $speed,
1050 'preset' => $settings['kng_mesh_preset'] ?? 'aurora',
1051 'points' => $payload_points,
1052 'fallback' => [
1053 'color' => $this->get_base_color($settings, $preset),
1054 ],
1055 ];
1056 }
1057
1058 protected function build_export_css(array $settings, array $preset, array $points): string
1059 {
1060 return '';
1061 }
1062
1063 protected function get_points(array $settings, array $preset): array
1064 {
1065 return $this->normalize_points($preset['points'] ?? []);
1066 }
1067
1068 protected function get_base_color(array $settings, array $preset): string
1069 {
1070 $custom = trim((string) ($settings['kng_mesh_base_color'] ?? ''));
1071 if ($custom !== '') {
1072 return $custom;
1073 }
1074 return $preset['base'] ?? '#0f172a';
1075 }
1076
1077 protected function get_preset(array $settings): array
1078 {
1079 $presets = $this->get_presets();
1080 $preset_key = $settings['kng_mesh_preset'] ?? 'aurora';
1081
1082 return $presets[$preset_key] ?? reset($presets);
1083 }
1084
1085 protected function get_preset_options(): array
1086 {
1087 $options = [];
1088 foreach ($this->get_presets() as $key => $preset) {
1089 $options[$key] = $preset['label'];
1090 }
1091
1092 return $options;
1093 }
1094
1095 protected function get_presets(): array
1096 {
1097 return [
1098 'aurora' => [
1099 'label' => esc_html__('Aurora Mist', 'king-addons'),
1100 'base' => '#0b1020',
1101 'points' => [
1102 ['color' => '#7fffd4', 'x' => 18, 'y' => 22, 'radius' => 260, 'intensity' => 0.85, 'feather' => 55, 'blend' => 'screen'],
1103 ['color' => '#60a5fa', 'x' => 72, 'y' => 18, 'radius' => 240, 'intensity' => 0.8, 'feather' => 60, 'blend' => 'screen'],
1104 ['color' => '#a78bfa', 'x' => 35, 'y' => 70, 'radius' => 300, 'intensity' => 0.75, 'feather' => 70, 'blend' => 'screen'],
1105 ['color' => '#22d3ee', 'x' => 80, 'y' => 72, 'radius' => 220, 'intensity' => 0.7, 'feather' => 65, 'blend' => 'screen'],
1106 ],
1107 ],
1108 'sunset' => [
1109 'label' => esc_html__('Sunset Bloom', 'king-addons'),
1110 'base' => '#190d1c',
1111 'points' => [
1112 ['color' => '#fb7185', 'x' => 20, 'y' => 30, 'radius' => 280, 'intensity' => 0.9, 'feather' => 60, 'blend' => 'screen'],
1113 ['color' => '#fbbf24', 'x' => 70, 'y' => 24, 'radius' => 230, 'intensity' => 0.7, 'feather' => 55, 'blend' => 'screen'],
1114 ['color' => '#f472b6', 'x' => 36, 'y' => 72, 'radius' => 300, 'intensity' => 0.8, 'feather' => 70, 'blend' => 'screen'],
1115 ['color' => '#f97316', 'x' => 78, 'y' => 68, 'radius' => 210, 'intensity' => 0.65, 'feather' => 65, 'blend' => 'screen'],
1116 ],
1117 ],
1118 'mint' => [
1119 'label' => esc_html__('Mint Fog', 'king-addons'),
1120 'base' => '#061318',
1121 'points' => [
1122 ['color' => '#5eead4', 'x' => 22, 'y' => 26, 'radius' => 260, 'intensity' => 0.8, 'feather' => 60, 'blend' => 'screen'],
1123 ['color' => '#22d3ee', 'x' => 68, 'y' => 22, 'radius' => 240, 'intensity' => 0.75, 'feather' => 60, 'blend' => 'screen'],
1124 ['color' => '#34d399', 'x' => 36, 'y' => 70, 'radius' => 310, 'intensity' => 0.7, 'feather' => 72, 'blend' => 'screen'],
1125 ['color' => '#38bdf8', 'x' => 76, 'y' => 74, 'radius' => 220, 'intensity' => 0.65, 'feather' => 68, 'blend' => 'screen'],
1126 ],
1127 ],
1128 'twilight' => [
1129 'label' => esc_html__('Twilight Drift', 'king-addons'),
1130 'base' => '#0f0b1f',
1131 'points' => [
1132 ['color' => '#818cf8', 'x' => 24, 'y' => 24, 'radius' => 250, 'intensity' => 0.75, 'feather' => 62, 'blend' => 'screen'],
1133 ['color' => '#f472b6', 'x' => 70, 'y' => 28, 'radius' => 260, 'intensity' => 0.7, 'feather' => 60, 'blend' => 'screen'],
1134 ['color' => '#22d3ee', 'x' => 32, 'y' => 74, 'radius' => 310, 'intensity' => 0.68, 'feather' => 72, 'blend' => 'screen'],
1135 ['color' => '#f97316', 'x' => 78, 'y' => 70, 'radius' => 220, 'intensity' => 0.6, 'feather' => 68, 'blend' => 'screen'],
1136 ],
1137 ],
1138 'dusk' => [
1139 'label' => esc_html__('Deep Dusk', 'king-addons'),
1140 'base' => '#0b1120',
1141 'points' => [
1142 ['color' => '#38bdf8', 'x' => 22, 'y' => 18, 'radius' => 240, 'intensity' => 0.7, 'feather' => 60, 'blend' => 'screen'],
1143 ['color' => '#f472b6', 'x' => 70, 'y' => 20, 'radius' => 250, 'intensity' => 0.65, 'feather' => 58, 'blend' => 'screen'],
1144 ['color' => '#34d399', 'x' => 30, 'y' => 74, 'radius' => 300, 'intensity' => 0.6, 'feather' => 72, 'blend' => 'screen'],
1145 ['color' => '#fbbf24', 'x' => 78, 'y' => 72, 'radius' => 210, 'intensity' => 0.55, 'feather' => 68, 'blend' => 'screen'],
1146 ],
1147 ],
1148 'monochrome' => [
1149 'label' => esc_html__('Monochrome Glow', 'king-addons'),
1150 'base' => '#0a0a0f',
1151 'points' => [
1152 ['color' => '#f9fafb', 'x' => 30, 'y' => 24, 'radius' => 260, 'intensity' => 0.4, 'feather' => 60, 'blend' => 'screen'],
1153 ['color' => '#94a3b8', 'x' => 72, 'y' => 28, 'radius' => 240, 'intensity' => 0.35, 'feather' => 58, 'blend' => 'screen'],
1154 ['color' => '#e2e8f0', 'x' => 40, 'y' => 72, 'radius' => 300, 'intensity' => 0.32, 'feather' => 70, 'blend' => 'screen'],
1155 ['color' => '#cbd5f5', 'x' => 78, 'y' => 72, 'radius' => 220, 'intensity' => 0.3, 'feather' => 66, 'blend' => 'screen'],
1156 ],
1157 ],
1158 ];
1159 }
1160
1161 protected function build_point_variables(array $points): string
1162 {
1163 $vars = [];
1164 $index = 1;
1165
1166 foreach ($points as $point) {
1167 $color = $this->to_rgba($point['color'], $point['intensity']);
1168 $fade = $this->to_rgba($point['color'], 0);
1169 $solid_radius = max(0, $point['radius'] - ($point['radius'] * ($point['feather'] / 100)));
1170
1171 $vars[] = sprintf('--kng-mesh-p%1$d-x:%2$s%%', $index, $point['x']);
1172 $vars[] = sprintf('--kng-mesh-p%1$d-y:%2$s%%', $index, $point['y']);
1173 $vars[] = sprintf('--kng-mesh-p%1$d-color:%2$s', $index, $color);
1174 $vars[] = sprintf('--kng-mesh-p%1$d-fade:%2$s', $index, $fade);
1175 $vars[] = sprintf('--kng-mesh-p%1$d-solid:%2$spx', $index, round($solid_radius, 2));
1176 $vars[] = sprintf('--kng-mesh-p%1$d-radius:%2$spx', $index, $point['radius']);
1177 $index++;
1178 }
1179
1180 return implode(';', $vars) . ';';
1181 }
1182
1183 protected function build_gradient_css(array $points): string
1184 {
1185 $layers = [];
1186 $index = 1;
1187
1188 foreach ($points as $point) {
1189 $layers[] = sprintf(
1190 'radial-gradient(circle at var(--kng-mesh-p%1$d-x) var(--kng-mesh-p%1$d-y), var(--kng-mesh-p%1$d-color) 0px, var(--kng-mesh-p%1$d-color) var(--kng-mesh-p%1$d-solid), var(--kng-mesh-p%1$d-fade) var(--kng-mesh-p%1$d-radius))',
1191 $index
1192 );
1193 $index++;
1194 }
1195
1196 return implode(', ', $layers);
1197 }
1198
1199 protected function build_blend_modes(array $points): string
1200 {
1201 $modes = [];
1202 foreach ($points as $point) {
1203 $modes[] = $point['blend'] ?? 'screen';
1204 }
1205
1206 return implode(', ', $modes);
1207 }
1208
1209 protected function to_rgba(string $color, float $alpha): string
1210 {
1211 $alpha = max(0, min(1, $alpha));
1212 $color = trim($color);
1213
1214 if (stripos($color, 'rgba(') === 0) {
1215 if (preg_match('/rgba\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*\\)/i', $color, $matches)) {
1216 return sprintf('rgba(%d, %d, %d, %.3f)', (int) $matches[1], (int) $matches[2], (int) $matches[3], $alpha);
1217 }
1218 return $color;
1219 }
1220
1221 if (stripos($color, 'rgb(') === 0) {
1222 if (preg_match('/rgb\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*,\\s*([\\d.]+)\\s*\\)/i', $color, $matches)) {
1223 return sprintf('rgba(%d, %d, %d, %.3f)', (int) $matches[1], (int) $matches[2], (int) $matches[3], $alpha);
1224 }
1225 return $color;
1226 }
1227
1228 $hex = ltrim($color, '#');
1229 if (strlen($hex) === 3) {
1230 $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
1231 }
1232
1233 if (strlen($hex) !== 6) {
1234 return $color;
1235 }
1236
1237 $rgb = [
1238 hexdec(substr($hex, 0, 2)),
1239 hexdec(substr($hex, 2, 2)),
1240 hexdec(substr($hex, 4, 2)),
1241 ];
1242
1243 return sprintf('rgba(%d, %d, %d, %.3f)', $rgb[0], $rgb[1], $rgb[2], $alpha);
1244 }
1245
1246 /**
1247 * Normalize points input to safe defaults.
1248 *
1249 * @param array $points
1250 * @return array
1251 */
1252 protected function normalize_points(array $points): array
1253 {
1254 $normalized = [];
1255 $limit = 10;
1256
1257 foreach ($points as $point) {
1258 if (count($normalized) >= $limit) {
1259 break;
1260 }
1261 if (!is_array($point)) {
1262 continue;
1263 }
1264 $normalized[] = $this->normalize_point($point);
1265 }
1266
1267 return $normalized;
1268 }
1269
1270 /**
1271 * Normalize a single point definition.
1272 *
1273 * @param array $point
1274 * @return array
1275 */
1276 protected function normalize_point(array $point): array
1277 {
1278 $defaults = [
1279 'color' => '#ffffff',
1280 'x' => 50,
1281 'y' => 50,
1282 'radius' => 240,
1283 'intensity' => 0.8,
1284 'feather' => 60,
1285 'blend' => 'screen',
1286 'motion' => 'drift',
1287 'amplitude' => 18,
1288 'frequency' => 0.25,
1289 'phase' => 0,
1290 'direction' => 0,
1291 'speed' => 1,
1292 ];
1293
1294 $color = $this->normalize_color((string) ($point['color'] ?? $defaults['color']), $defaults['color']);
1295 $blend = strtolower((string) ($point['blend'] ?? $defaults['blend']));
1296 $blend = in_array($blend, ['normal', 'screen', 'overlay', 'soft-light'], true) ? $blend : $defaults['blend'];
1297 $motion = strtolower((string) ($point['motion'] ?? $defaults['motion']));
1298 $motion = in_array($motion, ['static', 'drift', 'orbit', 'noise'], true) ? $motion : $defaults['motion'];
1299
1300 return [
1301 'color' => $color,
1302 'x' => $this->clamp_value($point['x'] ?? $defaults['x'], 0, 100, $defaults['x']),
1303 'y' => $this->clamp_value($point['y'] ?? $defaults['y'], 0, 100, $defaults['y']),
1304 'radius' => $this->clamp_value($point['radius'] ?? $defaults['radius'], 80, 600, $defaults['radius']),
1305 'intensity' => $this->clamp_value($point['intensity'] ?? $defaults['intensity'], 0, 1, $defaults['intensity']),
1306 'feather' => $this->clamp_value($point['feather'] ?? $defaults['feather'], 0, 100, $defaults['feather']),
1307 'blend' => $blend,
1308 'motion' => $motion,
1309 'amplitude' => $this->clamp_value($point['amplitude'] ?? $defaults['amplitude'], 0, 80, $defaults['amplitude']),
1310 'frequency' => $this->clamp_value($point['frequency'] ?? $defaults['frequency'], 0, 2, $defaults['frequency']),
1311 'phase' => $this->clamp_value($point['phase'] ?? $defaults['phase'], 0, 360, $defaults['phase']),
1312 'direction' => $this->clamp_value($point['direction'] ?? $defaults['direction'], -180, 180, $defaults['direction']),
1313 'speed' => $this->clamp_value($point['speed'] ?? $defaults['speed'], 0.1, 3, $defaults['speed']),
1314 ];
1315 }
1316
1317 /**
1318 * Normalize color input.
1319 *
1320 * @param string $color
1321 * @param string $fallback
1322 * @return string
1323 */
1324 protected function normalize_color(string $color, string $fallback): string
1325 {
1326 $color = trim($color);
1327 if ($color === '') {
1328 return $fallback;
1329 }
1330 if (stripos($color, 'rgba(') === 0 || stripos($color, 'rgb(') === 0) {
1331 return $color;
1332 }
1333 $hex = sanitize_hex_color($color);
1334 if (!empty($hex)) {
1335 return $hex;
1336 }
1337 return $fallback;
1338 }
1339
1340 /**
1341 * Clamp numeric values safely.
1342 *
1343 * @param mixed $value
1344 * @param float $min
1345 * @param float $max
1346 * @param float $fallback
1347 * @return float
1348 */
1349 protected function clamp_value($value, float $min, float $max, float $fallback): float
1350 {
1351 if (!is_numeric($value)) {
1352 return $fallback;
1353 }
1354 $value = (float) $value;
1355 if ($value < $min) {
1356 return $min;
1357 }
1358 if ($value > $max) {
1359 return $max;
1360 }
1361 return $value;
1362 }
1363
1364 /**
1365 * Get Elementor template content.
1366 *
1367 * @param int $template_id
1368 * @return string
1369 */
1370 protected function get_template_content(int $template_id): string
1371 {
1372 if ($template_id <= 0 || !class_exists(Plugin::class)) {
1373 return '';
1374 }
1375
1376 $has_css = 'internal' === get_option('elementor_css_print_method');
1377 return Plugin::instance()->frontend->get_builder_content_for_display($template_id, $has_css);
1378 }
1379
1380 /**
1381 * Get Elementor templates options.
1382 *
1383 * @return array<string, string>
1384 */
1385 protected function get_elementor_templates_options(): array
1386 {
1387 if (!class_exists(Plugin::class)) {
1388 return [];
1389 }
1390
1391 $options = [];
1392 $templates = Plugin::$instance->templates_manager->get_source('local')->get_items();
1393 if (!empty($templates)) {
1394 foreach ($templates as $template) {
1395 $options[$template['template_id']] = $template['title'];
1396 }
1397 }
1398
1399 return $options;
1400 }
1401
1402 protected function is_pro(): bool
1403 {
1404 return false;
1405 }
1406 }
1407