start_controls_section( 'section_items', array( 'label' => esc_html__( 'Items', 'advanced-animation' ) ) ); $items = new Repeater(); $items->add_control( 'item_type', array( 'label' => esc_html__( 'Content', 'advanced-animation' ), 'type' => Controls_Manager::SELECT, 'default' => 'text', 'options' => array( 'text' => __( 'Text', 'advanced-animation' ), 'image' => __( 'Image', 'advanced-animation' ), 'icon' => __( 'Icon & text', 'advanced-animation' ), 'template' => __( 'Saved template', 'advanced-animation' ) ), ) ); $items->add_control( 'text', array( 'label' => esc_html__( 'Text', 'advanced-animation' ), 'type' => Controls_Manager::TEXT, 'default' => __( 'Create something great', 'advanced-animation' ), 'dynamic' => array( 'active' => true ), 'condition' => array( 'item_type' => array( 'text', 'icon' ) ), ) ); $items->add_control( 'image', array( 'label' => esc_html__( 'Image', 'advanced-animation' ), 'type' => Controls_Manager::MEDIA, 'dynamic' => array( 'active' => true ), 'condition' => array( 'item_type' => 'image' ), ) ); $items->add_control( 'image_alt', array( 'label' => esc_html__( 'Alternative text', 'advanced-animation' ), 'type' => Controls_Manager::TEXT, 'description' => __( 'Leave blank to use the media library alternative text.', 'advanced-animation' ), 'condition' => array( 'item_type' => 'image' ), ) ); $items->add_control( 'icon', array( 'label' => esc_html__( 'Icon', 'advanced-animation' ), 'type' => Controls_Manager::ICONS, 'default' => array( 'value' => 'fas fa-star', 'library' => 'fa-solid' ), 'condition' => array( 'item_type' => 'icon' ), ) ); $templates = array( '' => __( 'Select a template', 'advanced-animation' ) ); foreach ( get_posts( array( 'post_type' => 'elementor_library', 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => 'title', 'order' => 'ASC' ) ) as $template ) { $templates[ $template->ID ] = $template->post_title; } $items->add_control( 'template_id', array( 'label' => esc_html__( 'Template', 'advanced-animation' ), 'type' => Controls_Manager::SELECT2, 'options' => $templates, 'description' => __( 'Use a published Elementor template for cards or other custom content. Set its width under Style → Items.', 'advanced-animation' ), 'condition' => array( 'item_type' => 'template' ), ) ); $items->add_control( 'link', array( 'label' => esc_html__( 'Link', 'advanced-animation' ), 'type' => Controls_Manager::URL, 'dynamic' => array( 'active' => true ), 'condition' => array( 'item_type!' => 'template' ), ) ); $this->add_control( 'items', array( 'type' => Controls_Manager::REPEATER, 'fields' => $items->get_controls(), 'title_field' => '{{{ item_type === "template" ? "Template #" + template_id : (text || item_type) }}}', 'default' => array( array( 'item_type' => 'icon', 'text' => __( 'Design without limits', 'advanced-animation' ), 'icon' => array( 'value' => 'fas fa-star', 'library' => 'fa-solid' ) ), array( 'item_type' => 'text', 'text' => __( 'Build something bold', 'advanced-animation' ) ), array( 'item_type' => 'icon', 'text' => __( 'Keep moving forward', 'advanced-animation' ), 'icon' => array( 'value' => 'fas fa-star', 'library' => 'fa-solid' ) ), ), ) ); $this->add_control( 'accessible_label', array( 'label' => __( 'Accessible label', 'advanced-animation' ), 'type' => Controls_Manager::TEXT, 'default' => __( 'Scrolling content', 'advanced-animation' ) ) ); $this->end_controls_section(); $this->start_controls_section( 'section_motion', array( 'label' => __( 'Ticker', 'advanced-animation' ) ) ); $this->add_control( 'direction', array( 'label' => __( 'Direction', 'advanced-animation' ), 'type' => Controls_Manager::SELECT, 'default' => 'left', 'options' => array( 'left' => __( 'Left', 'advanced-animation' ), 'right' => __( 'Right', 'advanced-animation' ), 'up' => __( 'Up', 'advanced-animation' ), 'down' => __( 'Down', 'advanced-animation' ) ), ) ); $this->add_responsive_control( 'speed', array( 'label' => __( 'Speed (px/second)', 'advanced-animation' ), 'type' => Controls_Manager::NUMBER, 'default' => 60, 'min' => 0, 'max' => 1000, 'step' => 1, 'description' => __( 'Constant speed regardless of content length. Use 0 for a drag-only ticker.', 'advanced-animation' ), 'selectors' => array( '{{WRAPPER}} .aa-marquee' => '--aa-marquee-speed: {{VALUE}};' ), ) ); $this->add_control( 'hover_speed', array( 'label' => __( 'Speed on hover', 'advanced-animation' ), 'type' => Controls_Manager::NUMBER, 'default' => 0, 'min' => 0, 'max' => 3, 'step' => 0.1, 'description' => __( 'Multiplier: 0 pauses, 0.5 slows down, 1 keeps the same speed, 2 doubles it.', 'advanced-animation' ), ) ); $this->add_control( 'draggable', array( 'label' => __( 'Drag to scroll', 'advanced-animation' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes' ) ); $this->add_control( 'momentum', array( 'label' => __( 'Drag momentum', 'advanced-animation' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes', 'condition' => array( 'draggable' => 'yes' ) ) ); $this->add_control( 'show_pause', array( 'label' => __( 'Show pause / play button', 'advanced-animation' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes' ) ); $this->add_control( 'preview_motion', array( 'label' => __( 'Animate in editor', 'advanced-animation' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes' ) ); $this->end_controls_section(); $this->start_controls_section( 'section_layout', array( 'label' => __( 'Layout', 'advanced-animation' ), 'tab' => Controls_Manager::TAB_STYLE ) ); $this->add_responsive_control( 'gap', array( 'label' => __( 'Gap', 'advanced-animation' ), 'type' => Controls_Manager::SLIDER, 'size_units' => array( 'px', 'em', 'rem' ), 'range' => array( 'px' => array( 'min' => 0, 'max' => 200 ) ), 'default' => array( 'size' => 40, 'unit' => 'px' ), 'selectors' => array( '{{WRAPPER}} .aa-marquee' => '--aa-marquee-gap: {{SIZE}}{{UNIT}};' ), ) ); $this->add_responsive_control( 'height', array( 'label' => __( 'Viewport height', 'advanced-animation' ), 'type' => Controls_Manager::SLIDER, 'size_units' => array( 'px', 'vh' ), 'range' => array( 'px' => array( 'min' => 40, 'max' => 1000 ), 'vh' => array( 'min' => 5, 'max' => 100 ) ), 'description' => __( 'Vertical tickers default to 300px. Horizontal tickers use their content height unless set here.', 'advanced-animation' ), 'selectors' => array( '{{WRAPPER}} .aa-marquee__viewport' => '--aa-marquee-height: {{SIZE}}{{UNIT}};' ), ) ); $this->add_responsive_control( 'alignment', array( 'label' => __( 'Item alignment', 'advanced-animation' ), 'type' => Controls_Manager::SELECT, 'default' => 'center', 'options' => array( 'flex-start' => __( 'Start', 'advanced-animation' ), 'center' => __( 'Center', 'advanced-animation' ), 'flex-end' => __( 'End', 'advanced-animation' ) ), 'selectors' => array( '{{WRAPPER}} .aa-marquee' => '--aa-marquee-align: {{VALUE}};' ), ) ); $this->add_control( 'fade', array( 'label' => __( 'Fade edges', 'advanced-animation' ), 'type' => Controls_Manager::SWITCHER, 'default' => 'yes' ) ); $this->add_responsive_control( 'fade_width', array( 'label' => __( 'Fade width', 'advanced-animation' ), 'type' => Controls_Manager::SLIDER, 'size_units' => array( 'px', '%' ), 'default' => array( 'size' => 60, 'unit' => 'px' ), 'range' => array( 'px' => array( 'min' => 0, 'max' => 300 ), '%' => array( 'min' => 0, 'max' => 50 ) ), 'selectors' => array( '{{WRAPPER}} .aa-marquee' => '--aa-marquee-fade: {{SIZE}}{{UNIT}};' ), 'condition' => array( 'fade' => 'yes' ), ) ); $this->add_responsive_control( 'padding', array( 'label' => __( 'Padding', 'advanced-animation' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => array( 'px', 'em', '%' ), 'selectors' => array( '{{WRAPPER}} .aa-marquee__viewport' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};' ), ) ); $this->add_group_control( Group_Control_Background::get_type(), array( 'name' => 'background', 'types' => array( 'classic', 'gradient' ), 'selector' => '{{WRAPPER}} .aa-marquee' ) ); $this->end_controls_section(); $this->start_controls_section( 'section_item_style', array( 'label' => __( 'Items', 'advanced-animation' ), 'tab' => Controls_Manager::TAB_STYLE ) ); $this->add_group_control( Group_Control_Typography::get_type(), array( 'name' => 'typography', 'selector' => '{{WRAPPER}} .aa-marquee__text' ) ); $this->add_control( 'text_color', array( 'label' => __( 'Text color', 'advanced-animation' ), 'type' => Controls_Manager::COLOR, 'selectors' => array( '{{WRAPPER}} .aa-marquee__text' => 'color: {{VALUE}};' ) ) ); $this->add_control( 'icon_color', array( 'label' => __( 'Icon color', 'advanced-animation' ), 'type' => Controls_Manager::COLOR, 'selectors' => array( '{{WRAPPER}} .aa-marquee__icon' => 'color: {{VALUE}};' ) ) ); $this->add_responsive_control( 'icon_size', array( 'label' => __( 'Icon size', 'advanced-animation' ), 'type' => Controls_Manager::SLIDER, 'range' => array( 'px' => array( 'min' => 8, 'max' => 150 ) ), 'selectors' => array( '{{WRAPPER}} .aa-marquee__icon' => 'font-size: {{SIZE}}{{UNIT}};' ) ) ); $this->add_responsive_control( 'icon_gap', array( 'label' => __( 'Icon spacing', 'advanced-animation' ), 'type' => Controls_Manager::SLIDER, 'selectors' => array( '{{WRAPPER}} .aa-marquee__content' => 'gap: {{SIZE}}{{UNIT}};' ) ) ); $this->add_responsive_control( 'image_width', array( 'label' => __( 'Image width', 'advanced-animation' ), 'type' => Controls_Manager::SLIDER, 'range' => array( 'px' => array( 'min' => 10, 'max' => 800 ) ), 'selectors' => array( '{{WRAPPER}} .aa-marquee__image' => 'width: {{SIZE}}{{UNIT}};' ) ) ); $this->add_responsive_control( 'image_height', array( 'label' => __( 'Image height', 'advanced-animation' ), 'type' => Controls_Manager::SLIDER, 'range' => array( 'px' => array( 'min' => 10, 'max' => 800 ) ), 'selectors' => array( '{{WRAPPER}} .aa-marquee__image' => 'height: {{SIZE}}{{UNIT}};' ) ) ); $this->add_control( 'image_fit', array( 'label' => __( 'Image fit', 'advanced-animation' ), 'type' => Controls_Manager::SELECT, 'default' => 'contain', 'options' => array( 'contain' => __( 'Contain', 'advanced-animation' ), 'cover' => __( 'Cover', 'advanced-animation' ) ), 'selectors' => array( '{{WRAPPER}} .aa-marquee__image' => 'object-fit: {{VALUE}};' ) ) ); $this->add_responsive_control( 'item_width', array( 'label' => __( 'Item width', 'advanced-animation' ), 'type' => Controls_Manager::SLIDER, 'size_units' => array( 'px', 'vw' ), 'range' => array( 'px' => array( 'min' => 40, 'max' => 1000 ), 'vw' => array( 'min' => 5, 'max' => 100 ) ), 'selectors' => array( '{{WRAPPER}} .aa-marquee__item' => 'width: {{SIZE}}{{UNIT}};' ), ) ); $this->add_responsive_control( 'item_padding', array( 'label' => __( 'Item padding', 'advanced-animation' ), 'type' => Controls_Manager::DIMENSIONS, 'size_units' => array( 'px', 'em', '%' ), 'selectors' => array( '{{WRAPPER}} .aa-marquee__item' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};' ) ) ); $this->add_control( 'item_background', array( 'label' => __( 'Item background', 'advanced-animation' ), 'type' => Controls_Manager::COLOR, 'selectors' => array( '{{WRAPPER}} .aa-marquee__item' => 'background-color: {{VALUE}};' ) ) ); $this->add_group_control( Group_Control_Border::get_type(), array( 'name' => 'item_border', 'selector' => '{{WRAPPER}} .aa-marquee__item' ) ); $this->add_control( 'item_radius', array( 'label' => __( 'Border radius', 'advanced-animation' ), 'type' => Controls_Manager::SLIDER, 'selectors' => array( '{{WRAPPER}} .aa-marquee__item' => 'border-radius: {{SIZE}}{{UNIT}}; overflow: hidden;' ) ) ); $this->add_group_control( Group_Control_Box_Shadow::get_type(), array( 'name' => 'item_shadow', 'selector' => '{{WRAPPER}} .aa-marquee__item' ) ); $this->end_controls_section(); } private function render_template( int $id ): void { if ( $id < 1 ) { return; } $post = get_post( $id ); if ( ! $post || 'elementor_library' !== $post->post_type || 'publish' !== $post->post_status || post_password_required( $post ) || in_array( $id, self::$template_stack, true ) || count( self::$template_stack ) >= 8 ) { return; } self::$template_stack[] = $id; try { echo \Elementor\Plugin::$instance->frontend->get_builder_content_for_display( $id, true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Elementor renders the template. } finally { array_pop( self::$template_stack ); } } protected function render() { $settings = $this->get_settings_for_display(); $items = $settings['items'] ?? array(); if ( ! $items ) { return; } $direction = in_array( $settings['direction'] ?? '', array( 'left', 'right', 'up', 'down' ), true ) ? $settings['direction'] : 'left'; $config = array( 'direction' => $direction, 'speed' => max( 0, min( 1000, (float) ( $settings['speed'] ?? 60 ) ) ), 'hoverSpeed' => max( 0, min( 3, (float) ( $settings['hover_speed'] ?? 0 ) ) ), 'draggable' => 'yes' === ( $settings['draggable'] ?? '' ), 'momentum' => 'yes' === ( $settings['momentum'] ?? '' ), 'previewMotion' => 'yes' === ( $settings['preview_motion'] ?? '' ), ); $classes = 'aa-marquee' . ( in_array( $direction, array( 'up', 'down' ), true ) ? ' aa-marquee--vertical' : '' ) . ( 'yes' === ( $settings['fade'] ?? '' ) ? ' aa-marquee--fade' : '' ); ?>