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

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