PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.65
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.65
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.65, at includes/widgets/Interactive_Gradient_Mesh/Interactive_Gradient_Mesh.php

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