PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.63
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.63
51.1.86 51.1.84 51.1.85 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 All 40 releases
king-addons / includes / widgets / Reveal_Swipe_Cards / Reveal_Swipe_Cards.php

Reveal_Swipe_Cards.php in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.63, at includes/widgets/Reveal_Swipe_Cards/Reveal_Swipe_Cards.php

1,542 lines 49.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Reveal Swipe Cards Widget.
4 *
5 * Creative card widget with wipe-reveal effect triggered by hover or scroll.
6 * Pro version adds custom mask shapes, blur edge, touch swipe, and sequence mode.
7 *
8 * @package King_Addons
9 */
10
11 namespace King_Addons;
12
13 use Elementor\Controls_Manager;
14 use Elementor\Group_Control_Background;
15 use Elementor\Group_Control_Border;
16 use Elementor\Group_Control_Box_Shadow;
17 use Elementor\Group_Control_Typography;
18 use Elementor\Icons_Manager;
19 use Elementor\Plugin;
20 use Elementor\Repeater;
21 use Elementor\Widget_Base;
22
23 if (!defined('ABSPATH')) {
24 exit;
25 }
26
27 /**
28 * Class Reveal_Swipe_Cards
29 *
30 * Elementor widget for animated reveal cards with wipe effect.
31 */
32 class Reveal_Swipe_Cards extends Widget_Base
33 {
34 /**
35 * Get widget name.
36 *
37 * @return string Widget name.
38 */
39 public function get_name(): string
40 {
41 return 'king-addons-reveal-swipe-cards';
42 }
43
44 /**
45 * Get widget title.
46 *
47 * @return string Widget title.
48 */
49 public function get_title(): string
50 {
51 return esc_html__('Reveal Swipe Cards', 'king-addons');
52 }
53
54 /**
55 * Get widget icon.
56 *
57 * @return string Widget icon.
58 */
59 public function get_icon(): string
60 {
61 return 'eicon-slides';
62 }
63
64 /**
65 * Get widget categories.
66 *
67 * @return array Widget categories.
68 */
69 public function get_categories(): array
70 {
71 return ['king-addons'];
72 }
73
74 /**
75 * Get widget keywords.
76 *
77 * @return array Widget keywords.
78 */
79 public function get_keywords(): array
80 {
81 return ['reveal', 'swipe', 'cards', 'wipe', 'hover', 'animation', 'creative', 'king-addons'];
82 }
83
84 /**
85 * Get style dependencies.
86 *
87 * @return array Style handles.
88 */
89 public function get_style_depends(): array
90 {
91 return [KING_ADDONS_ASSETS_UNIQUE_KEY . '-reveal-swipe-cards-style'];
92 }
93
94 /**
95 * Get script dependencies.
96 *
97 * @return array Script handles.
98 */
99 public function get_script_depends(): array
100 {
101 return [KING_ADDONS_ASSETS_UNIQUE_KEY . '-reveal-swipe-cards-script'];
102 }
103
104 public function get_custom_help_url()
105 {
106 return 'mailto:[email protected]?subject=Bug Report - King Addons&body=Please describe the issue';
107 }
108
109 /**
110 * Register widget controls.
111 *
112 * @return void
113 */
114 protected function register_controls(): void
115 {
116 $this->register_cards_controls();
117 $this->register_layout_controls();
118 $this->register_reveal_controls();
119 $this->register_style_card_controls();
120 $this->register_style_content_controls();
121 $this->register_style_button_controls();
122 $this->register_style_badge_controls();
123 $this->register_style_overlay_controls();
124 $this->register_pro_notice_controls();
125 }
126
127 /**
128 * Register cards repeater controls.
129 *
130 * @return void
131 */
132 protected function register_cards_controls(): void
133 {
134 $this->start_controls_section(
135 'kng_rsc_cards_section',
136 [
137 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Cards', 'king-addons'),
138 'tab' => Controls_Manager::TAB_CONTENT,
139 ]
140 );
141
142 $repeater = new Repeater();
143
144 $repeater->add_control(
145 'media_type',
146 [
147 'label' => esc_html__('Media Type', 'king-addons'),
148 'type' => Controls_Manager::SELECT,
149 'default' => 'image',
150 'options' => [
151 'none' => esc_html__('None', 'king-addons'),
152 'image' => esc_html__('Image', 'king-addons'),
153 'icon' => esc_html__('Icon', 'king-addons'),
154 ],
155 ]
156 );
157
158 $repeater->add_control(
159 'image',
160 [
161 'label' => esc_html__('Image', 'king-addons'),
162 'type' => Controls_Manager::MEDIA,
163 'default' => [
164 'url' => \Elementor\Utils::get_placeholder_image_src(),
165 ],
166 'condition' => [
167 'media_type' => 'image',
168 ],
169 ]
170 );
171
172 $repeater->add_control(
173 'icon',
174 [
175 'label' => esc_html__('Icon', 'king-addons'),
176 'type' => Controls_Manager::ICONS,
177 'default' => [
178 'value' => 'fas fa-star',
179 'library' => 'fa-solid',
180 ],
181 'condition' => [
182 'media_type' => 'icon',
183 ],
184 ]
185 );
186
187 $repeater->add_control(
188 'title',
189 [
190 'label' => esc_html__('Title', 'king-addons'),
191 'type' => Controls_Manager::TEXT,
192 'default' => esc_html__('Card Title', 'king-addons'),
193 'label_block' => true,
194 ]
195 );
196
197 $repeater->add_control(
198 'description',
199 [
200 'label' => esc_html__('Description', 'king-addons'),
201 'type' => Controls_Manager::TEXTAREA,
202 'default' => esc_html__('Card description goes here. Add your content.', 'king-addons'),
203 'rows' => 4,
204 ]
205 );
206
207 $repeater->add_control(
208 'button_text',
209 [
210 'label' => esc_html__('Button Text', 'king-addons'),
211 'type' => Controls_Manager::TEXT,
212 'default' => esc_html__('Learn More', 'king-addons'),
213 ]
214 );
215
216 $repeater->add_control(
217 'button_link',
218 [
219 'label' => esc_html__('Button Link', 'king-addons'),
220 'type' => Controls_Manager::URL,
221 'placeholder' => esc_html__('https://your-link.com', 'king-addons'),
222 ]
223 );
224
225 $repeater->add_control(
226 'badge_enabled',
227 [
228 'label' => esc_html__('Show Badge', 'king-addons'),
229 'type' => Controls_Manager::SWITCHER,
230 'separator' => 'before',
231 ]
232 );
233
234 $repeater->add_control(
235 'badge_text',
236 [
237 'label' => esc_html__('Badge Text', 'king-addons'),
238 'type' => Controls_Manager::TEXT,
239 'default' => esc_html__('New', 'king-addons'),
240 'condition' => [
241 'badge_enabled' => 'yes',
242 ],
243 ]
244 );
245
246 $this->add_control(
247 'kng_rsc_cards',
248 [
249 'label' => esc_html__('Cards', 'king-addons'),
250 'type' => Controls_Manager::REPEATER,
251 'fields' => $repeater->get_controls(),
252 'default' => $this->get_default_cards(),
253 'title_field' => '{{ title }}',
254 ]
255 );
256
257 $this->end_controls_section();
258 }
259
260 /**
261 * Register layout controls.
262 *
263 * @return void
264 */
265 protected function register_layout_controls(): void
266 {
267 $this->start_controls_section(
268 'kng_rsc_layout_section',
269 [
270 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Layout', 'king-addons'),
271 'tab' => Controls_Manager::TAB_CONTENT,
272 ]
273 );
274
275 $this->add_responsive_control(
276 'kng_rsc_columns',
277 [
278 'label' => esc_html__('Columns', 'king-addons'),
279 'type' => Controls_Manager::SELECT,
280 'default' => '3',
281 'tablet_default' => '2',
282 'mobile_default' => '1',
283 'options' => [
284 '1' => '1',
285 '2' => '2',
286 '3' => '3',
287 '4' => '4',
288 '5' => '5',
289 '6' => '6',
290 ],
291 'selectors' => [
292 '{{WRAPPER}} .kng-rsc' => '--kng-rsc-columns: {{VALUE}};',
293 ],
294 ]
295 );
296
297 $this->add_responsive_control(
298 'kng_rsc_gap',
299 [
300 'label' => esc_html__('Gap', 'king-addons'),
301 'type' => Controls_Manager::SLIDER,
302 'size_units' => ['px', 'em', 'rem'],
303 'range' => [
304 'px' => ['min' => 0, 'max' => 100],
305 ],
306 'default' => [
307 'size' => 24,
308 'unit' => 'px',
309 ],
310 'selectors' => [
311 '{{WRAPPER}} .kng-rsc' => '--kng-rsc-gap: {{SIZE}}{{UNIT}};',
312 ],
313 ]
314 );
315
316 $this->add_responsive_control(
317 'kng_rsc_min_height',
318 [
319 'label' => esc_html__('Card Min Height', 'king-addons'),
320 'type' => Controls_Manager::SLIDER,
321 'size_units' => ['px', 'vh'],
322 'range' => [
323 'px' => ['min' => 150, 'max' => 800],
324 'vh' => ['min' => 20, 'max' => 100],
325 ],
326 'default' => [
327 'size' => 320,
328 'unit' => 'px',
329 ],
330 'selectors' => [
331 '{{WRAPPER}} .kng-rsc' => '--kng-rsc-min-height: {{SIZE}}{{UNIT}};',
332 ],
333 ]
334 );
335
336 $this->add_control(
337 'kng_rsc_content_align',
338 [
339 'label' => esc_html__('Content Alignment', 'king-addons'),
340 'type' => Controls_Manager::CHOOSE,
341 'default' => 'center',
342 'options' => [
343 'flex-start' => [
344 'title' => esc_html__('Top', 'king-addons'),
345 'icon' => 'eicon-v-align-top',
346 ],
347 'center' => [
348 'title' => esc_html__('Middle', 'king-addons'),
349 'icon' => 'eicon-v-align-middle',
350 ],
351 'flex-end' => [
352 'title' => esc_html__('Bottom', 'king-addons'),
353 'icon' => 'eicon-v-align-bottom',
354 ],
355 ],
356 'selectors' => [
357 '{{WRAPPER}} .kng-rsc' => '--kng-rsc-content-align: {{VALUE}};',
358 ],
359 ]
360 );
361
362 $this->add_control(
363 'kng_rsc_text_align',
364 [
365 'label' => esc_html__('Text Alignment', 'king-addons'),
366 'type' => Controls_Manager::CHOOSE,
367 'default' => 'center',
368 'options' => [
369 'left' => [
370 'title' => esc_html__('Left', 'king-addons'),
371 'icon' => 'eicon-text-align-left',
372 ],
373 'center' => [
374 'title' => esc_html__('Center', 'king-addons'),
375 'icon' => 'eicon-text-align-center',
376 ],
377 'right' => [
378 'title' => esc_html__('Right', 'king-addons'),
379 'icon' => 'eicon-text-align-right',
380 ],
381 ],
382 'selectors' => [
383 '{{WRAPPER}} .kng-rsc' => '--kng-rsc-text-align: {{VALUE}};',
384 ],
385 ]
386 );
387
388 $this->end_controls_section();
389 }
390
391 /**
392 * Register reveal settings controls.
393 *
394 * @return void
395 */
396 protected function register_reveal_controls(): void
397 {
398 $this->start_controls_section(
399 'kng_rsc_reveal_section',
400 [
401 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Reveal Settings', 'king-addons'),
402 'tab' => Controls_Manager::TAB_CONTENT,
403 ]
404 );
405
406 $this->add_control(
407 'kng_rsc_trigger',
408 [
409 'label' => esc_html__('Trigger', 'king-addons'),
410 'type' => Controls_Manager::SELECT,
411 'default' => 'hover',
412 'options' => [
413 'hover' => esc_html__('Hover', 'king-addons'),
414 'scroll' => esc_html__('Scroll', 'king-addons'),
415 'both' => esc_html__('Hover + Scroll', 'king-addons'),
416 ],
417 ]
418 );
419
420 $this->add_control(
421 'kng_rsc_direction',
422 [
423 'label' => esc_html__('Direction', 'king-addons'),
424 'type' => Controls_Manager::SELECT,
425 'default' => 'left',
426 'options' => [
427 'left' => esc_html__('Left to Right', 'king-addons'),
428 'right' => esc_html__('Right to Left', 'king-addons'),
429 'top' => esc_html__('Top to Bottom', 'king-addons'),
430 'bottom' => esc_html__('Bottom to Top', 'king-addons'),
431 ],
432 ]
433 );
434
435 $this->add_control(
436 'kng_rsc_duration',
437 [
438 'label' => esc_html__('Duration (ms)', 'king-addons'),
439 'type' => Controls_Manager::SLIDER,
440 'range' => [
441 'px' => ['min' => 150, 'max' => 2000, 'step' => 50],
442 ],
443 'default' => [
444 'size' => 500,
445 ],
446 'selectors' => [
447 '{{WRAPPER}} .kng-rsc' => '--kng-rsc-duration: {{SIZE}}ms;',
448 ],
449 ]
450 );
451
452 $this->add_control(
453 'kng_rsc_easing',
454 [
455 'label' => esc_html__('Easing', 'king-addons'),
456 'type' => Controls_Manager::SELECT,
457 'default' => 'ease-out',
458 'options' => [
459 'linear' => esc_html__('Linear', 'king-addons'),
460 'ease' => esc_html__('Ease', 'king-addons'),
461 'ease-in' => esc_html__('Ease In', 'king-addons'),
462 'ease-out' => esc_html__('Ease Out', 'king-addons'),
463 'ease-in-out' => esc_html__('Ease In Out', 'king-addons'),
464 'cubic-bezier(0.4, 0, 0.2, 1)' => esc_html__('Smooth', 'king-addons'),
465 ],
466 'selectors' => [
467 '{{WRAPPER}} .kng-rsc' => '--kng-rsc-easing: {{VALUE}};',
468 ],
469 ]
470 );
471
472 $this->add_control(
473 'kng_rsc_reset_on_leave',
474 [
475 'label' => esc_html__('Reset on Hover Leave', 'king-addons'),
476 'type' => Controls_Manager::SWITCHER,
477 'default' => 'yes',
478 'condition' => [
479 'kng_rsc_trigger' => ['hover', 'both'],
480 ],
481 ]
482 );
483
484 $this->add_control(
485 'kng_rsc_scroll_heading',
486 [
487 'label' => esc_html__('Scroll Options', 'king-addons'),
488 'type' => Controls_Manager::HEADING,
489 'separator' => 'before',
490 'condition' => [
491 'kng_rsc_trigger' => ['scroll', 'both'],
492 ],
493 ]
494 );
495
496 $this->add_control(
497 'kng_rsc_scroll_threshold',
498 [
499 'label' => esc_html__('Threshold (%)', 'king-addons'),
500 'type' => Controls_Manager::SLIDER,
501 'range' => [
502 'px' => ['min' => 0, 'max' => 100],
503 ],
504 'default' => [
505 'size' => 30,
506 ],
507 'condition' => [
508 'kng_rsc_trigger' => ['scroll', 'both'],
509 ],
510 ]
511 );
512
513 $this->add_control(
514 'kng_rsc_scroll_once',
515 [
516 'label' => esc_html__('Reveal Once', 'king-addons'),
517 'type' => Controls_Manager::SWITCHER,
518 'default' => 'yes',
519 'condition' => [
520 'kng_rsc_trigger' => ['scroll', 'both'],
521 ],
522 ]
523 );
524
525 $this->add_control(
526 'kng_rsc_scroll_reset',
527 [
528 'label' => esc_html__('Reset When Out of View', 'king-addons'),
529 'type' => Controls_Manager::SWITCHER,
530 'condition' => [
531 'kng_rsc_trigger' => ['scroll', 'both'],
532 'kng_rsc_scroll_once!' => 'yes',
533 ],
534 ]
535 );
536
537 $this->end_controls_section();
538 }
539
540 /**
541 * Register card style controls.
542 *
543 * @return void
544 */
545 protected function register_style_card_controls(): void
546 {
547 $this->start_controls_section(
548 'kng_rsc_style_card_section',
549 [
550 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Card', 'king-addons'),
551 'tab' => Controls_Manager::TAB_STYLE,
552 ]
553 );
554
555 $this->add_group_control(
556 Group_Control_Background::get_type(),
557 [
558 'name' => 'kng_rsc_card_bg',
559 'selector' => '{{WRAPPER}} .kng-rsc-card__content',
560 ]
561 );
562
563 $this->add_group_control(
564 Group_Control_Border::get_type(),
565 [
566 'name' => 'kng_rsc_card_border',
567 'selector' => '{{WRAPPER}} .kng-rsc-card',
568 ]
569 );
570
571 $this->add_responsive_control(
572 'kng_rsc_card_radius',
573 [
574 'label' => esc_html__('Border Radius', 'king-addons'),
575 'type' => Controls_Manager::DIMENSIONS,
576 'size_units' => ['px', '%'],
577 'selectors' => [
578 '{{WRAPPER}} .kng-rsc-card' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
579 ],
580 ]
581 );
582
583 $this->add_group_control(
584 Group_Control_Box_Shadow::get_type(),
585 [
586 'name' => 'kng_rsc_card_shadow',
587 'selector' => '{{WRAPPER}} .kng-rsc-card',
588 ]
589 );
590
591 $this->add_responsive_control(
592 'kng_rsc_card_padding',
593 [
594 'label' => esc_html__('Padding', 'king-addons'),
595 'type' => Controls_Manager::DIMENSIONS,
596 'size_units' => ['px', 'em'],
597 'selectors' => [
598 '{{WRAPPER}} .kng-rsc-card__inner' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
599 ],
600 ]
601 );
602
603 $this->add_control(
604 'kng_rsc_card_hover_heading',
605 [
606 'label' => esc_html__('Hover Effects', 'king-addons'),
607 'type' => Controls_Manager::HEADING,
608 'separator' => 'before',
609 ]
610 );
611
612 $this->add_control(
613 'kng_rsc_card_hover_transform',
614 [
615 'label' => esc_html__('Hover Transform', 'king-addons'),
616 'type' => Controls_Manager::SELECT,
617 'default' => 'none',
618 'options' => [
619 'none' => esc_html__('None', 'king-addons'),
620 'lift' => esc_html__('Lift Up', 'king-addons'),
621 'scale' => esc_html__('Scale Up', 'king-addons'),
622 'tilt' => esc_html__('Tilt', 'king-addons'),
623 ],
624 ]
625 );
626
627 $this->add_group_control(
628 Group_Control_Box_Shadow::get_type(),
629 [
630 'name' => 'kng_rsc_card_hover_shadow',
631 'label' => esc_html__('Hover Shadow', 'king-addons'),
632 'selector' => '{{WRAPPER}} .kng-rsc-card:hover',
633 ]
634 );
635
636 $this->end_controls_section();
637 }
638
639 /**
640 * Register content style controls (title, description, icon/image).
641 *
642 * @return void
643 */
644 protected function register_style_content_controls(): void
645 {
646 $this->start_controls_section(
647 'kng_rsc_style_content_section',
648 [
649 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Content', 'king-addons'),
650 'tab' => Controls_Manager::TAB_STYLE,
651 ]
652 );
653
654 // Media (icon/image)
655 $this->add_control(
656 'kng_rsc_media_heading',
657 [
658 'label' => esc_html__('Media', 'king-addons'),
659 'type' => Controls_Manager::HEADING,
660 ]
661 );
662
663 $this->add_responsive_control(
664 'kng_rsc_icon_size',
665 [
666 'label' => esc_html__('Icon Size', 'king-addons'),
667 'type' => Controls_Manager::SLIDER,
668 'size_units' => ['px'],
669 'range' => [
670 'px' => ['min' => 16, 'max' => 120],
671 ],
672 'selectors' => [
673 '{{WRAPPER}} .kng-rsc-card__icon' => 'font-size: {{SIZE}}{{UNIT}};',
674 '{{WRAPPER}} .kng-rsc-card__icon svg' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}};',
675 ],
676 ]
677 );
678
679 $this->add_control(
680 'kng_rsc_icon_color',
681 [
682 'label' => esc_html__('Icon Color', 'king-addons'),
683 'type' => Controls_Manager::COLOR,
684 'selectors' => [
685 '{{WRAPPER}} .kng-rsc-card__icon' => 'color: {{VALUE}};',
686 '{{WRAPPER}} .kng-rsc-card__icon svg' => 'fill: {{VALUE}};',
687 ],
688 ]
689 );
690
691 $this->add_control(
692 'kng_rsc_icon_bg_color',
693 [
694 'label' => esc_html__('Icon Background', 'king-addons'),
695 'type' => Controls_Manager::COLOR,
696 'selectors' => [
697 '{{WRAPPER}} .kng-rsc-card__icon' => 'background-color: {{VALUE}};',
698 ],
699 ]
700 );
701
702 $this->add_responsive_control(
703 'kng_rsc_icon_padding',
704 [
705 'label' => esc_html__('Icon Padding', 'king-addons'),
706 'type' => Controls_Manager::SLIDER,
707 'size_units' => ['px'],
708 'range' => [
709 'px' => ['min' => 0, 'max' => 50],
710 ],
711 'selectors' => [
712 '{{WRAPPER}} .kng-rsc-card__icon' => 'padding: {{SIZE}}{{UNIT}};',
713 ],
714 ]
715 );
716
717 $this->add_responsive_control(
718 'kng_rsc_icon_radius',
719 [
720 'label' => esc_html__('Icon Border Radius', 'king-addons'),
721 'type' => Controls_Manager::DIMENSIONS,
722 'size_units' => ['px', '%'],
723 'selectors' => [
724 '{{WRAPPER}} .kng-rsc-card__icon' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
725 ],
726 ]
727 );
728
729 $this->add_responsive_control(
730 'kng_rsc_image_width',
731 [
732 'label' => esc_html__('Image Width', 'king-addons'),
733 'type' => Controls_Manager::SLIDER,
734 'size_units' => ['px', '%'],
735 'range' => [
736 'px' => ['min' => 40, 'max' => 400],
737 '%' => ['min' => 10, 'max' => 100],
738 ],
739 'selectors' => [
740 '{{WRAPPER}} .kng-rsc-card__image' => 'width: {{SIZE}}{{UNIT}};',
741 ],
742 ]
743 );
744
745 $this->add_responsive_control(
746 'kng_rsc_image_radius',
747 [
748 'label' => esc_html__('Image Border Radius', 'king-addons'),
749 'type' => Controls_Manager::DIMENSIONS,
750 'size_units' => ['px', '%'],
751 'selectors' => [
752 '{{WRAPPER}} .kng-rsc-card__image' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
753 ],
754 ]
755 );
756
757 $this->add_responsive_control(
758 'kng_rsc_media_spacing',
759 [
760 'label' => esc_html__('Media Spacing', 'king-addons'),
761 'type' => Controls_Manager::SLIDER,
762 'size_units' => ['px'],
763 'range' => [
764 'px' => ['min' => 0, 'max' => 60],
765 ],
766 'selectors' => [
767 '{{WRAPPER}} .kng-rsc-card__media' => 'margin-bottom: {{SIZE}}{{UNIT}};',
768 ],
769 ]
770 );
771
772 // Title
773 $this->add_control(
774 'kng_rsc_title_heading',
775 [
776 'label' => esc_html__('Title', 'king-addons'),
777 'type' => Controls_Manager::HEADING,
778 'separator' => 'before',
779 ]
780 );
781
782 $this->add_group_control(
783 Group_Control_Typography::get_type(),
784 [
785 'name' => 'kng_rsc_title_typography',
786 'selector' => '{{WRAPPER}} .kng-rsc-card__title',
787 ]
788 );
789
790 $this->add_control(
791 'kng_rsc_title_color',
792 [
793 'label' => esc_html__('Title Color', 'king-addons'),
794 'type' => Controls_Manager::COLOR,
795 'selectors' => [
796 '{{WRAPPER}} .kng-rsc-card__title' => 'color: {{VALUE}};',
797 ],
798 ]
799 );
800
801 $this->add_responsive_control(
802 'kng_rsc_title_spacing',
803 [
804 'label' => esc_html__('Title Spacing', 'king-addons'),
805 'type' => Controls_Manager::SLIDER,
806 'size_units' => ['px'],
807 'range' => [
808 'px' => ['min' => 0, 'max' => 40],
809 ],
810 'selectors' => [
811 '{{WRAPPER}} .kng-rsc-card__title' => 'margin-bottom: {{SIZE}}{{UNIT}};',
812 ],
813 ]
814 );
815
816 // Description
817 $this->add_control(
818 'kng_rsc_desc_heading',
819 [
820 'label' => esc_html__('Description', 'king-addons'),
821 'type' => Controls_Manager::HEADING,
822 'separator' => 'before',
823 ]
824 );
825
826 $this->add_group_control(
827 Group_Control_Typography::get_type(),
828 [
829 'name' => 'kng_rsc_desc_typography',
830 'selector' => '{{WRAPPER}} .kng-rsc-card__description',
831 ]
832 );
833
834 $this->add_control(
835 'kng_rsc_desc_color',
836 [
837 'label' => esc_html__('Description Color', 'king-addons'),
838 'type' => Controls_Manager::COLOR,
839 'selectors' => [
840 '{{WRAPPER}} .kng-rsc-card__description' => 'color: {{VALUE}};',
841 ],
842 ]
843 );
844
845 $this->add_responsive_control(
846 'kng_rsc_desc_spacing',
847 [
848 'label' => esc_html__('Description Spacing', 'king-addons'),
849 'type' => Controls_Manager::SLIDER,
850 'size_units' => ['px'],
851 'range' => [
852 'px' => ['min' => 0, 'max' => 40],
853 ],
854 'selectors' => [
855 '{{WRAPPER}} .kng-rsc-card__description' => 'margin-bottom: {{SIZE}}{{UNIT}};',
856 ],
857 ]
858 );
859
860 $this->end_controls_section();
861 }
862
863 /**
864 * Register button style controls.
865 *
866 * @return void
867 */
868 protected function register_style_button_controls(): void
869 {
870 $this->start_controls_section(
871 'kng_rsc_style_button_section',
872 [
873 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Button', 'king-addons'),
874 'tab' => Controls_Manager::TAB_STYLE,
875 ]
876 );
877
878 $this->add_group_control(
879 Group_Control_Typography::get_type(),
880 [
881 'name' => 'kng_rsc_btn_typography',
882 'selector' => '{{WRAPPER}} .kng-rsc-card__button',
883 ]
884 );
885
886 $this->start_controls_tabs('kng_rsc_btn_tabs');
887
888 $this->start_controls_tab(
889 'kng_rsc_btn_tab_normal',
890 [
891 'label' => esc_html__('Normal', 'king-addons'),
892 ]
893 );
894
895 $this->add_control(
896 'kng_rsc_btn_color',
897 [
898 'label' => esc_html__('Text Color', 'king-addons'),
899 'type' => Controls_Manager::COLOR,
900 'selectors' => [
901 '{{WRAPPER}} .kng-rsc-card__button' => 'color: {{VALUE}};',
902 ],
903 ]
904 );
905
906 $this->add_control(
907 'kng_rsc_btn_bg',
908 [
909 'label' => esc_html__('Background', 'king-addons'),
910 'type' => Controls_Manager::COLOR,
911 'selectors' => [
912 '{{WRAPPER}} .kng-rsc-card__button' => 'background-color: {{VALUE}};',
913 ],
914 ]
915 );
916
917 $this->add_group_control(
918 Group_Control_Border::get_type(),
919 [
920 'name' => 'kng_rsc_btn_border',
921 'selector' => '{{WRAPPER}} .kng-rsc-card__button',
922 ]
923 );
924
925 $this->end_controls_tab();
926
927 $this->start_controls_tab(
928 'kng_rsc_btn_tab_hover',
929 [
930 'label' => esc_html__('Hover', 'king-addons'),
931 ]
932 );
933
934 $this->add_control(
935 'kng_rsc_btn_hover_color',
936 [
937 'label' => esc_html__('Text Color', 'king-addons'),
938 'type' => Controls_Manager::COLOR,
939 'selectors' => [
940 '{{WRAPPER}} .kng-rsc-card__button:hover' => 'color: {{VALUE}};',
941 ],
942 ]
943 );
944
945 $this->add_control(
946 'kng_rsc_btn_hover_bg',
947 [
948 'label' => esc_html__('Background', 'king-addons'),
949 'type' => Controls_Manager::COLOR,
950 'selectors' => [
951 '{{WRAPPER}} .kng-rsc-card__button:hover' => 'background-color: {{VALUE}};',
952 ],
953 ]
954 );
955
956 $this->add_control(
957 'kng_rsc_btn_hover_border_color',
958 [
959 'label' => esc_html__('Border Color', 'king-addons'),
960 'type' => Controls_Manager::COLOR,
961 'selectors' => [
962 '{{WRAPPER}} .kng-rsc-card__button:hover' => 'border-color: {{VALUE}};',
963 ],
964 ]
965 );
966
967 $this->end_controls_tab();
968
969 $this->end_controls_tabs();
970
971 $this->add_responsive_control(
972 'kng_rsc_btn_radius',
973 [
974 'label' => esc_html__('Border Radius', 'king-addons'),
975 'type' => Controls_Manager::DIMENSIONS,
976 'size_units' => ['px', '%'],
977 'separator' => 'before',
978 'selectors' => [
979 '{{WRAPPER}} .kng-rsc-card__button' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
980 ],
981 ]
982 );
983
984 $this->add_responsive_control(
985 'kng_rsc_btn_padding',
986 [
987 'label' => esc_html__('Padding', 'king-addons'),
988 'type' => Controls_Manager::DIMENSIONS,
989 'size_units' => ['px', 'em'],
990 'selectors' => [
991 '{{WRAPPER}} .kng-rsc-card__button' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
992 ],
993 ]
994 );
995
996 $this->end_controls_section();
997 }
998
999 /**
1000 * Register badge style controls.
1001 *
1002 * @return void
1003 */
1004 protected function register_style_badge_controls(): void
1005 {
1006 $this->start_controls_section(
1007 'kng_rsc_style_badge_section',
1008 [
1009 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Badge', 'king-addons'),
1010 'tab' => Controls_Manager::TAB_STYLE,
1011 ]
1012 );
1013
1014 $this->add_group_control(
1015 Group_Control_Typography::get_type(),
1016 [
1017 'name' => 'kng_rsc_badge_typography',
1018 'selector' => '{{WRAPPER}} .kng-rsc-card__badge',
1019 ]
1020 );
1021
1022 $this->add_control(
1023 'kng_rsc_badge_color',
1024 [
1025 'label' => esc_html__('Text Color', 'king-addons'),
1026 'type' => Controls_Manager::COLOR,
1027 'selectors' => [
1028 '{{WRAPPER}} .kng-rsc-card__badge' => 'color: {{VALUE}};',
1029 ],
1030 ]
1031 );
1032
1033 $this->add_control(
1034 'kng_rsc_badge_bg',
1035 [
1036 'label' => esc_html__('Background', 'king-addons'),
1037 'type' => Controls_Manager::COLOR,
1038 'selectors' => [
1039 '{{WRAPPER}} .kng-rsc-card__badge' => 'background-color: {{VALUE}};',
1040 ],
1041 ]
1042 );
1043
1044 $this->add_responsive_control(
1045 'kng_rsc_badge_radius',
1046 [
1047 'label' => esc_html__('Border Radius', 'king-addons'),
1048 'type' => Controls_Manager::DIMENSIONS,
1049 'size_units' => ['px', '%'],
1050 'selectors' => [
1051 '{{WRAPPER}} .kng-rsc-card__badge' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1052 ],
1053 ]
1054 );
1055
1056 $this->add_responsive_control(
1057 'kng_rsc_badge_padding',
1058 [
1059 'label' => esc_html__('Padding', 'king-addons'),
1060 'type' => Controls_Manager::DIMENSIONS,
1061 'size_units' => ['px', 'em'],
1062 'selectors' => [
1063 '{{WRAPPER}} .kng-rsc-card__badge' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1064 ],
1065 ]
1066 );
1067
1068 $this->add_control(
1069 'kng_rsc_badge_position',
1070 [
1071 'label' => esc_html__('Position', 'king-addons'),
1072 'type' => Controls_Manager::SELECT,
1073 'default' => 'top-right',
1074 'options' => [
1075 'top-left' => esc_html__('Top Left', 'king-addons'),
1076 'top-right' => esc_html__('Top Right', 'king-addons'),
1077 'bottom-left' => esc_html__('Bottom Left', 'king-addons'),
1078 'bottom-right' => esc_html__('Bottom Right', 'king-addons'),
1079 ],
1080 ]
1081 );
1082
1083 $this->end_controls_section();
1084 }
1085
1086 /**
1087 * Register overlay style controls.
1088 *
1089 * @return void
1090 */
1091 protected function register_style_overlay_controls(): void
1092 {
1093 $this->start_controls_section(
1094 'kng_rsc_style_overlay_section',
1095 [
1096 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Reveal Overlay', 'king-addons'),
1097 'tab' => Controls_Manager::TAB_STYLE,
1098 ]
1099 );
1100
1101 $this->add_control(
1102 'kng_rsc_overlay_color',
1103 [
1104 'label' => esc_html__('Overlay Color', 'king-addons'),
1105 'type' => Controls_Manager::COLOR,
1106 'default' => '#2563eb',
1107 'selectors' => [
1108 '{{WRAPPER}} .kng-rsc' => '--kng-rsc-overlay-color: {{VALUE}};',
1109 ],
1110 ]
1111 );
1112
1113 $this->add_control(
1114 'kng_rsc_overlay_opacity',
1115 [
1116 'label' => esc_html__('Overlay Opacity', 'king-addons'),
1117 'type' => Controls_Manager::SLIDER,
1118 'range' => [
1119 'px' => ['min' => 0, 'max' => 1, 'step' => 0.05],
1120 ],
1121 'default' => [
1122 'size' => 1,
1123 ],
1124 'selectors' => [
1125 '{{WRAPPER}} .kng-rsc' => '--kng-rsc-overlay-opacity: {{SIZE}};',
1126 ],
1127 ]
1128 );
1129
1130 $this->add_control(
1131 'kng_rsc_overlay_content_heading',
1132 [
1133 'label' => esc_html__('Overlay Content', 'king-addons'),
1134 'type' => Controls_Manager::HEADING,
1135 'separator' => 'before',
1136 ]
1137 );
1138
1139 $this->add_control(
1140 'kng_rsc_overlay_content_type',
1141 [
1142 'label' => esc_html__('Content Type', 'king-addons'),
1143 'type' => Controls_Manager::SELECT,
1144 'default' => 'none',
1145 'options' => [
1146 'none' => esc_html__('None', 'king-addons'),
1147 'icon' => esc_html__('Icon', 'king-addons'),
1148 'text' => esc_html__('Text', 'king-addons'),
1149 ],
1150 ]
1151 );
1152
1153 $this->add_control(
1154 'kng_rsc_overlay_icon',
1155 [
1156 'label' => esc_html__('Overlay Icon', 'king-addons'),
1157 'type' => Controls_Manager::ICONS,
1158 'default' => [
1159 'value' => 'fas fa-eye',
1160 'library' => 'fa-solid',
1161 ],
1162 'condition' => [
1163 'kng_rsc_overlay_content_type' => 'icon',
1164 ],
1165 ]
1166 );
1167
1168 $this->add_control(
1169 'kng_rsc_overlay_text',
1170 [
1171 'label' => esc_html__('Overlay Text', 'king-addons'),
1172 'type' => Controls_Manager::TEXT,
1173 'default' => esc_html__('Hover to reveal', 'king-addons'),
1174 'condition' => [
1175 'kng_rsc_overlay_content_type' => 'text',
1176 ],
1177 ]
1178 );
1179
1180 $this->add_control(
1181 'kng_rsc_overlay_content_color',
1182 [
1183 'label' => esc_html__('Content Color', 'king-addons'),
1184 'type' => Controls_Manager::COLOR,
1185 'default' => '#ffffff',
1186 'selectors' => [
1187 '{{WRAPPER}} .kng-rsc-card__overlay-content' => 'color: {{VALUE}};',
1188 '{{WRAPPER}} .kng-rsc-card__overlay-content svg' => 'fill: {{VALUE}};',
1189 ],
1190 'condition' => [
1191 'kng_rsc_overlay_content_type!' => 'none',
1192 ],
1193 ]
1194 );
1195
1196 $this->add_responsive_control(
1197 'kng_rsc_overlay_content_size',
1198 [
1199 'label' => esc_html__('Content Size', 'king-addons'),
1200 'type' => Controls_Manager::SLIDER,
1201 'size_units' => ['px'],
1202 'range' => [
1203 'px' => ['min' => 12, 'max' => 100],
1204 ],
1205 'default' => [
1206 'size' => 32,
1207 'unit' => 'px',
1208 ],
1209 'selectors' => [
1210 '{{WRAPPER}} .kng-rsc-card__overlay-content' => 'font-size: {{SIZE}}{{UNIT}};',
1211 '{{WRAPPER}} .kng-rsc-card__overlay-content svg' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}};',
1212 ],
1213 'condition' => [
1214 'kng_rsc_overlay_content_type!' => 'none',
1215 ],
1216 ]
1217 );
1218
1219 $this->end_controls_section();
1220 }
1221
1222 /**
1223 * Register Pro features notice (Free version).
1224 *
1225 * @return void
1226 */
1227 protected function register_pro_notice_controls(): void
1228 {
1229 if (!king_addons_freemius()->can_use_premium_code__premium_only()) {
1230 Core::renderProFeaturesSection($this, '', Controls_Manager::RAW_HTML, 'reveal-swipe-cards', [
1231 'Custom mask shapes (circle, diagonal, wave)',
1232 'Blur edge effect for soft reveal',
1233 'Touch swipe on mobile devices',
1234 'Sequence mode (stagger reveal)',
1235 'Active one at a time mode',
1236 ]);
1237 }
1238 }
1239
1240 /**
1241 * Get default cards data.
1242 *
1243 * @return array Default cards.
1244 */
1245 protected function get_default_cards(): array
1246 {
1247 return [
1248 [
1249 'media_type' => 'icon',
1250 'icon' => ['value' => 'fas fa-rocket', 'library' => 'fa-solid'],
1251 'title' => esc_html__('Fast Performance', 'king-addons'),
1252 'description' => esc_html__('Optimized for speed and efficiency to deliver the best experience.', 'king-addons'),
1253 'button_text' => esc_html__('Learn More', 'king-addons'),
1254 ],
1255 [
1256 'media_type' => 'icon',
1257 'icon' => ['value' => 'fas fa-shield-alt', 'library' => 'fa-solid'],
1258 'title' => esc_html__('Secure & Reliable', 'king-addons'),
1259 'description' => esc_html__('Built with security in mind to protect your data and privacy.', 'king-addons'),
1260 'button_text' => esc_html__('Discover', 'king-addons'),
1261 ],
1262 [
1263 'media_type' => 'icon',
1264 'icon' => ['value' => 'fas fa-paint-brush', 'library' => 'fa-solid'],
1265 'title' => esc_html__('Beautiful Design', 'king-addons'),
1266 'description' => esc_html__('Modern and clean aesthetics that captivate your audience.', 'king-addons'),
1267 'button_text' => esc_html__('Explore', 'king-addons'),
1268 ],
1269 ];
1270 }
1271
1272 /**
1273 * Render widget output.
1274 *
1275 * @return void
1276 */
1277 protected function render(): void
1278 {
1279 $settings = $this->get_settings_for_display();
1280 $cards = $settings['kng_rsc_cards'] ?? [];
1281
1282 if (empty($cards)) {
1283 if ($this->is_editor()) {
1284 echo '<div class="kng-rsc-empty">' . esc_html__('Add cards to get started.', 'king-addons') . '</div>';
1285 }
1286 return;
1287 }
1288
1289 $wrapper_classes = $this->get_wrapper_classes($settings);
1290 $data_settings = $this->get_data_settings($settings);
1291
1292 $this->add_render_attribute('wrapper', [
1293 'class' => implode(' ', $wrapper_classes),
1294 'data-settings' => wp_json_encode($data_settings),
1295 ]);
1296
1297 echo '<div ' . $this->get_render_attribute_string('wrapper') . '>';
1298 echo '<div class="kng-rsc__grid">';
1299
1300 foreach ($cards as $index => $card) {
1301 $this->render_card($settings, $card, $index);
1302 }
1303
1304 echo '</div>';
1305 echo '</div>';
1306 }
1307
1308 /**
1309 * Render single card.
1310 *
1311 * @param array $settings Widget settings.
1312 * @param array $card Card data.
1313 * @param int $index Card index.
1314 * @return void
1315 */
1316 protected function render_card(array $settings, array $card, int $index): void
1317 {
1318 $title = esc_html(trim((string) ($card['title'] ?? '')));
1319 $description = wp_kses_post(trim((string) ($card['description'] ?? '')));
1320 $button_text = esc_html(trim((string) ($card['button_text'] ?? '')));
1321 $button_link = $card['button_link'] ?? [];
1322 $media_type = $card['media_type'] ?? 'none';
1323 $badge_enabled = 'yes' === ($card['badge_enabled'] ?? '');
1324 $badge_text = esc_html(trim((string) ($card['badge_text'] ?? '')));
1325 $badge_position = $settings['kng_rsc_badge_position'] ?? 'top-right';
1326
1327 $card_classes = ['kng-rsc-card'];
1328
1329 echo '<div class="' . esc_attr(implode(' ', $card_classes)) . '" data-card-index="' . esc_attr((string) $index) . '">';
1330
1331 // Reveal overlay (mask layer)
1332 echo '<div class="kng-rsc-card__overlay" aria-hidden="true">';
1333 $this->render_overlay_content($settings);
1334 echo '</div>';
1335
1336 // Content layer
1337 echo '<div class="kng-rsc-card__content">';
1338 echo '<div class="kng-rsc-card__inner">';
1339
1340 // Badge
1341 if ($badge_enabled && '' !== $badge_text) {
1342 echo '<span class="kng-rsc-card__badge kng-rsc-card__badge--' . esc_attr($badge_position) . '">' . $badge_text . '</span>';
1343 }
1344
1345 // Media
1346 if ('none' !== $media_type) {
1347 echo '<div class="kng-rsc-card__media">';
1348
1349 if ('image' === $media_type && !empty($card['image']['url'])) {
1350 echo '<img class="kng-rsc-card__image" src="' . esc_url($card['image']['url']) . '" alt="' . esc_attr($title) . '" />';
1351 } elseif ('icon' === $media_type && !empty($card['icon']['value'])) {
1352 echo '<span class="kng-rsc-card__icon">';
1353 Icons_Manager::render_icon($card['icon'], ['aria-hidden' => 'true']);
1354 echo '</span>';
1355 }
1356
1357 echo '</div>';
1358 }
1359
1360 // Title
1361 if ('' !== $title) {
1362 echo '<h3 class="kng-rsc-card__title">' . $title . '</h3>';
1363 }
1364
1365 // Description
1366 if ('' !== $description) {
1367 echo '<p class="kng-rsc-card__description">' . $description . '</p>';
1368 }
1369
1370 // Button
1371 if ('' !== $button_text) {
1372 $link_attrs = $this->get_link_attributes($button_link, 'kng-rsc-card__button');
1373 if ('' !== $link_attrs) {
1374 echo '<a ' . $link_attrs . '>' . $button_text . '</a>';
1375 } else {
1376 echo '<span class="kng-rsc-card__button">' . $button_text . '</span>';
1377 }
1378 }
1379
1380 echo '</div>';
1381 echo '</div>';
1382 echo '</div>';
1383 }
1384
1385 /**
1386 * Get wrapper CSS classes.
1387 *
1388 * @param array $settings Widget settings.
1389 * @return array CSS classes.
1390 */
1391 protected function get_wrapper_classes(array $settings): array
1392 {
1393 $direction = $this->sanitize_direction($settings['kng_rsc_direction'] ?? 'left');
1394 $trigger = $this->sanitize_trigger($settings['kng_rsc_trigger'] ?? 'hover');
1395 $hover_transform = $settings['kng_rsc_card_hover_transform'] ?? 'none';
1396
1397 $classes = [
1398 'kng-rsc',
1399 'kng-rsc--direction-' . $direction,
1400 'kng-rsc--trigger-' . $trigger,
1401 ];
1402
1403 if ('none' !== $hover_transform) {
1404 $classes[] = 'kng-rsc--hover-' . $hover_transform;
1405 }
1406
1407 if ($this->is_pro_enabled()) {
1408 $classes[] = 'kng-rsc--pro';
1409 }
1410
1411 return $classes;
1412 }
1413
1414 /**
1415 * Get data settings for JS.
1416 *
1417 * @param array $settings Widget settings.
1418 * @return array Data settings.
1419 */
1420 protected function get_data_settings(array $settings): array
1421 {
1422 $data = [
1423 'trigger' => $this->sanitize_trigger($settings['kng_rsc_trigger'] ?? 'hover'),
1424 'direction' => $this->sanitize_direction($settings['kng_rsc_direction'] ?? 'left'),
1425 'duration' => absint($settings['kng_rsc_duration']['size'] ?? 500),
1426 'resetOnLeave' => 'yes' === ($settings['kng_rsc_reset_on_leave'] ?? 'yes'),
1427 'scrollThreshold' => absint($settings['kng_rsc_scroll_threshold']['size'] ?? 30) / 100,
1428 'scrollOnce' => 'yes' === ($settings['kng_rsc_scroll_once'] ?? 'yes'),
1429 'scrollReset' => 'yes' === ($settings['kng_rsc_scroll_reset'] ?? ''),
1430 ];
1431
1432 return $data;
1433 }
1434
1435 /**
1436 * Get link attributes string.
1437 *
1438 * @param array $link Link data.
1439 * @param string $class CSS class.
1440 * @return string Attributes string.
1441 */
1442 protected function get_link_attributes(array $link, string $class = ''): string
1443 {
1444 if (empty($link['url'])) {
1445 return '';
1446 }
1447
1448 $attributes = [
1449 'href' => esc_url($link['url']),
1450 ];
1451
1452 if ('' !== $class) {
1453 $attributes['class'] = $class;
1454 }
1455
1456 if (!empty($link['is_external'])) {
1457 $attributes['target'] = '_blank';
1458 $attributes['rel'] = 'noopener noreferrer';
1459 }
1460
1461 if (!empty($link['nofollow'])) {
1462 $attributes['rel'] = isset($attributes['rel']) ? $attributes['rel'] . ' nofollow' : 'nofollow';
1463 }
1464
1465 $output = [];
1466 foreach ($attributes as $key => $value) {
1467 $output[] = esc_attr($key) . '="' . esc_attr((string) $value) . '"';
1468 }
1469
1470 return implode(' ', $output);
1471 }
1472
1473 /**
1474 * Sanitize direction value.
1475 *
1476 * @param string $direction Direction value.
1477 * @return string Sanitized direction.
1478 */
1479 protected function sanitize_direction(string $direction): string
1480 {
1481 $allowed = ['left', 'right', 'top', 'bottom'];
1482 return in_array($direction, $allowed, true) ? $direction : 'left';
1483 }
1484
1485 /**
1486 * Sanitize trigger value.
1487 *
1488 * @param string $trigger Trigger value.
1489 * @return string Sanitized trigger.
1490 */
1491 protected function sanitize_trigger(string $trigger): string
1492 {
1493 $allowed = ['hover', 'scroll', 'both'];
1494 return in_array($trigger, $allowed, true) ? $trigger : 'hover';
1495 }
1496
1497 /**
1498 * Check if Pro features are enabled.
1499 *
1500 * @return bool True if Pro enabled.
1501 */
1502 protected function is_pro_enabled(): bool
1503 {
1504 return function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code__premium_only();
1505 }
1506
1507 /**
1508 * Check if in editor mode.
1509 *
1510 * @return bool True if editor.
1511 */
1512 protected function is_editor(): bool
1513 {
1514 return class_exists(Plugin::class) && Plugin::$instance->editor->is_edit_mode();
1515 }
1516
1517 /**
1518 * Render overlay content (icon or text).
1519 *
1520 * @param array $settings Widget settings.
1521 * @return void
1522 */
1523 protected function render_overlay_content(array $settings): void
1524 {
1525 $content_type = $settings['kng_rsc_overlay_content_type'] ?? 'none';
1526
1527 if ('none' === $content_type) {
1528 return;
1529 }
1530
1531 echo '<span class="kng-rsc-card__overlay-content">';
1532
1533 if ('icon' === $content_type && !empty($settings['kng_rsc_overlay_icon']['value'])) {
1534 Icons_Manager::render_icon($settings['kng_rsc_overlay_icon'], ['aria-hidden' => 'true']);
1535 } elseif ('text' === $content_type && !empty($settings['kng_rsc_overlay_text'])) {
1536 echo esc_html($settings['kng_rsc_overlay_text']);
1537 }
1538
1539 echo '</span>';
1540 }
1541 }
1542