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 / Scrollytelling_Slides / Scrollytelling_Slides.php

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

2,370 lines 79.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Scrollytelling Slides Widget (Free).
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons;
9
10 use Elementor\Controls_Manager;
11 use Elementor\Embed;
12 use Elementor\Group_Control_Background;
13 use Elementor\Group_Control_Border;
14 use Elementor\Group_Control_Box_Shadow;
15 use Elementor\Group_Control_Image_Size;
16 use Elementor\Group_Control_Typography;
17 use Elementor\Plugin;
18 use Elementor\Repeater;
19 use Elementor\Utils;
20 use Elementor\Widget_Base;
21
22 if (!defined('ABSPATH')) {
23 exit;
24 }
25
26 /**
27 * Scrollytelling slides widget.
28 */
29 class Scrollytelling_Slides extends Widget_Base
30 {
31 /**
32 * Widget slug.
33 *
34 * @return string
35 */
36 public function get_name(): string
37 {
38 return 'king-addons-scrollytelling-slides';
39 }
40
41 /**
42 * Widget title.
43 *
44 * @return string
45 */
46 public function get_title(): string
47 {
48 return esc_html__('Scrollytelling Slides', 'king-addons');
49 }
50
51 /**
52 * Widget icon.
53 *
54 * @return string
55 */
56 public function get_icon(): string
57 {
58 return 'king-addons-icon king-addons-scrollytelling-slides';
59 }
60
61 /**
62 * Style dependencies.
63 *
64 * @return array<int, string>
65 */
66 public function get_style_depends(): array
67 {
68 return [
69 KING_ADDONS_ASSETS_UNIQUE_KEY . '-scrollytelling-slides-style',
70 ];
71 }
72
73 /**
74 * Script dependencies.
75 *
76 * @return array<int, string>
77 */
78 public function get_script_depends(): array
79 {
80 $deps = [
81 KING_ADDONS_ASSETS_UNIQUE_KEY . '-scrollytelling-slides-script',
82 ];
83
84 if (king_addons_can_use_pro()) {
85 $deps[] = KING_ADDONS_ASSETS_UNIQUE_KEY . '-lottie-lottie';
86 }
87
88 return $deps;
89 }
90
91 /**
92 * Categories.
93 *
94 * @return array<int, string>
95 */
96 public function get_categories(): array
97 {
98 return ['king-addons'];
99 }
100
101 /**
102 * Keywords.
103 *
104 * @return array<int, string>
105 */
106 public function get_keywords(): array
107 {
108 return ['scrollytelling', 'slides', 'story', 'scroll', 'progress'];
109 }
110
111 /**
112 * Register controls.
113 *
114 * @return void
115 */
116 public function register_controls(): void
117 {
118 $this->register_content_controls();
119 $this->register_layout_controls();
120 $this->register_behavior_controls();
121 $this->register_style_controls();
122 $this->register_pro_notice_controls();
123 }
124
125 /**
126 * Render widget output.
127 *
128 * @return void
129 */
130 public function render(): void
131 {
132 $settings = $this->get_settings_for_display();
133 $slides = $settings['kng_slides'] ?? [];
134
135 if (!is_array($slides) || empty($slides)) {
136 echo '<div class="king-addons-scrollytelling__notice">' . esc_html__('Please add at least one slide.', 'king-addons') . '</div>';
137 return;
138 }
139
140 $can_pro = king_addons_can_use_pro();
141 $max_slides = $can_pro ? 20 : 5;
142 $slides = array_values(array_slice($slides, 0, $max_slides));
143
144 $is_editor = class_exists(Plugin::class) && Plugin::instance()->editor->is_edit_mode();
145 if (!$can_pro && count($slides) < 3) {
146 if ($is_editor) {
147 echo '<div class="king-addons-scrollytelling__notice">' . esc_html__('Add at least 3 slides for the Free version.', 'king-addons') . '</div>';
148 }
149 return;
150 }
151
152 $progress_style = $settings['kng_progress_style'] ?? 'dots';
153 $markers_enabled = ($settings['kng_progress_markers'] ?? '') === 'yes';
154 $label_mode = $can_pro ? ($settings['kng_progress_label_mode'] ?? '') : '';
155 $show_numbers = ($settings['kng_progress_numbers'] ?? '') === 'yes';
156 $show_labels = false;
157 $reverse_columns = ($settings['kng_reverse_columns'] ?? '') === 'yes';
158 $sections_enabled = ($settings['kng_sections_enable'] ?? '') === 'yes';
159 $section_separators = $can_pro && $sections_enabled && (($settings['kng_section_separators'] ?? '') === 'yes');
160 $mobile_media_position = $settings['kng_mobile_media_position'] ?? 'above';
161 $sticky_media = $can_pro && (($settings['kng_media_sticky'] ?? '') === 'yes');
162 $transition_type = $this->sanitize_transition_type($settings['kng_transition_type'] ?? 'fade');
163 $transition_duration = 420;
164 if (!empty($settings['kng_transition_duration']['size'])) {
165 $transition_duration = (int) $settings['kng_transition_duration']['size'];
166 }
167 $transition_duration = min(max($transition_duration, 150), 1200);
168 $transition_easing = $this->sanitize_transition_easing($settings['kng_transition_easing'] ?? 'ease');
169
170 $snap_mode = $can_pro ? $this->sanitize_snap_mode($settings['kng_snap_mode'] ?? 'off') : 'off';
171 $snap_duration = 420;
172 if (!empty($settings['kng_snap_duration']['size'])) {
173 $snap_duration = (int) $settings['kng_snap_duration']['size'];
174 }
175 $snap_duration = min(max($snap_duration, 150), 2000);
176 $lottie_active_only = $can_pro && (($settings['kng_lottie_active_only'] ?? 'yes') === 'yes');
177
178 $ratio_custom = '';
179 if (($settings['kng_media_ratio'] ?? '') === 'custom') {
180 $ratio_custom = $this->sanitize_ratio($settings['kng_media_ratio_custom'] ?? '');
181 }
182
183 $wrapper_classes = [
184 'king-addons-scrollytelling',
185 'king-addons-scrollytelling--progress-' . sanitize_html_class($progress_style),
186 ];
187
188 if ($reverse_columns) {
189 $wrapper_classes[] = 'king-addons-scrollytelling--reverse';
190 }
191
192 if ($can_pro && $label_mode !== '') {
193 $show_numbers = ('numbers' === $label_mode);
194 $show_labels = ('labels' === $label_mode);
195 }
196
197 if ($show_numbers) {
198 $wrapper_classes[] = 'king-addons-scrollytelling--numbers';
199 }
200
201 if ($show_labels) {
202 $wrapper_classes[] = 'king-addons-scrollytelling--labels';
203 }
204
205 if ('line' === $progress_style && !$markers_enabled) {
206 $wrapper_classes[] = 'king-addons-scrollytelling--markers-hidden';
207 }
208
209 if ('below' === $mobile_media_position) {
210 $wrapper_classes[] = 'king-addons-scrollytelling--mobile-media-below';
211 }
212
213 if ($sticky_media) {
214 $wrapper_classes[] = 'king-addons-scrollytelling--sticky';
215 }
216
217 if ($section_separators) {
218 $wrapper_classes[] = 'king-addons-scrollytelling--section-separators';
219 }
220
221 if ($can_pro && !empty($transition_type)) {
222 $wrapper_classes[] = 'king-addons-scrollytelling--transition-' . $transition_type;
223 }
224
225 $activation_offset = 40;
226 if (!empty($settings['kng_activation_offset']['size'])) {
227 $activation_offset = (int) $settings['kng_activation_offset']['size'];
228 }
229
230 $markers_visible = !('line' === $progress_style && !$markers_enabled);
231
232 $options = [
233 'offset' => $activation_offset,
234 'clickableDots' => $can_pro && (($settings['kng_dots_clickable'] ?? '') === 'yes') && $markers_visible,
235 'updateHash' => $can_pro && (($settings['kng_deep_link'] ?? '') === 'yes'),
236 'readHash' => $can_pro && (($settings['kng_deep_link'] ?? '') === 'yes'),
237 'sticky' => $sticky_media,
238 'snap' => $snap_mode,
239 'snapDuration' => $snap_duration,
240 'lottieActiveOnly' => $lottie_active_only,
241 ];
242
243 $this->add_render_attribute('wrapper', [
244 'class' => $wrapper_classes,
245 'data-options' => wp_json_encode($options),
246 ]);
247
248 if (!empty($ratio_custom)) {
249 $this->add_render_attribute('wrapper', 'style', '--kng-sly-media-ratio: ' . $ratio_custom . ';');
250 }
251
252 if ($sticky_media) {
253 $sticky_offset = isset($settings['kng_media_sticky_offset']) ? (int) $settings['kng_media_sticky_offset'] : 0;
254 $this->add_render_attribute('wrapper', 'style', '--kng-sly-sticky-offset: ' . $sticky_offset . 'px;');
255 }
256
257 if ($can_pro) {
258 $this->add_render_attribute(
259 'wrapper',
260 'style',
261 '--kng-sly-transition-duration: ' . $transition_duration . 'ms; --kng-sly-transition-ease: ' . $transition_easing . ';'
262 );
263 }
264
265 $clickable_dots = !empty($options['clickableDots']);
266
267 echo '<div ' . $this->get_render_attribute_string('wrapper') . '>';
268 echo '<div class="king-addons-scrollytelling__inner">';
269
270 echo '<div class="king-addons-scrollytelling__slides">';
271
272 foreach ($slides as $index => $slide) {
273 if ($sections_enabled && ($slide['kng_slide_new_section'] ?? '') === 'yes') {
274 $section_heading = trim((string) ($slide['kng_slide_section_heading'] ?? ''));
275 if (!empty($section_heading)) {
276 echo '<div class="king-addons-scrollytelling__section">' . esc_html($section_heading) . '</div>';
277 }
278 }
279
280 $slide_classes = ['king-addons-scrollytelling__slide'];
281 if (0 === $index) {
282 $slide_classes[] = 'is-active';
283 }
284
285 $slide_key = 'slide_' . $index;
286 $this->add_render_attribute($slide_key, [
287 'class' => $slide_classes,
288 'data-index' => (string) $index,
289 ]);
290
291 $anchor = $this->sanitize_anchor($slide['kng_slide_anchor'] ?? '');
292 if (!empty($anchor)) {
293 $this->add_render_attribute($slide_key, 'data-anchor', $anchor);
294 }
295
296 echo '<article ' . $this->get_render_attribute_string($slide_key) . '>';
297 echo '<div class="king-addons-scrollytelling__progress-cell">';
298
299 $dot_label = $this->get_dot_label($slide, $index);
300 $dot_display = $this->get_dot_display_label($slide, $index);
301 $dot_attrs = [
302 'type' => 'button',
303 'class' => 'king-addons-scrollytelling__dot',
304 'data-index' => (string) $index,
305 'aria-label' => $dot_label,
306 ];
307
308 if ($index === 0) {
309 $dot_attrs['aria-current'] = 'step';
310 }
311
312 if (!$clickable_dots) {
313 $dot_attrs['disabled'] = 'disabled';
314 $dot_attrs['tabindex'] = '-1';
315 }
316
317 echo '<button ' . $this->compile_attributes($dot_attrs) . '>';
318 echo '<span class="king-addons-scrollytelling__dot-core">';
319 echo '<span class="king-addons-scrollytelling__dot-number" aria-hidden="true">' . esc_html((string) ($index + 1)) . '</span>';
320 echo '</span>';
321 echo '<span class="king-addons-scrollytelling__dot-label">' . esc_html($dot_display) . '</span>';
322 echo '</button>';
323 echo '</div>';
324
325 echo '<div class="king-addons-scrollytelling__text">';
326 echo '<div class="king-addons-scrollytelling__card">';
327
328 $title = $slide['kng_slide_title'] ?? '';
329 $subtitle = $slide['kng_slide_subtitle'] ?? '';
330 $description = $slide['kng_slide_description'] ?? '';
331 $bullets = $this->parse_bullets($slide['kng_slide_bullets'] ?? '');
332
333 if (!empty($title)) {
334 echo '<h3 class="king-addons-scrollytelling__title">' . esc_html($title) . '</h3>';
335 }
336
337 if (!empty($subtitle)) {
338 echo '<p class="king-addons-scrollytelling__subtitle">' . esc_html($subtitle) . '</p>';
339 }
340
341 if (!empty($description)) {
342 echo '<div class="king-addons-scrollytelling__description">' . wp_kses_post($description) . '</div>';
343 }
344
345 if (!empty($bullets)) {
346 echo '<ul class="king-addons-scrollytelling__bullets">';
347 foreach ($bullets as $bullet) {
348 echo '<li>' . esc_html($bullet) . '</li>';
349 }
350 echo '</ul>';
351 }
352
353 $button_text = $slide['kng_slide_button_text'] ?? '';
354 $button_link = $slide['kng_slide_button_link'] ?? [];
355 $button_attrs = $this->get_link_attributes($button_link);
356 if (!empty($button_text) && !empty($button_attrs['href'])) {
357 $button_attrs['class'] = 'king-addons-scrollytelling__button';
358 echo '<a ' . $this->compile_attributes($button_attrs) . '>' . esc_html($button_text) . '</a>';
359 }
360
361 echo '</div>';
362 echo '</div>';
363
364 echo '<div class="king-addons-scrollytelling__media">';
365 echo $this->render_media($slide, $settings, $can_pro); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
366 echo '</div>';
367
368 echo '</article>';
369 }
370
371 echo '</div>';
372
373 if ($sticky_media) {
374 echo '<div class="king-addons-scrollytelling__media-panel">';
375 echo '<div class="king-addons-scrollytelling__media-sticky" data-index="0">';
376 echo $this->render_media($slides[0], $settings, $can_pro); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
377 echo '</div>';
378 echo '</div>';
379 }
380
381 echo '</div>';
382 echo '</div>';
383 }
384
385 /**
386 * Content controls.
387 *
388 * @return void
389 */
390 protected function register_content_controls(): void
391 {
392 $this->start_controls_section(
393 'kng_content_section',
394 [
395 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Slides', 'king-addons'),
396 'tab' => Controls_Manager::TAB_CONTENT,
397 ]
398 );
399
400 $repeater = new Repeater();
401
402 $repeater->add_control(
403 'kng_slide_label',
404 [
405 'label' => esc_html__('Slide Label', 'king-addons'),
406 'type' => Controls_Manager::TEXT,
407 'dynamic' => [
408 'active' => true,
409 ],
410 ]
411 );
412
413 $repeater->add_control(
414 'kng_slide_title',
415 [
416 'label' => esc_html__('Title', 'king-addons'),
417 'type' => Controls_Manager::TEXT,
418 'dynamic' => [
419 'active' => true,
420 ],
421 'default' => esc_html__('Slide Title', 'king-addons'),
422 'label_block' => true,
423 ]
424 );
425
426 $repeater->add_control(
427 'kng_slide_subtitle',
428 [
429 'label' => esc_html__('Subtitle', 'king-addons'),
430 'type' => Controls_Manager::TEXT,
431 'dynamic' => [
432 'active' => true,
433 ],
434 'label_block' => true,
435 ]
436 );
437
438 $repeater->add_control(
439 'kng_slide_description',
440 [
441 'label' => esc_html__('Description', 'king-addons'),
442 'type' => Controls_Manager::WYSIWYG,
443 'dynamic' => [
444 'active' => true,
445 ],
446 'default' => esc_html__('Use this slide to tell a compelling product story.', 'king-addons'),
447 ]
448 );
449
450 $repeater->add_control(
451 'kng_slide_bullets',
452 [
453 'label' => esc_html__('Bullets', 'king-addons'),
454 'type' => Controls_Manager::TEXTAREA,
455 'rows' => 4,
456 'description' => esc_html__('Add one bullet per line.', 'king-addons'),
457 ]
458 );
459
460 $repeater->add_control(
461 'kng_slide_button_text',
462 [
463 'label' => esc_html__('CTA Text', 'king-addons'),
464 'type' => Controls_Manager::TEXT,
465 'dynamic' => [
466 'active' => true,
467 ],
468 ]
469 );
470
471 $repeater->add_control(
472 'kng_slide_button_link',
473 [
474 'label' => esc_html__('CTA Link', 'king-addons'),
475 'type' => Controls_Manager::URL,
476 'dynamic' => [
477 'active' => true,
478 ],
479 'placeholder' => esc_html__('https://', 'king-addons'),
480 ]
481 );
482
483 $repeater->add_control(
484 'kng_slide_media_type',
485 [
486 'label' => esc_html__('Media Type', 'king-addons'),
487 'type' => Controls_Manager::SELECT,
488 'default' => 'image',
489 'options' => [
490 'image' => esc_html__('Image', 'king-addons'),
491 'video' => esc_html__('Video', 'king-addons'),
492 'lottie' => esc_html__('Lottie (Pro)', 'king-addons'),
493 ],
494 ]
495 );
496
497 $repeater->add_control(
498 'kng_slide_image',
499 [
500 'label' => esc_html__('Image', 'king-addons'),
501 'type' => Controls_Manager::MEDIA,
502 'default' => [
503 'url' => Utils::get_placeholder_image_src(),
504 ],
505 'condition' => [
506 'kng_slide_media_type' => 'image',
507 ],
508 ]
509 );
510
511 $repeater->add_control(
512 'kng_slide_video_url',
513 [
514 'label' => esc_html__('Video URL', 'king-addons'),
515 'type' => Controls_Manager::URL,
516 'dynamic' => [
517 'active' => true,
518 ],
519 'placeholder' => esc_html__('https://', 'king-addons'),
520 'condition' => [
521 'kng_slide_media_type' => 'video',
522 ],
523 ]
524 );
525
526 $repeater->add_control(
527 'kng_slide_video_autoplay',
528 [
529 'label' => esc_html__('Autoplay', 'king-addons'),
530 'type' => Controls_Manager::SWITCHER,
531 'return_value' => 'yes',
532 'condition' => [
533 'kng_slide_media_type' => 'video',
534 ],
535 ]
536 );
537
538 $repeater->add_control(
539 'kng_slide_video_muted',
540 [
541 'label' => esc_html__('Muted', 'king-addons'),
542 'type' => Controls_Manager::SWITCHER,
543 'return_value' => 'yes',
544 'default' => 'yes',
545 'condition' => [
546 'kng_slide_media_type' => 'video',
547 ],
548 ]
549 );
550
551 $repeater->add_control(
552 'kng_slide_video_loop',
553 [
554 'label' => esc_html__('Loop', 'king-addons'),
555 'type' => Controls_Manager::SWITCHER,
556 'return_value' => 'yes',
557 'condition' => [
558 'kng_slide_media_type' => 'video',
559 ],
560 ]
561 );
562
563 $repeater->add_control(
564 'kng_slide_video_controls',
565 [
566 'label' => esc_html__('Show Controls', 'king-addons'),
567 'type' => Controls_Manager::SWITCHER,
568 'return_value' => 'yes',
569 'default' => 'yes',
570 'condition' => [
571 'kng_slide_media_type' => 'video',
572 ],
573 ]
574 );
575
576 $repeater->add_control(
577 'kng_slide_video_poster',
578 [
579 'label' => esc_html__('Video Poster', 'king-addons'),
580 'type' => Controls_Manager::MEDIA,
581 'condition' => [
582 'kng_slide_media_type' => 'video',
583 ],
584 ]
585 );
586
587 $repeater->add_control(
588 'kng_slide_lottie_url',
589 [
590 'label' => esc_html__('Lottie URL (Pro)', 'king-addons'),
591 'type' => Controls_Manager::TEXT,
592 'dynamic' => [
593 'active' => true,
594 ],
595 'condition' => [
596 'kng_slide_media_type' => 'lottie',
597 ],
598 'classes' => 'king-addons-pro-control',
599 ]
600 );
601
602 $repeater->add_control(
603 'kng_slide_lottie_loop',
604 [
605 'label' => esc_html__('Lottie Loop (Pro)', 'king-addons'),
606 'type' => Controls_Manager::SWITCHER,
607 'return_value' => 'yes',
608 'default' => 'yes',
609 'condition' => [
610 'kng_slide_media_type' => 'lottie',
611 ],
612 'classes' => 'king-addons-pro-control',
613 ]
614 );
615
616 $repeater->add_control(
617 'kng_slide_lottie_speed',
618 [
619 'label' => esc_html__('Lottie Speed (Pro)', 'king-addons'),
620 'type' => Controls_Manager::NUMBER,
621 'default' => 1,
622 'min' => 0.1,
623 'step' => 0.1,
624 'condition' => [
625 'kng_slide_media_type' => 'lottie',
626 ],
627 'classes' => 'king-addons-pro-control',
628 ]
629 );
630
631 $repeater->add_control(
632 'kng_slide_lottie_segment_start',
633 [
634 'label' => esc_html__('Lottie Segment Start (Pro)', 'king-addons'),
635 'type' => Controls_Manager::NUMBER,
636 'min' => 0,
637 'condition' => [
638 'kng_slide_media_type' => 'lottie',
639 ],
640 'classes' => 'king-addons-pro-control',
641 ]
642 );
643
644 $repeater->add_control(
645 'kng_slide_lottie_segment_end',
646 [
647 'label' => esc_html__('Lottie Segment End (Pro)', 'king-addons'),
648 'type' => Controls_Manager::NUMBER,
649 'min' => 0,
650 'condition' => [
651 'kng_slide_media_type' => 'lottie',
652 ],
653 'classes' => 'king-addons-pro-control',
654 ]
655 );
656
657 $repeater->add_control(
658 'kng_slide_anchor',
659 [
660 'label' => esc_html__('Slide ID / Anchor (Pro)', 'king-addons'),
661 'type' => Controls_Manager::TEXT,
662 'description' => esc_html__('Used for deep linking.', 'king-addons'),
663 'classes' => 'king-addons-pro-control',
664 ]
665 );
666
667 $repeater->add_control(
668 'kng_slide_new_section',
669 [
670 'label' => esc_html__('Start New Section', 'king-addons'),
671 'type' => Controls_Manager::SWITCHER,
672 'return_value' => 'yes',
673 'separator' => 'before',
674 ]
675 );
676
677 $repeater->add_control(
678 'kng_slide_section_heading',
679 [
680 'label' => esc_html__('Section Heading', 'king-addons'),
681 'type' => Controls_Manager::TEXT,
682 'condition' => [
683 'kng_slide_new_section' => 'yes',
684 ],
685 ]
686 );
687
688 $this->add_control(
689 'kng_slides',
690 [
691 'label' => esc_html__('Slides', 'king-addons'),
692 'type' => Controls_Manager::REPEATER,
693 'fields' => $repeater->get_controls(),
694 'default' => $this->get_default_slides(),
695 'title_field' => '{{{ kng_slide_title }}}',
696 'description' => esc_html__('Free version is limited to 5 slides.', 'king-addons'),
697 ]
698 );
699
700 $this->add_control(
701 'kng_sections_enable',
702 [
703 'label' => esc_html__('Enable Sections', 'king-addons'),
704 'type' => Controls_Manager::SWITCHER,
705 'return_value' => 'yes',
706 'separator' => 'before',
707 ]
708 );
709
710 $this->end_controls_section();
711
712 $this->start_controls_section(
713 'kng_behavior_pro_section',
714 [
715 'label' => KING_ADDONS_ELEMENTOR_ICON_PRO . esc_html__('Behavior (Pro)', 'king-addons'),
716 'tab' => Controls_Manager::TAB_CONTENT,
717 ]
718 );
719
720 $this->add_control(
721 'kng_media_sticky',
722 [
723 'label' => esc_html__('Sticky Media Panel (Pro)', 'king-addons'),
724 'type' => Controls_Manager::SWITCHER,
725 'return_value' => 'yes',
726 ]
727 );
728
729 Core::renderUpgradeProNotice($this, Controls_Manager::RAW_HTML, 'scrollytelling-slides', 'kng_media_sticky', ['yes']);
730
731 $this->add_control(
732 'kng_media_sticky_offset',
733 [
734 'label' => esc_html__('Sticky Offset (px) (Pro)', 'king-addons'),
735 'type' => Controls_Manager::NUMBER,
736 'default' => 0,
737 'condition' => [
738 'kng_media_sticky' => 'yes',
739 ],
740 ]
741 );
742
743 $this->add_control(
744 'kng_transition_type',
745 [
746 'label' => esc_html__('Transition Type (Pro)', 'king-addons'),
747 'type' => Controls_Manager::SELECT,
748 'default' => 'fade',
749 'options' => [
750 'fade' => esc_html__('Fade', 'king-addons'),
751 'slide' => esc_html__('Slide', 'king-addons'),
752 'scale' => esc_html__('Scale', 'king-addons'),
753 'crossfade' => esc_html__('Crossfade', 'king-addons'),
754 ],
755 'separator' => 'before',
756 ]
757 );
758
759 Core::renderUpgradeProNotice($this, Controls_Manager::RAW_HTML, 'scrollytelling-slides', 'kng_transition_type', ['fade', 'slide', 'scale', 'crossfade']);
760
761 $this->add_control(
762 'kng_transition_duration',
763 [
764 'label' => esc_html__('Transition Duration (ms) (Pro)', 'king-addons'),
765 'type' => Controls_Manager::SLIDER,
766 'size_units' => ['ms'],
767 'range' => [
768 'ms' => [
769 'min' => 150,
770 'max' => 1200,
771 ],
772 ],
773 'default' => [
774 'size' => 420,
775 'unit' => 'ms',
776 ],
777 ]
778 );
779
780 $this->add_control(
781 'kng_transition_easing',
782 [
783 'label' => esc_html__('Transition Easing (Pro)', 'king-addons'),
784 'type' => Controls_Manager::SELECT,
785 'default' => 'ease',
786 'options' => [
787 'ease' => esc_html__('Ease', 'king-addons'),
788 'ease-in' => esc_html__('Ease In', 'king-addons'),
789 'ease-out' => esc_html__('Ease Out', 'king-addons'),
790 'ease-in-out' => esc_html__('Ease In Out', 'king-addons'),
791 'linear' => esc_html__('Linear', 'king-addons'),
792 ],
793 ]
794 );
795
796 Core::renderUpgradeProNotice($this, Controls_Manager::RAW_HTML, 'scrollytelling-slides', 'kng_transition_easing', ['ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear']);
797
798 $this->add_control(
799 'kng_dots_clickable',
800 [
801 'label' => esc_html__('Clickable Dots (Pro)', 'king-addons'),
802 'type' => Controls_Manager::SWITCHER,
803 'return_value' => 'yes',
804 'separator' => 'before',
805 ]
806 );
807
808 Core::renderUpgradeProNotice($this, Controls_Manager::RAW_HTML, 'scrollytelling-slides', 'kng_dots_clickable', ['yes']);
809
810 $this->add_control(
811 'kng_progress_label_mode',
812 [
813 'label' => esc_html__('Dot Labels (Pro)', 'king-addons'),
814 'type' => Controls_Manager::SELECT,
815 'default' => '',
816 'options' => [
817 '' => esc_html__('Default', 'king-addons'),
818 'none' => esc_html__('None', 'king-addons'),
819 'numbers' => esc_html__('Numbers', 'king-addons'),
820 'labels' => esc_html__('Slide Label', 'king-addons'),
821 ],
822 ]
823 );
824
825 Core::renderUpgradeProNotice($this, Controls_Manager::RAW_HTML, 'scrollytelling-slides', 'kng_progress_label_mode', ['none', 'numbers', 'labels']);
826
827 $this->add_control(
828 'kng_snap_mode',
829 [
830 'label' => esc_html__('Snap To Slides (Pro)', 'king-addons'),
831 'type' => Controls_Manager::SELECT,
832 'default' => 'off',
833 'options' => [
834 'off' => esc_html__('Off', 'king-addons'),
835 'soft' => esc_html__('Soft', 'king-addons'),
836 'strict' => esc_html__('Strict', 'king-addons'),
837 ],
838 'separator' => 'before',
839 ]
840 );
841
842 Core::renderUpgradeProNotice($this, Controls_Manager::RAW_HTML, 'scrollytelling-slides', 'kng_snap_mode', ['off', 'soft', 'strict']);
843
844 $this->add_control(
845 'kng_snap_duration',
846 [
847 'label' => esc_html__('Snap Duration (ms) (Pro)', 'king-addons'),
848 'type' => Controls_Manager::SLIDER,
849 'size_units' => ['ms'],
850 'range' => [
851 'ms' => [
852 'min' => 150,
853 'max' => 2000,
854 ],
855 ],
856 'default' => [
857 'size' => 420,
858 'unit' => 'ms',
859 ],
860 'condition' => [
861 'kng_snap_mode!' => 'off',
862 ],
863 ]
864 );
865
866 $this->add_control(
867 'kng_deep_link',
868 [
869 'label' => esc_html__('Deep Linking (Pro)', 'king-addons'),
870 'type' => Controls_Manager::SWITCHER,
871 'return_value' => 'yes',
872 ]
873 );
874
875 Core::renderUpgradeProNotice($this, Controls_Manager::RAW_HTML, 'scrollytelling-slides', 'kng_deep_link', ['yes']);
876
877 $this->add_control(
878 'kng_lottie_active_only',
879 [
880 'label' => esc_html__('Play Lottie On Active (Pro)', 'king-addons'),
881 'type' => Controls_Manager::SWITCHER,
882 'return_value' => 'yes',
883 'default' => 'yes',
884 'separator' => 'before',
885 ]
886 );
887
888 Core::renderUpgradeProNotice($this, Controls_Manager::RAW_HTML, 'scrollytelling-slides', 'kng_lottie_active_only', ['yes']);
889
890 $this->add_control(
891 'kng_section_separators',
892 [
893 'label' => esc_html__('Section Separators (Pro)', 'king-addons'),
894 'type' => Controls_Manager::SWITCHER,
895 'return_value' => 'yes',
896 'condition' => [
897 'kng_sections_enable' => 'yes',
898 ],
899 ]
900 );
901
902 Core::renderUpgradeProNotice($this, Controls_Manager::RAW_HTML, 'scrollytelling-slides', 'kng_section_separators', ['yes']);
903
904 $this->end_controls_section();
905 }
906
907 /**
908 * Layout controls.
909 *
910 * @return void
911 */
912 protected function register_layout_controls(): void
913 {
914 $this->start_controls_section(
915 'kng_layout_section',
916 [
917 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Layout', 'king-addons'),
918 'tab' => Controls_Manager::TAB_CONTENT,
919 ]
920 );
921
922 $this->add_responsive_control(
923 'kng_columns_ratio',
924 [
925 'label' => esc_html__('Columns Ratio', 'king-addons'),
926 'type' => Controls_Manager::SELECT,
927 'default' => '50-50',
928 'options' => [
929 '40-60' => esc_html__('40 / 60', 'king-addons'),
930 '50-50' => esc_html__('50 / 50', 'king-addons'),
931 '60-40' => esc_html__('60 / 40', 'king-addons'),
932 ],
933 'selectors_dictionary' => [
934 '40-60' => '--kng-sly-text-fr: 0.4fr; --kng-sly-media-fr: 0.6fr;',
935 '50-50' => '--kng-sly-text-fr: 1fr; --kng-sly-media-fr: 1fr;',
936 '60-40' => '--kng-sly-text-fr: 0.6fr; --kng-sly-media-fr: 0.4fr;',
937 ],
938 'selectors' => [
939 '{{WRAPPER}} .king-addons-scrollytelling' => '{{VALUE}}',
940 ],
941 ]
942 );
943
944 $this->add_control(
945 'kng_reverse_columns',
946 [
947 'label' => esc_html__('Reverse Columns', 'king-addons'),
948 'type' => Controls_Manager::SWITCHER,
949 'return_value' => 'yes',
950 ]
951 );
952
953 $this->add_control(
954 'kng_content_alignment',
955 [
956 'label' => esc_html__('Content Alignment', 'king-addons'),
957 'type' => Controls_Manager::SELECT,
958 'default' => 'top',
959 'options' => [
960 'top' => esc_html__('Top', 'king-addons'),
961 'center' => esc_html__('Center', 'king-addons'),
962 ],
963 'selectors_dictionary' => [
964 'top' => '--kng-sly-align: flex-start;',
965 'center' => '--kng-sly-align: center;',
966 ],
967 'selectors' => [
968 '{{WRAPPER}} .king-addons-scrollytelling' => '{{VALUE}}',
969 ],
970 ]
971 );
972
973 $this->add_responsive_control(
974 'kng_column_gap',
975 [
976 'label' => esc_html__('Column Gap', 'king-addons'),
977 'type' => Controls_Manager::SLIDER,
978 'size_units' => ['px', 'em'],
979 'range' => [
980 'px' => [
981 'min' => 0,
982 'max' => 120,
983 ],
984 'em' => [
985 'min' => 0,
986 'max' => 10,
987 ],
988 ],
989 'selectors' => [
990 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-column-gap: {{SIZE}}{{UNIT}};',
991 ],
992 ]
993 );
994
995 $this->add_responsive_control(
996 'kng_row_gap',
997 [
998 'label' => esc_html__('Slide Gap', 'king-addons'),
999 'type' => Controls_Manager::SLIDER,
1000 'size_units' => ['px', 'em'],
1001 'range' => [
1002 'px' => [
1003 'min' => 0,
1004 'max' => 160,
1005 ],
1006 'em' => [
1007 'min' => 0,
1008 'max' => 12,
1009 ],
1010 ],
1011 'selectors' => [
1012 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-row-gap: {{SIZE}}{{UNIT}};',
1013 ],
1014 ]
1015 );
1016
1017 $this->add_control(
1018 'kng_slide_min_height',
1019 [
1020 'label' => esc_html__('Slide Min Height', 'king-addons'),
1021 'type' => Controls_Manager::SELECT,
1022 'default' => 'auto',
1023 'options' => [
1024 'auto' => esc_html__('Auto', 'king-addons'),
1025 '100vh' => esc_html__('100vh', 'king-addons'),
1026 'custom' => esc_html__('Custom (vh)', 'king-addons'),
1027 ],
1028 'selectors_dictionary' => [
1029 'auto' => '--kng-sly-slide-min-height: auto;',
1030 '100vh' => '--kng-sly-slide-min-height: 100vh;',
1031 ],
1032 'selectors' => [
1033 '{{WRAPPER}} .king-addons-scrollytelling' => '{{VALUE}}',
1034 ],
1035 ]
1036 );
1037
1038 $this->add_control(
1039 'kng_slide_min_height_custom',
1040 [
1041 'label' => esc_html__('Custom Min Height', 'king-addons'),
1042 'type' => Controls_Manager::SLIDER,
1043 'size_units' => ['vh'],
1044 'range' => [
1045 'vh' => [
1046 'min' => 120,
1047 'max' => 400,
1048 ],
1049 ],
1050 'default' => [
1051 'size' => 120,
1052 'unit' => 'vh',
1053 ],
1054 'selectors' => [
1055 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-slide-min-height: {{SIZE}}{{UNIT}};',
1056 ],
1057 'condition' => [
1058 'kng_slide_min_height' => 'custom',
1059 ],
1060 ]
1061 );
1062
1063 $this->add_control(
1064 'kng_media_ratio',
1065 [
1066 'label' => esc_html__('Media Aspect Ratio', 'king-addons'),
1067 'type' => Controls_Manager::SELECT,
1068 'default' => '16-9',
1069 'options' => [
1070 '16-9' => esc_html__('16:9', 'king-addons'),
1071 '4-3' => esc_html__('4:3', 'king-addons'),
1072 '1-1' => esc_html__('1:1', 'king-addons'),
1073 '9-16' => esc_html__('9:16', 'king-addons'),
1074 'custom' => esc_html__('Custom', 'king-addons'),
1075 ],
1076 'selectors_dictionary' => [
1077 '16-9' => '--kng-sly-media-ratio: 16 / 9;',
1078 '4-3' => '--kng-sly-media-ratio: 4 / 3;',
1079 '1-1' => '--kng-sly-media-ratio: 1 / 1;',
1080 '9-16' => '--kng-sly-media-ratio: 9 / 16;',
1081 ],
1082 'selectors' => [
1083 '{{WRAPPER}} .king-addons-scrollytelling' => '{{VALUE}}',
1084 ],
1085 ]
1086 );
1087
1088 $this->add_control(
1089 'kng_media_ratio_custom',
1090 [
1091 'label' => esc_html__('Custom Ratio', 'king-addons'),
1092 'type' => Controls_Manager::TEXT,
1093 'placeholder' => '3/2',
1094 'condition' => [
1095 'kng_media_ratio' => 'custom',
1096 ],
1097 ]
1098 );
1099
1100 $this->add_control(
1101 'kng_media_fit',
1102 [
1103 'label' => esc_html__('Media Fit', 'king-addons'),
1104 'type' => Controls_Manager::SELECT,
1105 'default' => 'cover',
1106 'options' => [
1107 'cover' => esc_html__('Cover', 'king-addons'),
1108 'contain' => esc_html__('Contain', 'king-addons'),
1109 ],
1110 'selectors' => [
1111 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-media-fit: {{VALUE}};',
1112 ],
1113 ]
1114 );
1115
1116 $this->add_control(
1117 'kng_mobile_media_position',
1118 [
1119 'label' => esc_html__('Mobile Media Position', 'king-addons'),
1120 'type' => Controls_Manager::SELECT,
1121 'default' => 'above',
1122 'options' => [
1123 'above' => esc_html__('Above Text', 'king-addons'),
1124 'below' => esc_html__('Below Text', 'king-addons'),
1125 ],
1126 ]
1127 );
1128
1129 $this->add_group_control(
1130 Group_Control_Image_Size::get_type(),
1131 [
1132 'name' => 'kng_image_size',
1133 'default' => 'large',
1134 'separator' => 'before',
1135 ]
1136 );
1137
1138 $this->end_controls_section();
1139 }
1140
1141 /**
1142 * Behavior controls.
1143 *
1144 * @return void
1145 */
1146 protected function register_behavior_controls(): void
1147 {
1148 $this->start_controls_section(
1149 'kng_behavior_section',
1150 [
1151 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Behavior', 'king-addons'),
1152 'tab' => Controls_Manager::TAB_CONTENT,
1153 ]
1154 );
1155
1156 $this->add_control(
1157 'kng_activation_offset',
1158 [
1159 'label' => esc_html__('Activation Offset (%)', 'king-addons'),
1160 'type' => Controls_Manager::SLIDER,
1161 'size_units' => ['%'],
1162 'range' => [
1163 '%' => [
1164 'min' => 10,
1165 'max' => 80,
1166 ],
1167 ],
1168 'default' => [
1169 'size' => 40,
1170 'unit' => '%',
1171 ],
1172 ]
1173 );
1174
1175 $this->add_responsive_control(
1176 'kng_scroll_margin',
1177 [
1178 'label' => esc_html__('Scroll Margin Top', 'king-addons'),
1179 'type' => Controls_Manager::SLIDER,
1180 'size_units' => ['px', 'vh'],
1181 'range' => [
1182 'px' => [
1183 'min' => 0,
1184 'max' => 200,
1185 ],
1186 'vh' => [
1187 'min' => 0,
1188 'max' => 40,
1189 ],
1190 ],
1191 'default' => [
1192 'size' => 12,
1193 'unit' => 'vh',
1194 ],
1195 'selectors' => [
1196 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-scroll-margin: {{SIZE}}{{UNIT}};',
1197 ],
1198 ]
1199 );
1200
1201 $this->add_control(
1202 'kng_progress_style',
1203 [
1204 'label' => esc_html__('Progress Style', 'king-addons'),
1205 'type' => Controls_Manager::SELECT,
1206 'default' => 'dots',
1207 'options' => [
1208 'dots' => esc_html__('Dots', 'king-addons'),
1209 'line' => esc_html__('Line', 'king-addons'),
1210 ],
1211 ]
1212 );
1213
1214 $this->add_control(
1215 'kng_progress_markers',
1216 [
1217 'label' => esc_html__('Show Markers', 'king-addons'),
1218 'type' => Controls_Manager::SWITCHER,
1219 'return_value' => 'yes',
1220 'default' => '',
1221 'condition' => [
1222 'kng_progress_style' => 'line',
1223 ],
1224 ]
1225 );
1226
1227 $this->add_control(
1228 'kng_progress_numbers',
1229 [
1230 'label' => esc_html__('Show Numbers', 'king-addons'),
1231 'type' => Controls_Manager::SWITCHER,
1232 'return_value' => 'yes',
1233 ]
1234 );
1235
1236 $this->end_controls_section();
1237 }
1238
1239 /**
1240 * Style controls.
1241 *
1242 * @return void
1243 */
1244 protected function register_style_controls(): void
1245 {
1246 $this->start_controls_section(
1247 'kng_container_style',
1248 [
1249 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Container', 'king-addons'),
1250 'tab' => Controls_Manager::TAB_STYLE,
1251 ]
1252 );
1253
1254 $this->add_group_control(
1255 Group_Control_Background::get_type(),
1256 [
1257 'name' => 'kng_container_background',
1258 'selector' => '{{WRAPPER}} .king-addons-scrollytelling',
1259 ]
1260 );
1261
1262 $this->add_group_control(
1263 Group_Control_Border::get_type(),
1264 [
1265 'name' => 'kng_container_border',
1266 'selector' => '{{WRAPPER}} .king-addons-scrollytelling',
1267 ]
1268 );
1269
1270 $this->add_responsive_control(
1271 'kng_container_radius',
1272 [
1273 'label' => esc_html__('Border Radius', 'king-addons'),
1274 'type' => Controls_Manager::SLIDER,
1275 'size_units' => ['px', '%'],
1276 'selectors' => [
1277 '{{WRAPPER}} .king-addons-scrollytelling' => 'border-radius: {{SIZE}}{{UNIT}};',
1278 ],
1279 ]
1280 );
1281
1282 $this->add_responsive_control(
1283 'kng_container_padding',
1284 [
1285 'label' => esc_html__('Padding', 'king-addons'),
1286 'type' => Controls_Manager::DIMENSIONS,
1287 'size_units' => ['px', '%', 'em'],
1288 'selectors' => [
1289 '{{WRAPPER}} .king-addons-scrollytelling__inner' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1290 ],
1291 ]
1292 );
1293
1294 $this->add_responsive_control(
1295 'kng_container_max_width',
1296 [
1297 'label' => esc_html__('Max Width', 'king-addons'),
1298 'type' => Controls_Manager::SLIDER,
1299 'size_units' => ['px', '%'],
1300 'range' => [
1301 'px' => [
1302 'min' => 320,
1303 'max' => 1600,
1304 ],
1305 '%' => [
1306 'min' => 60,
1307 'max' => 100,
1308 ],
1309 ],
1310 'selectors' => [
1311 '{{WRAPPER}} .king-addons-scrollytelling__inner' => 'max-width: {{SIZE}}{{UNIT}}; margin-left: auto; margin-right: auto;',
1312 ],
1313 ]
1314 );
1315
1316 $this->end_controls_section();
1317
1318 $this->start_controls_section(
1319 'kng_typography_style',
1320 [
1321 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Typography', 'king-addons'),
1322 'tab' => Controls_Manager::TAB_STYLE,
1323 ]
1324 );
1325
1326 $this->add_control(
1327 'kng_title_color',
1328 [
1329 'label' => esc_html__('Title Color', 'king-addons'),
1330 'type' => Controls_Manager::COLOR,
1331 'selectors' => [
1332 '{{WRAPPER}} .king-addons-scrollytelling__title' => 'color: {{VALUE}};',
1333 ],
1334 ]
1335 );
1336
1337 $this->add_group_control(
1338 Group_Control_Typography::get_type(),
1339 [
1340 'name' => 'kng_title_typography',
1341 'selector' => '{{WRAPPER}} .king-addons-scrollytelling__title',
1342 ]
1343 );
1344
1345 $this->add_control(
1346 'kng_subtitle_color',
1347 [
1348 'label' => esc_html__('Subtitle Color', 'king-addons'),
1349 'type' => Controls_Manager::COLOR,
1350 'selectors' => [
1351 '{{WRAPPER}} .king-addons-scrollytelling__subtitle' => 'color: {{VALUE}};',
1352 ],
1353 ]
1354 );
1355
1356 $this->add_group_control(
1357 Group_Control_Typography::get_type(),
1358 [
1359 'name' => 'kng_subtitle_typography',
1360 'selector' => '{{WRAPPER}} .king-addons-scrollytelling__subtitle',
1361 ]
1362 );
1363
1364 $this->add_control(
1365 'kng_description_color',
1366 [
1367 'label' => esc_html__('Body Color', 'king-addons'),
1368 'type' => Controls_Manager::COLOR,
1369 'selectors' => [
1370 '{{WRAPPER}} .king-addons-scrollytelling__description' => 'color: {{VALUE}};',
1371 ],
1372 ]
1373 );
1374
1375 $this->add_group_control(
1376 Group_Control_Typography::get_type(),
1377 [
1378 'name' => 'kng_description_typography',
1379 'selector' => '{{WRAPPER}} .king-addons-scrollytelling__description',
1380 ]
1381 );
1382
1383 $this->add_control(
1384 'kng_bullets_color',
1385 [
1386 'label' => esc_html__('Bullets Color', 'king-addons'),
1387 'type' => Controls_Manager::COLOR,
1388 'selectors' => [
1389 '{{WRAPPER}} .king-addons-scrollytelling__bullets' => 'color: {{VALUE}};',
1390 ],
1391 ]
1392 );
1393
1394 $this->add_group_control(
1395 Group_Control_Typography::get_type(),
1396 [
1397 'name' => 'kng_bullets_typography',
1398 'selector' => '{{WRAPPER}} .king-addons-scrollytelling__bullets',
1399 ]
1400 );
1401
1402 $this->end_controls_section();
1403
1404 $this->start_controls_section(
1405 'kng_section_heading_style',
1406 [
1407 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Section Heading', 'king-addons'),
1408 'tab' => Controls_Manager::TAB_STYLE,
1409 'condition' => [
1410 'kng_sections_enable' => 'yes',
1411 ],
1412 ]
1413 );
1414
1415 $this->add_control(
1416 'kng_section_heading_color',
1417 [
1418 'label' => esc_html__('Color', 'king-addons'),
1419 'type' => Controls_Manager::COLOR,
1420 'selectors' => [
1421 '{{WRAPPER}} .king-addons-scrollytelling__section' => 'color: {{VALUE}};',
1422 ],
1423 ]
1424 );
1425
1426 $this->add_group_control(
1427 Group_Control_Typography::get_type(),
1428 [
1429 'name' => 'kng_section_heading_typography',
1430 'selector' => '{{WRAPPER}} .king-addons-scrollytelling__section',
1431 ]
1432 );
1433
1434 $this->add_responsive_control(
1435 'kng_section_heading_spacing',
1436 [
1437 'label' => esc_html__('Spacing', 'king-addons'),
1438 'type' => Controls_Manager::SLIDER,
1439 'size_units' => ['px', 'em'],
1440 'range' => [
1441 'px' => [
1442 'min' => 0,
1443 'max' => 80,
1444 ],
1445 'em' => [
1446 'min' => 0,
1447 'max' => 6,
1448 ],
1449 ],
1450 'selectors' => [
1451 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-section-gap: {{SIZE}}{{UNIT}};',
1452 ],
1453 ]
1454 );
1455
1456 $this->end_controls_section();
1457
1458 $this->start_controls_section(
1459 'kng_card_style',
1460 [
1461 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Slide Card', 'king-addons'),
1462 'tab' => Controls_Manager::TAB_STYLE,
1463 ]
1464 );
1465
1466 $this->add_group_control(
1467 Group_Control_Background::get_type(),
1468 [
1469 'name' => 'kng_card_background',
1470 'selector' => '{{WRAPPER}} .king-addons-scrollytelling__card',
1471 ]
1472 );
1473
1474 $this->add_group_control(
1475 Group_Control_Border::get_type(),
1476 [
1477 'name' => 'kng_card_border',
1478 'selector' => '{{WRAPPER}} .king-addons-scrollytelling__card',
1479 ]
1480 );
1481
1482 $this->add_responsive_control(
1483 'kng_card_radius',
1484 [
1485 'label' => esc_html__('Border Radius', 'king-addons'),
1486 'type' => Controls_Manager::SLIDER,
1487 'size_units' => ['px', '%'],
1488 'selectors' => [
1489 '{{WRAPPER}} .king-addons-scrollytelling__card' => 'border-radius: {{SIZE}}{{UNIT}};',
1490 ],
1491 ]
1492 );
1493
1494 $this->add_group_control(
1495 Group_Control_Box_Shadow::get_type(),
1496 [
1497 'name' => 'kng_card_shadow',
1498 'selector' => '{{WRAPPER}} .king-addons-scrollytelling__card',
1499 ]
1500 );
1501
1502 $this->add_responsive_control(
1503 'kng_card_padding',
1504 [
1505 'label' => esc_html__('Padding', 'king-addons'),
1506 'type' => Controls_Manager::DIMENSIONS,
1507 'size_units' => ['px', '%', 'em'],
1508 'selectors' => [
1509 '{{WRAPPER}} .king-addons-scrollytelling__card' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1510 ],
1511 ]
1512 );
1513
1514 $this->end_controls_section();
1515
1516 $this->start_controls_section(
1517 'kng_media_style',
1518 [
1519 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Media', 'king-addons'),
1520 'tab' => Controls_Manager::TAB_STYLE,
1521 ]
1522 );
1523
1524 $this->add_responsive_control(
1525 'kng_media_radius',
1526 [
1527 'label' => esc_html__('Border Radius', 'king-addons'),
1528 'type' => Controls_Manager::SLIDER,
1529 'size_units' => ['px', '%'],
1530 'selectors' => [
1531 '{{WRAPPER}} .king-addons-scrollytelling__media-frame' => 'border-radius: {{SIZE}}{{UNIT}};',
1532 ],
1533 ]
1534 );
1535
1536 $this->add_group_control(
1537 Group_Control_Box_Shadow::get_type(),
1538 [
1539 'name' => 'kng_media_shadow',
1540 'selector' => '{{WRAPPER}} .king-addons-scrollytelling__media-frame',
1541 ]
1542 );
1543
1544 $this->add_control(
1545 'kng_media_opacity_inactive',
1546 [
1547 'label' => esc_html__('Inactive Opacity', 'king-addons'),
1548 'type' => Controls_Manager::SLIDER,
1549 'size_units' => ['custom'],
1550 'range' => [
1551 'custom' => [
1552 'min' => 0,
1553 'max' => 1,
1554 'step' => 0.05,
1555 ],
1556 ],
1557 'default' => [
1558 'size' => 0.8,
1559 'unit' => 'custom',
1560 ],
1561 'selectors' => [
1562 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-media-opacity: {{SIZE}};',
1563 ],
1564 ]
1565 );
1566
1567 $this->add_control(
1568 'kng_media_opacity_active',
1569 [
1570 'label' => esc_html__('Active Opacity', 'king-addons'),
1571 'type' => Controls_Manager::SLIDER,
1572 'size_units' => ['custom'],
1573 'range' => [
1574 'custom' => [
1575 'min' => 0,
1576 'max' => 1,
1577 'step' => 0.05,
1578 ],
1579 ],
1580 'default' => [
1581 'size' => 1,
1582 'unit' => 'custom',
1583 ],
1584 'selectors' => [
1585 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-media-opacity-active: {{SIZE}};',
1586 ],
1587 ]
1588 );
1589
1590 $this->add_group_control(
1591 Group_Control_Background::get_type(),
1592 [
1593 'name' => 'kng_media_overlay',
1594 'types' => ['gradient'],
1595 'selector' => '{{WRAPPER}} .king-addons-scrollytelling__media-frame::after',
1596 ]
1597 );
1598
1599 $this->add_control(
1600 'kng_media_overlay_opacity',
1601 [
1602 'label' => esc_html__('Overlay Opacity', 'king-addons'),
1603 'type' => Controls_Manager::SLIDER,
1604 'size_units' => ['custom'],
1605 'range' => [
1606 'custom' => [
1607 'min' => 0,
1608 'max' => 1,
1609 'step' => 0.05,
1610 ],
1611 ],
1612 'default' => [
1613 'size' => 1,
1614 'unit' => 'custom',
1615 ],
1616 'selectors' => [
1617 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-media-overlay-opacity: {{SIZE}};',
1618 ],
1619 ]
1620 );
1621
1622 $this->end_controls_section();
1623
1624 $this->start_controls_section(
1625 'kng_progress_style_section',
1626 [
1627 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Progress', 'king-addons'),
1628 'tab' => Controls_Manager::TAB_STYLE,
1629 ]
1630 );
1631
1632 $this->add_responsive_control(
1633 'kng_progress_column_width',
1634 [
1635 'label' => esc_html__('Column Width', 'king-addons'),
1636 'type' => Controls_Manager::SLIDER,
1637 'size_units' => ['px'],
1638 'range' => [
1639 'px' => [
1640 'min' => 24,
1641 'max' => 120,
1642 ],
1643 ],
1644 'selectors' => [
1645 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-progress-cell: {{SIZE}}{{UNIT}};',
1646 ],
1647 ]
1648 );
1649
1650 $this->add_responsive_control(
1651 'kng_progress_alignment',
1652 [
1653 'label' => esc_html__('Dot Alignment', 'king-addons'),
1654 'type' => Controls_Manager::CHOOSE,
1655 'toggle' => true,
1656 'default' => 'center',
1657 'options' => [
1658 'left' => [
1659 'title' => esc_html__('Left', 'king-addons'),
1660 'icon' => 'eicon-text-align-left',
1661 ],
1662 'center' => [
1663 'title' => esc_html__('Center', 'king-addons'),
1664 'icon' => 'eicon-text-align-center',
1665 ],
1666 'right' => [
1667 'title' => esc_html__('Right', 'king-addons'),
1668 'icon' => 'eicon-text-align-right',
1669 ],
1670 ],
1671 'selectors_dictionary' => [
1672 'left' => '--kng-sly-progress-justify: flex-start; --kng-sly-progress-line-position: calc(var(--kng-sly-progress-size) / 2);',
1673 'center' => '--kng-sly-progress-justify: center; --kng-sly-progress-line-position: calc(var(--kng-sly-progress-cell) / 2);',
1674 'right' => '--kng-sly-progress-justify: flex-end; --kng-sly-progress-line-position: calc(var(--kng-sly-progress-cell) - (var(--kng-sly-progress-size) / 2));',
1675 ],
1676 'selectors' => [
1677 '{{WRAPPER}} .king-addons-scrollytelling' => '{{VALUE}}',
1678 ],
1679 ]
1680 );
1681
1682 $this->add_responsive_control(
1683 'kng_progress_dot_size',
1684 [
1685 'label' => esc_html__('Dot Size', 'king-addons'),
1686 'type' => Controls_Manager::SLIDER,
1687 'size_units' => ['px'],
1688 'range' => [
1689 'px' => [
1690 'min' => 6,
1691 'max' => 24,
1692 ],
1693 ],
1694 'selectors' => [
1695 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-progress-size: {{SIZE}}{{UNIT}};',
1696 ],
1697 ]
1698 );
1699
1700 $this->add_control(
1701 'kng_progress_dot_inactive',
1702 [
1703 'label' => esc_html__('Dot Color', 'king-addons'),
1704 'type' => Controls_Manager::COLOR,
1705 'selectors' => [
1706 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-progress-inactive: {{VALUE}};',
1707 ],
1708 ]
1709 );
1710
1711 $this->add_control(
1712 'kng_progress_dot_active',
1713 [
1714 'label' => esc_html__('Active Dot Color', 'king-addons'),
1715 'type' => Controls_Manager::COLOR,
1716 'selectors' => [
1717 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-progress-active: {{VALUE}};',
1718 ],
1719 ]
1720 );
1721
1722 $this->add_control(
1723 'kng_progress_dot_completed',
1724 [
1725 'label' => esc_html__('Completed Dot Color', 'king-addons'),
1726 'type' => Controls_Manager::COLOR,
1727 'selectors' => [
1728 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-progress-complete: {{VALUE}};',
1729 ],
1730 ]
1731 );
1732
1733 $this->add_control(
1734 'kng_progress_label_color',
1735 [
1736 'label' => esc_html__('Label Color', 'king-addons'),
1737 'type' => Controls_Manager::COLOR,
1738 'selectors' => [
1739 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-progress-label-color: {{VALUE}};',
1740 ],
1741 ]
1742 );
1743
1744 $this->add_group_control(
1745 Group_Control_Typography::get_type(),
1746 [
1747 'name' => 'kng_progress_label_typography',
1748 'selector' => '{{WRAPPER}} .king-addons-scrollytelling__dot-label',
1749 ]
1750 );
1751
1752 $this->add_control(
1753 'kng_progress_line_color',
1754 [
1755 'label' => esc_html__('Line Color', 'king-addons'),
1756 'type' => Controls_Manager::COLOR,
1757 'selectors' => [
1758 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-progress-line-color: {{VALUE}};',
1759 ],
1760 ]
1761 );
1762
1763 $this->add_responsive_control(
1764 'kng_progress_line_thickness',
1765 [
1766 'label' => esc_html__('Line Thickness', 'king-addons'),
1767 'type' => Controls_Manager::SLIDER,
1768 'size_units' => ['px'],
1769 'range' => [
1770 'px' => [
1771 'min' => 1,
1772 'max' => 6,
1773 ],
1774 ],
1775 'selectors' => [
1776 '{{WRAPPER}} .king-addons-scrollytelling' => '--kng-sly-progress-line: {{SIZE}}{{UNIT}};',
1777 ],
1778 ]
1779 );
1780
1781 $this->end_controls_section();
1782
1783 $this->start_controls_section(
1784 'kng_button_style',
1785 [
1786 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('CTA Button', 'king-addons'),
1787 'tab' => Controls_Manager::TAB_STYLE,
1788 ]
1789 );
1790
1791 $this->add_group_control(
1792 Group_Control_Typography::get_type(),
1793 [
1794 'name' => 'kng_button_typography',
1795 'selector' => '{{WRAPPER}} .king-addons-scrollytelling__button',
1796 ]
1797 );
1798
1799 $this->add_responsive_control(
1800 'kng_button_padding',
1801 [
1802 'label' => esc_html__('Padding', 'king-addons'),
1803 'type' => Controls_Manager::DIMENSIONS,
1804 'size_units' => ['px', 'em'],
1805 'selectors' => [
1806 '{{WRAPPER}} .king-addons-scrollytelling__button' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1807 ],
1808 ]
1809 );
1810
1811 $this->add_responsive_control(
1812 'kng_button_radius',
1813 [
1814 'label' => esc_html__('Border Radius', 'king-addons'),
1815 'type' => Controls_Manager::SLIDER,
1816 'size_units' => ['px', '%'],
1817 'selectors' => [
1818 '{{WRAPPER}} .king-addons-scrollytelling__button' => 'border-radius: {{SIZE}}{{UNIT}};',
1819 ],
1820 ]
1821 );
1822
1823 $this->start_controls_tabs('kng_button_tabs');
1824
1825 $this->start_controls_tab(
1826 'kng_button_tab_normal',
1827 [
1828 'label' => esc_html__('Normal', 'king-addons'),
1829 ]
1830 );
1831
1832 $this->add_control(
1833 'kng_button_bg',
1834 [
1835 'label' => esc_html__('Background', 'king-addons'),
1836 'type' => Controls_Manager::COLOR,
1837 'selectors' => [
1838 '{{WRAPPER}} .king-addons-scrollytelling__button' => 'background-color: {{VALUE}};',
1839 ],
1840 ]
1841 );
1842
1843 $this->add_control(
1844 'kng_button_color',
1845 [
1846 'label' => esc_html__('Text Color', 'king-addons'),
1847 'type' => Controls_Manager::COLOR,
1848 'selectors' => [
1849 '{{WRAPPER}} .king-addons-scrollytelling__button' => 'color: {{VALUE}};',
1850 ],
1851 ]
1852 );
1853
1854 $this->add_control(
1855 'kng_button_border_color',
1856 [
1857 'label' => esc_html__('Border Color', 'king-addons'),
1858 'type' => Controls_Manager::COLOR,
1859 'selectors' => [
1860 '{{WRAPPER}} .king-addons-scrollytelling__button' => 'border-color: {{VALUE}};',
1861 ],
1862 ]
1863 );
1864
1865 $this->end_controls_tab();
1866
1867 $this->start_controls_tab(
1868 'kng_button_tab_hover',
1869 [
1870 'label' => esc_html__('Hover', 'king-addons'),
1871 ]
1872 );
1873
1874 $this->add_control(
1875 'kng_button_bg_hover',
1876 [
1877 'label' => esc_html__('Background', 'king-addons'),
1878 'type' => Controls_Manager::COLOR,
1879 'selectors' => [
1880 '{{WRAPPER}} .king-addons-scrollytelling__button:hover' => 'background-color: {{VALUE}};',
1881 ],
1882 ]
1883 );
1884
1885 $this->add_control(
1886 'kng_button_color_hover',
1887 [
1888 'label' => esc_html__('Text Color', 'king-addons'),
1889 'type' => Controls_Manager::COLOR,
1890 'selectors' => [
1891 '{{WRAPPER}} .king-addons-scrollytelling__button:hover' => 'color: {{VALUE}};',
1892 ],
1893 ]
1894 );
1895
1896 $this->add_control(
1897 'kng_button_border_color_hover',
1898 [
1899 'label' => esc_html__('Border Color', 'king-addons'),
1900 'type' => Controls_Manager::COLOR,
1901 'selectors' => [
1902 '{{WRAPPER}} .king-addons-scrollytelling__button:hover' => 'border-color: {{VALUE}};',
1903 ],
1904 ]
1905 );
1906
1907 $this->end_controls_tab();
1908
1909 $this->end_controls_tabs();
1910
1911 $this->end_controls_section();
1912 }
1913
1914 /**
1915 * Register Pro notice controls.
1916 *
1917 * @return void
1918 */
1919 public function register_pro_notice_controls(): void
1920 {
1921 if (!king_addons_can_use_pro()) {
1922 Core::renderProFeaturesSection($this, '', Controls_Manager::RAW_HTML, 'scrollytelling-slides', [
1923 'Sticky media panel with configurable offset',
1924 'Animated transitions (fade/slide/scale) with easing presets',
1925 'Per-slide Lottie animations with play-on-active control',
1926 'Clickable dots with optional label modes',
1927 'Section separators in the progress rail',
1928 'Snap-to-slide scrolling with duration control',
1929 'Deep linking to slides with URL hash sync',
1930 ]);
1931 }
1932 }
1933
1934 /**
1935 * Render slide media.
1936 *
1937 * @param array<string, mixed> $slide Slide data.
1938 * @param array<string, mixed> $settings Widget settings.
1939 * @param bool $can_pro Whether Pro is available.
1940 *
1941 * @return string
1942 */
1943 protected function render_media(array $slide, array $settings, bool $can_pro): string
1944 {
1945 $media_type = $slide['kng_slide_media_type'] ?? 'image';
1946 $media_html = '';
1947
1948 if ('video' === $media_type) {
1949 $media_html = $this->get_video_markup($slide);
1950 } elseif ('lottie' === $media_type) {
1951 if ($can_pro) {
1952 $lottie_url = trim((string) ($slide['kng_slide_lottie_url'] ?? ''));
1953 if ($lottie_url !== '') {
1954 $speed = isset($slide['kng_slide_lottie_speed']) ? (float) $slide['kng_slide_lottie_speed'] : 1.0;
1955 if ($speed <= 0) {
1956 $speed = 1.0;
1957 }
1958
1959 $settings = [
1960 'loop' => ($slide['kng_slide_lottie_loop'] ?? '') === 'yes',
1961 'speed' => $speed,
1962 'segmentStart' => $slide['kng_slide_lottie_segment_start'] ?? '',
1963 'segmentEnd' => $slide['kng_slide_lottie_segment_end'] ?? '',
1964 ];
1965
1966 $media_html = sprintf(
1967 '<div class="king-addons-scrollytelling__lottie" data-json-url="%s" data-settings="%s"></div>',
1968 esc_url($lottie_url),
1969 esc_attr(wp_json_encode($settings))
1970 );
1971 } else {
1972 $media_html = '<div class="king-addons-scrollytelling__media-placeholder">' . esc_html__('Add a Lottie URL for this slide.', 'king-addons') . '</div>';
1973 }
1974 } else {
1975 $media_html = '<div class="king-addons-scrollytelling__media-placeholder">' . esc_html__('Lottie animation is available in Pro.', 'king-addons') . '</div>';
1976 }
1977 } else {
1978 $image = $slide['kng_slide_image'] ?? [];
1979 $image_id = is_array($image) ? (int) ($image['id'] ?? 0) : 0;
1980
1981 if ($image_id) {
1982 $media_html = Group_Control_Image_Size::get_attachment_image_html($settings, 'kng_image_size', $image_id);
1983 }
1984
1985 if (empty($media_html)) {
1986 $placeholder = Utils::get_placeholder_image_src();
1987 $media_html = '<img src="' . esc_url($placeholder) . '" alt="" loading="lazy" />';
1988 }
1989 }
1990
1991 if (empty($media_html)) {
1992 $media_html = '<div class="king-addons-scrollytelling__media-placeholder">' . esc_html__('Add media for this slide.', 'king-addons') . '</div>';
1993 }
1994
1995 return '<div class="king-addons-scrollytelling__media-frame">' . $media_html . '</div>';
1996 }
1997
1998 /**
1999 * Build video markup.
2000 *
2001 * @param array<string, mixed> $slide Slide data.
2002 *
2003 * @return string
2004 */
2005 protected function get_video_markup(array $slide): string
2006 {
2007 $video_url = '';
2008 if (!empty($slide['kng_slide_video_url'])) {
2009 $video_url = is_array($slide['kng_slide_video_url'])
2010 ? (string) ($slide['kng_slide_video_url']['url'] ?? '')
2011 : (string) $slide['kng_slide_video_url'];
2012 }
2013
2014 if (empty($video_url)) {
2015 return '';
2016 }
2017
2018 $autoplay = ($slide['kng_slide_video_autoplay'] ?? '') === 'yes';
2019 $muted = ($slide['kng_slide_video_muted'] ?? 'yes') === 'yes';
2020 $loop = ($slide['kng_slide_video_loop'] ?? '') === 'yes';
2021 $controls = ($slide['kng_slide_video_controls'] ?? 'yes') === 'yes';
2022 $poster = '';
2023
2024 if (!empty($slide['kng_slide_video_poster']['url'])) {
2025 $poster = (string) $slide['kng_slide_video_poster']['url'];
2026 }
2027
2028 $video = Embed::get_video_properties($video_url);
2029
2030 if (!empty($video['provider']) && !empty($video['video_id'])) {
2031 if ('youtube' === $video['provider']) {
2032 $params = [
2033 'rel' => '0',
2034 'playsinline' => '1',
2035 'autoplay' => $autoplay ? '1' : '0',
2036 'mute' => $muted ? '1' : '0',
2037 'controls' => $controls ? '1' : '0',
2038 ];
2039
2040 if ($loop) {
2041 $params['loop'] = '1';
2042 $params['playlist'] = (string) $video['video_id'];
2043 }
2044
2045 $src = 'https://www.youtube.com/embed/' . rawurlencode((string) $video['video_id']) . '?' . http_build_query($params);
2046
2047 $attrs = [
2048 'class' => 'king-addons-scrollytelling__iframe',
2049 'src' => 'about:blank',
2050 'data-src' => esc_url($src),
2051 'loading' => 'lazy',
2052 'allow' => 'autoplay; encrypted-media; picture-in-picture',
2053 'allowfullscreen' => 'allowfullscreen',
2054 'title' => esc_attr__('Slide video', 'king-addons'),
2055 ];
2056
2057 return '<iframe ' . $this->compile_attributes($attrs) . '></iframe>';
2058 }
2059
2060 if ('vimeo' === $video['provider']) {
2061 $params = [
2062 'autoplay' => $autoplay ? '1' : '0',
2063 'muted' => $muted ? '1' : '0',
2064 'loop' => $loop ? '1' : '0',
2065 'title' => '0',
2066 'byline' => '0',
2067 'portrait' => '0',
2068 'controls' => $controls ? '1' : '0',
2069 ];
2070
2071 $src = 'https://player.vimeo.com/video/' . rawurlencode((string) $video['video_id']);
2072 $data_src = $src . '?' . http_build_query($params);
2073
2074 $attrs = [
2075 'class' => 'king-addons-scrollytelling__iframe',
2076 'src' => 'about:blank',
2077 'data-src' => esc_url($data_src),
2078 'loading' => 'lazy',
2079 'allow' => 'autoplay; fullscreen; picture-in-picture',
2080 'allowfullscreen' => 'allowfullscreen',
2081 'title' => esc_attr__('Slide video', 'king-addons'),
2082 ];
2083
2084 return '<iframe ' . $this->compile_attributes($attrs) . '></iframe>';
2085 }
2086 }
2087
2088 $attrs = [
2089 'playsinline',
2090 'preload="metadata"',
2091 ];
2092
2093 if ($autoplay) {
2094 $attrs[] = 'autoplay';
2095 }
2096
2097 if ($muted) {
2098 $attrs[] = 'muted';
2099 }
2100
2101 if ($loop) {
2102 $attrs[] = 'loop';
2103 }
2104
2105 if ($controls) {
2106 $attrs[] = 'controls';
2107 }
2108
2109 $attr_string = implode(' ', $attrs);
2110 $poster_attr = !empty($poster) ? ' poster="' . esc_url($poster) . '"' : '';
2111
2112 return '<video src="' . esc_url($video_url) . '" ' . $attr_string . $poster_attr . '></video>';
2113 }
2114
2115 /**
2116 * Build an accessible label for dots.
2117 *
2118 * @param array<string, mixed> $slide Slide data.
2119 * @param int $index Slide index.
2120 *
2121 * @return string
2122 */
2123 protected function get_dot_label(array $slide, int $index): string
2124 {
2125 $label = trim((string) ($slide['kng_slide_label'] ?? ''));
2126 if (!empty($label)) {
2127 return sprintf(esc_html__('Go to %s', 'king-addons'), $label);
2128 }
2129
2130 return sprintf(esc_html__('Go to slide %d', 'king-addons'), $index + 1);
2131 }
2132
2133 /**
2134 * Build a visible label for dot UI.
2135 *
2136 * @param array<string, mixed> $slide Slide data.
2137 * @param int $index Slide index.
2138 *
2139 * @return string
2140 */
2141 protected function get_dot_display_label(array $slide, int $index): string
2142 {
2143 $label = trim((string) ($slide['kng_slide_label'] ?? ''));
2144 if (!empty($label)) {
2145 return $label;
2146 }
2147
2148 $title = trim((string) ($slide['kng_slide_title'] ?? ''));
2149 if (!empty($title)) {
2150 return $title;
2151 }
2152
2153 return sprintf(esc_html__('Slide %d', 'king-addons'), $index + 1);
2154 }
2155
2156 /**
2157 * Parse bullet list string into items.
2158 *
2159 * @param string $value Bullets text.
2160 *
2161 * @return array<int, string>
2162 */
2163 protected function parse_bullets(string $value): array
2164 {
2165 $value = trim($value);
2166 if ('' === $value) {
2167 return [];
2168 }
2169
2170 $items = preg_split('/\r\n|\r|\n/', $value);
2171 if (!$items) {
2172 return [];
2173 }
2174
2175 $items = array_map('trim', $items);
2176 return array_values(array_filter($items, static fn($item) => $item !== ''));
2177 }
2178
2179 /**
2180 * Build link attributes.
2181 *
2182 * @param array<string, mixed> $link Link data.
2183 *
2184 * @return array<string, string>
2185 */
2186 protected function get_link_attributes(array $link): array
2187 {
2188 $href = $link['url'] ?? '';
2189 if (empty($href)) {
2190 return [];
2191 }
2192
2193 $attributes = [
2194 'href' => esc_url($href),
2195 ];
2196
2197 $rels = [];
2198
2199 if (!empty($link['is_external'])) {
2200 $attributes['target'] = '_blank';
2201 $rels[] = 'noopener';
2202 $rels[] = 'noreferrer';
2203 }
2204
2205 if (!empty($link['nofollow'])) {
2206 $rels[] = 'nofollow';
2207 }
2208
2209 if (!empty($rels)) {
2210 $attributes['rel'] = implode(' ', array_unique($rels));
2211 }
2212
2213 return $attributes;
2214 }
2215
2216 /**
2217 * Compile attributes for output.
2218 *
2219 * @param array<string, string> $attrs Attributes.
2220 *
2221 * @return string
2222 */
2223 protected function compile_attributes(array $attrs): string
2224 {
2225 $compiled = [];
2226
2227 foreach ($attrs as $key => $value) {
2228 if ($value === '') {
2229 continue;
2230 }
2231
2232 $compiled[] = esc_attr($key) . '="' . esc_attr($value) . '"';
2233 }
2234
2235 return implode(' ', $compiled);
2236 }
2237
2238 /**
2239 * Sanitize anchor.
2240 *
2241 * @param string $value Anchor value.
2242 *
2243 * @return string
2244 */
2245 protected function sanitize_anchor(string $value): string
2246 {
2247 $value = trim($value);
2248 if ($value === '') {
2249 return '';
2250 }
2251
2252 return sanitize_title($value);
2253 }
2254
2255 /**
2256 * Sanitize custom ratio input.
2257 *
2258 * @param string $ratio Ratio string.
2259 *
2260 * @return string
2261 */
2262 protected function sanitize_ratio(string $ratio): string
2263 {
2264 $ratio = trim($ratio);
2265 if ($ratio === '') {
2266 return '';
2267 }
2268
2269 if (preg_match('/^(\d+(?:\.\d+)?)\s*[:\/]\s*(\d+(?:\.\d+)?)$/', $ratio, $matches)) {
2270 $width = (float) $matches[1];
2271 $height = (float) $matches[2];
2272 if ($width > 0 && $height > 0) {
2273 return $width . ' / ' . $height;
2274 }
2275 }
2276
2277 return '';
2278 }
2279
2280 /**
2281 * Sanitize transition type.
2282 *
2283 * @param string $value Transition type.
2284 *
2285 * @return string
2286 */
2287 protected function sanitize_transition_type(string $value): string
2288 {
2289 $allowed = ['fade', 'slide', 'scale', 'crossfade'];
2290 return in_array($value, $allowed, true) ? $value : 'fade';
2291 }
2292
2293 /**
2294 * Sanitize transition easing.
2295 *
2296 * @param string $value Easing preset.
2297 *
2298 * @return string
2299 */
2300 protected function sanitize_transition_easing(string $value): string
2301 {
2302 $allowed = ['ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear'];
2303 return in_array($value, $allowed, true) ? $value : 'ease';
2304 }
2305
2306 /**
2307 * Sanitize snap mode.
2308 *
2309 * @param string $value Snap mode.
2310 *
2311 * @return string
2312 */
2313 protected function sanitize_snap_mode(string $value): string
2314 {
2315 $allowed = ['off', 'soft', 'strict'];
2316 return in_array($value, $allowed, true) ? $value : 'off';
2317 }
2318
2319 /**
2320 * Default slides.
2321 *
2322 * @return array<int, array<string, mixed>>
2323 */
2324 protected function get_default_slides(): array
2325 {
2326 return [
2327 [
2328 'kng_slide_label' => esc_html__('Intro', 'king-addons'),
2329 'kng_slide_title' => esc_html__('Tell the big idea', 'king-addons'),
2330 'kng_slide_subtitle' => esc_html__('Set the stage', 'king-addons'),
2331 'kng_slide_description' => esc_html__('Introduce the challenge and the promise in one focused slide.', 'king-addons'),
2332 'kng_slide_bullets' => "Quick context\nClear value\nStrong visual",
2333 'kng_slide_button_text' => esc_html__('Learn more', 'king-addons'),
2334 'kng_slide_button_link' => [
2335 'url' => '#',
2336 ],
2337 'kng_slide_media_type' => 'image',
2338 'kng_slide_image' => [
2339 'url' => Utils::get_placeholder_image_src(),
2340 ],
2341 ],
2342 [
2343 'kng_slide_label' => esc_html__('Feature', 'king-addons'),
2344 'kng_slide_title' => esc_html__('Highlight the feature', 'king-addons'),
2345 'kng_slide_subtitle' => esc_html__('Explain the impact', 'king-addons'),
2346 'kng_slide_description' => esc_html__('Use bullets to summarize outcomes and benefits.', 'king-addons'),
2347 'kng_slide_bullets' => "Faster workflows\nCleaner outputs\nEasy adoption",
2348 'kng_slide_media_type' => 'image',
2349 'kng_slide_image' => [
2350 'url' => Utils::get_placeholder_image_src(),
2351 ],
2352 ],
2353 [
2354 'kng_slide_label' => esc_html__('Next step', 'king-addons'),
2355 'kng_slide_title' => esc_html__('Move to action', 'king-addons'),
2356 'kng_slide_subtitle' => esc_html__('Invite the user', 'king-addons'),
2357 'kng_slide_description' => esc_html__('Close with a clear action and supporting visual.', 'king-addons'),
2358 'kng_slide_button_text' => esc_html__('Get started', 'king-addons'),
2359 'kng_slide_button_link' => [
2360 'url' => '#',
2361 ],
2362 'kng_slide_media_type' => 'image',
2363 'kng_slide_image' => [
2364 'url' => Utils::get_placeholder_image_src(),
2365 ],
2366 ],
2367 ];
2368 }
2369 }
2370