Border section.
*
* The native border targets the outer widget container while Style >
* Container styles the inner slider card — keeping both would stack two
* nested borders. Stripping it from the merged stack also stops
* previously saved native border values from rendering; the editor panel
* itself is filtered via JS (elements/widget/controls/common/default).
*
* @param bool $with_common_controls Whether to include the common controls.
* @return array Widget stack of controls.
*/
public function get_stack( $with_common_controls = true ) {
$stack = parent::get_stack( $with_common_controls );
if ( isset( $stack['controls']['_section_border'] ) ) {
foreach ( $stack['controls'] as $control_id => $control ) {
if ( '_section_border' === $control_id ) {
unset($stack['controls'][$control_id]);
continue;
}
if ( isset( $control['section'] ) && '_section_border' === $control['section'] ) {
unset($stack['controls'][$control_id]);
}
}
}
return $stack;
}
protected function register_controls() {
$is_pro = function_exists( 'pixvisualize_can_use_pro' ) && pixvisualize_can_use_pro();
/* ------------------------------------------------------------------ *
* CONTENT TAB — "Images & Content" (mirror of Gutenberg panel) *
* ------------------------------------------------------------------ */
$this->start_controls_section( 'pixvisualize_content_section', array(
'label' => __( 'Images & Content', 'pixvisualize' ),
) );
$this->add_control( 'before_image', array(
'label' => __( 'Before Image', 'pixvisualize' ),
'type' => Controls_Manager::MEDIA,
'media_types' => array('image'),
) );
$this->add_control( 'after_image', array(
'label' => __( 'After Image', 'pixvisualize' ),
'type' => Controls_Manager::MEDIA,
'media_types' => array('image'),
) );
$this->add_control( 'before_label', array(
'label' => __( 'Before Label', 'pixvisualize' ),
'type' => Controls_Manager::TEXT,
'default' => __( 'Before', 'pixvisualize' ),
'condition' => array(
'show_labels' => 'yes',
),
) );
$this->add_control( 'after_label', array(
'label' => __( 'After Label', 'pixvisualize' ),
'type' => Controls_Manager::TEXT,
'default' => __( 'After', 'pixvisualize' ),
'condition' => array(
'show_labels' => 'yes',
),
) );
$this->add_control( 'show_labels', array(
'label' => __( 'Show Labels', 'pixvisualize' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
) );
$this->end_controls_section();
/* ------------------------------------------------------------------ *
* CONTENT TAB — "Caption" (title + text below the slider) *
* ------------------------------------------------------------------ */
$this->start_controls_section( 'pixvisualize_caption_section', array(
'label' => __( 'Caption', 'pixvisualize' ),
) );
$this->add_control( 'show_caption', array(
'label' => __( 'Show Caption', 'pixvisualize' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
) );
$this->add_control( 'caption_title', array(
'label' => __( 'Caption Title', 'pixvisualize' ),
'type' => Controls_Manager::TEXT,
'default' => '',
'condition' => array(
'show_caption' => 'yes',
),
) );
$this->add_control( 'caption_text', array(
'label' => __( 'Caption Text', 'pixvisualize' ),
'type' => Controls_Manager::TEXT,
'default' => '',
'condition' => array(
'show_caption' => 'yes',
),
) );
$this->end_controls_section();
/* ------------------------------------------------------------------ *
* CONTENT TAB — "Layout & Orientation" *
* ------------------------------------------------------------------ */
$this->start_controls_section( 'pixvisualize_layout_section', array(
'label' => __( 'Layout & Orientation', 'pixvisualize' ),
) );
$this->add_control( 'orientation', array(
'label' => __( 'Orientation', 'pixvisualize' ),
'type' => Controls_Manager::SELECT,
'options' => $this->orientation_options(),
'default' => 'horizontal',
) );
$this->add_control( 'aspect_ratio', array(
'label' => __( 'Aspect Ratio', 'pixvisualize' ),
'description' => __( 'Width / height proportion, always respected (contain): 21/9 fills the width, 9/21 fills the height.', 'pixvisualize' ),
'type' => Controls_Manager::SELECT,
'options' => array(
'21/9' => '21 / 9',
'2/1' => '2 / 1',
'16/9' => '16 / 9',
'3/2' => '3 / 2',
'4/3' => '4 / 3',
'1/1' => '1 / 1',
'3/4' => '3 / 4',
'2/3' => '2 / 3',
'9/16' => '9 / 16',
'1/2' => '1 / 2',
'9/21' => '9 / 21',
),
'default' => '16/9',
) );
$this->add_control( 'height', array(
'label' => __( 'Height', 'pixvisualize' ),
'description' => __( 'Desired zone height, capped by a fixed parent height (contain).', 'pixvisualize' ),
'type' => Controls_Manager::SLIDER,
'size_units' => array('px'),
'range' => array(
'px' => array(
'min' => 100,
'max' => 2000,
),
),
'default' => array(
'unit' => 'px',
'size' => 450,
),
) );
$this->add_control( 'full_width', array(
'label' => __( 'Full Width', 'pixvisualize' ),
'description' => __( 'Spans the content width (ratio kept, centered). Useless with a fixed parent height — contain already caps it.', 'pixvisualize' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'no',
) );
$this->add_control( 'initial_position', array(
'label' => __( 'Initial Position', 'pixvisualize' ),
'type' => Controls_Manager::SLIDER,
'size_units' => array('%'),
'range' => array(
'%' => array(
'min' => 0,
'max' => 100,
),
),
'default' => array(
'unit' => '%',
'size' => 50,
),
) );
$this->end_controls_section();
$this->start_controls_section( 'pixvisualize_container_style_section', array(
'label' => __( 'Container', 'pixvisualize' ),
'tab' => Controls_Manager::TAB_STYLE,
) );
$this->add_control( 'pixvisualize_container_shadow_heading', array(
'type' => Controls_Manager::HEADING,
'label' => __( 'Container Shadow', 'pixvisualize' ),
) );
$this->add_control( 'container_shadow', array(
'label' => __( 'Container Shadow', 'pixvisualize' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'no',
) );
$this->add_control( 'pixvisualize_container_border_heading', array(
'type' => Controls_Manager::HEADING,
'label' => __( 'Container Border', 'pixvisualize' ),
) );
$this->add_control( 'show_border', array(
'label' => __( 'Show Border', 'pixvisualize' ),
'description' => ( $is_pro ? __( 'Show the default 2px solid gray border (a custom width below overrides it).', 'pixvisualize' ) : __( 'FREE: show a default 2px solid gray border.', 'pixvisualize' ) ),
'type' => Controls_Manager::SWITCHER,
'default' => 'no',
) );
$this->add_control( 'rounded_corners', array(
'label' => __( 'Rounded Corners', 'pixvisualize' ),
'description' => ( $is_pro ? __( 'Round all corners with the default 16px radius (custom radii below override it).', 'pixvisualize' ) : __( 'FREE: round all corners with the default 16px radius.', 'pixvisualize' ) ),
'type' => Controls_Manager::SWITCHER,
'default' => 'no',
) );
$this->end_controls_section();
$this->end_controls_section();
// Upsell in-context (pattern officiel Freemius : visible en free,
// absent en premium — runtime, PAS de marker premium-only ici).
if ( function_exists( 'pixvisualize_fs' ) && pixvisualize_fs()->is_not_paying() ) {
$this->start_controls_section( 'pixvisualize_upsell_section', array(
'label' => __( 'PixVisualize Pro', 'pixvisualize' ),
) );
$this->add_control( 'pixvisualize_upsell_text', array(
'type' => Controls_Manager::RAW_HTML,
'raw' => $this->renderUpsellHtml(),
'content_classes' => 'pixvisualize-upsell',
) );
$this->end_controls_section();
}
}
/**
* Pro upsell card (Free only, upgrade URL via Freemius).
*/
private function renderUpsellHtml() {
$url = ( function_exists( 'pixvisualize_fs' ) ? esc_url( pixvisualize_fs()->get_upgrade_url() ) : '#' );
$demo_url = ( function_exists( 'pixvisualize_get_demo_url' ) ? esc_url( pixvisualize_get_demo_url() ) : 'https://i-naridigit.com/demo/pixvisualize/' );
$features = array(
__( 'Advanced Elementor widget with full styling & live preview', 'pixvisualize' ),
__( 'Advanced Gutenberg controls', 'pixvisualize' ),
__( 'WooCommerce product gallery integration', 'pixvisualize' ),
__( 'Generate, save, edit & reuse shortcodes', 'pixvisualize' ),
__( 'Hover, Auto-Scan & Magnifier interactions', 'pixvisualize' ),
__( 'Vertical orientation', 'pixvisualize' ),
__( 'Responsive controls for every breakpoint', 'pixvisualize' ),
__( 'Custom handles & SVG icons', 'pixvisualize' ),
__( 'Advanced separator & handle styling', 'pixvisualize' ),
__( 'Fully customizable Before/After labels', 'pixvisualize' ),
__( 'Custom captions with advanced styling', 'pixvisualize' ),
__( 'Advanced borders, shadows & corner radius controls', 'pixvisualize' )
);
$list = '';
foreach ( $features as $feature ) {
$list .= '
•' . $feature . '';
}
return '' . '
' . esc_html__( 'Unlock advanced features to take your comparisons further:', 'pixvisualize' ) . '
' . '
' . '
' . '
';
}
/**
* Shared "Aspect Ratio" options used by the responsive ratio selects.
* The first (empty) option means "inherit the desktop ratio".
*/
protected function responsive_ratio_options() {
return array(
'' => __( 'Inherit (desktop)', 'pixvisualize' ),
'21/9' => '21 / 9',
'2/1' => '2 / 1',
'16/9' => '16 / 9',
'3/2' => '3 / 2',
'4/3' => '4 / 3',
'1/1' => '1 / 1',
'3/4' => '3 / 4',
'2/3' => '2 / 3',
'9/16' => '9 / 16',
'1/2' => '1 / 2',
'9/21' => '9 / 21',
);
}
/**
* Orientation options. Vertical is Pro : stripped from the free build
* (marker) and hidden at runtime without a licence.
*/
protected function orientation_options() {
$options = array(
'horizontal' => __( 'Horizontal', 'pixvisualize' ),
);
return $options;
}
/**
* Shared label position options.
*/
protected function label_position_options() {
return array(
'top-left' => __( 'Top Left', 'pixvisualize' ),
'top-right' => __( 'Top Right', 'pixvisualize' ),
'bottom-left' => __( 'Bottom Left', 'pixvisualize' ),
'bottom-right' => __( 'Bottom Right', 'pixvisualize' ),
);
}
protected function render() {
$settings = $this->get_settings_for_display();
$is_pro = function_exists( 'pixvisualize_can_use_pro' ) && pixvisualize_can_use_pro();
/* Content */
$beforeImage = ( isset( $settings['before_image']['url'] ) ? $settings['before_image']['url'] : '' );
$afterImage = ( isset( $settings['after_image']['url'] ) ? $settings['after_image']['url'] : '' );
$beforeLabel = ( !empty( $settings['before_label'] ) ? $settings['before_label'] : __( 'Before', 'pixvisualize' ) );
$afterLabel = ( !empty( $settings['after_label'] ) ? $settings['after_label'] : __( 'After', 'pixvisualize' ) );
$showLabels = 'yes' === ($settings['show_labels'] ?? 'yes');
$orientation = 'horizontal';
$aspectRatio = ( !empty( $settings['aspect_ratio'] ) ? $settings['aspect_ratio'] : '16/9' );
$height = ( isset( $settings['height']['size'] ) ? (int) $settings['height']['size'] : 450 );
$fullWidth = 'yes' === ($settings['full_width'] ?? 'no');
$initialPosition = ( isset( $settings['initial_position']['size'] ) ? (int) $settings['initial_position']['size'] : 50 );
$handleStyle = ( !empty( $settings['handle_style'] ) ? $settings['handle_style'] : 'circle' );
$handleIconSvg = ( !empty( $settings['handle_icon_svg'] ) ? $settings['handle_icon_svg'] : '' );
$handleSize = ( isset( $settings['handle_size']['size'] ) ? (int) $settings['handle_size']['size'] : 24 );
$handleSize = max( 16, min( 64, $handleSize ) );
// Legacy handle_background kept for backward compatibility (old
// content): new content leaves it empty so the disc follows the
// single synchronized separator_color control.
$handleBackground = ( !empty( $settings['handle_background'] ) ? $settings['handle_background'] : '' );
$handleArrowColor = ( !empty( $settings['handle_arrow_color'] ) ? $settings['handle_arrow_color'] : '#666666' );
$handlePadding = ( isset( $settings['handle_padding']['size'] ) ? (int) $settings['handle_padding']['size'] : 0 );
$handlePadding = max( 0, min( 20, $handlePadding ) );
$labelPositionBefore = ( !empty( $settings['label_position_before'] ) ? $settings['label_position_before'] : 'top-left' );
$labelPositionAfter = ( !empty( $settings['label_position_after'] ) ? $settings['label_position_after'] : 'top-right' );
$labelBackground = ( !empty( $settings['label_background'] ) ? $settings['label_background'] : '#00000080' );
$labelColor = ( !empty( $settings['label_color'] ) ? $settings['label_color'] : '#ffffff' );
$labelFontSize = ( isset( $settings['label_font_size']['size'] ) ? (int) $settings['label_font_size']['size'] : 12 );
$labelFontWeight = ( isset( $settings['label_font_weight']['size'] ) ? (int) $settings['label_font_weight']['size'] : 600 );
$labelPaddingX = ( isset( $settings['label_padding_x']['size'] ) ? (int) $settings['label_padding_x']['size'] : 10 );
$labelPaddingY = ( isset( $settings['label_padding_y']['size'] ) ? (int) $settings['label_padding_y']['size'] : 5 );
$labelBorderRadius = ( isset( $settings['label_border_radius']['size'] ) ? (int) $settings['label_border_radius']['size'] : 4 );
$showCaption = 'yes' === ($settings['show_caption'] ?? 'yes');
$captionTitle = ( !empty( $settings['caption_title'] ) ? $settings['caption_title'] : '' );
$captionText = ( !empty( $settings['caption_text'] ) ? $settings['caption_text'] : '' );
$captionBg = ( !empty( $settings['caption_bg'] ) ? $settings['caption_bg'] : '#ffffff' );
$captionTitleColor = ( !empty( $settings['caption_title_color'] ) ? $settings['caption_title_color'] : '#1e293b' );
$captionTextColor = ( !empty( $settings['caption_text_color'] ) ? $settings['caption_text_color'] : '#64748b' );
$captionAlignment = ( !empty( $settings['caption_alignment'] ) ? $settings['caption_alignment'] : 'center' );
if ( !in_array( $captionAlignment, array('left', 'center', 'right'), true ) ) {
$captionAlignment = 'center';
}
$captionTitleSize = ( isset( $settings['caption_title_size']['size'] ) ? (int) $settings['caption_title_size']['size'] : 22 );
$captionTextSize = ( isset( $settings['caption_text_size']['size'] ) ? (int) $settings['caption_text_size']['size'] : 15 );
// Legacy uniforme (widgets enregistrés avant la séparation X/Y).
$legacyCaptionPadding = ( isset( $settings['caption_padding']['size'] ) && '' !== $settings['caption_padding']['size'] ? (int) $settings['caption_padding']['size'] : 16 );
$captionPaddingX = ( isset( $settings['caption_padding_x']['size'] ) && '' !== $settings['caption_padding_x']['size'] ? (int) $settings['caption_padding_x']['size'] : $legacyCaptionPadding );
$captionPaddingY = ( isset( $settings['caption_padding_y']['size'] ) && '' !== $settings['caption_padding_y']['size'] ? (int) $settings['caption_padding_y']['size'] : $legacyCaptionPadding );
$separatorThickness = ( isset( $settings['separator_thickness']['size'] ) ? (int) $settings['separator_thickness']['size'] : 2 );
$separatorColor = ( !empty( $settings['separator_color'] ) ? $settings['separator_color'] : '#ffffff' );
$separatorShadow = 'yes' === ($settings['separator_shadow'] ?? 'yes');
$containerShadow = 'yes' === ($settings['container_shadow'] ?? 'no');
$containerShadowColor = ( !empty( $settings['container_shadow_color'] ) ? $settings['container_shadow_color'] : '#000000' );
$containerShadowHorz = ( isset( $settings['container_shadow_horizontal']['size'] ) ? (int) $settings['container_shadow_horizontal']['size'] : 0 );
$containerShadowVertical = ( isset( $settings['container_shadow_vertical']['size'] ) ? (int) $settings['container_shadow_vertical']['size'] : 8 );
$containerShadowBlur = ( isset( $settings['container_shadow_blur']['size'] ) ? (int) $settings['container_shadow_blur']['size'] : 20 );
$containerShadowSpread = ( isset( $settings['container_shadow_spread']['size'] ) ? (int) $settings['container_shadow_spread']['size'] : 0 );
$containerShadowOpacity = ( isset( $settings['container_shadow_opacity']['size'] ) ? (int) $settings['container_shadow_opacity']['size'] : 35 );
$hoverMode = 'yes' === ($settings['hover_mode'] ?? 'no');
$autoScan = 'yes' === ($settings['auto_scan'] ?? 'no');
$autoScanDuration = ( isset( $settings['auto_scan_duration']['size'] ) ? (int) $settings['auto_scan_duration']['size'] : 2500 );
$magnifier = 'yes' === ($settings['magnifier'] ?? 'no');
$magnifierZoom = ( isset( $settings['magnifier_zoom']['size'] ) ? (float) $settings['magnifier_zoom']['size'] : 2 );
$tabletPosition = ( isset( $settings['tablet_position']['size'] ) ? (int) $settings['tablet_position']['size'] : 50 );
$mobilePosition = ( isset( $settings['mobile_position']['size'] ) ? (int) $settings['mobile_position']['size'] : 50 );
$tabletRatio = ( !empty( $settings['tablet_ratio'] ) ? $settings['tablet_ratio'] : '' );
$mobileRatio = ( !empty( $settings['mobile_ratio'] ) ? $settings['mobile_ratio'] : '' );
/* Style */
$alignment = ( !empty( $settings['align_horizontal'] ) ? $settings['align_horizontal'] : 'center' );
$wrapperStyle = '';
$wrapperMargin = ( $is_pro && isset( $settings['wrapper_margin'] ) ? $settings['wrapper_margin'] : array() );
if ( !empty( $wrapperMargin ) && isset( $wrapperMargin['top'] ) ) {
$unit = ( !empty( $wrapperMargin['unit'] ) ? $wrapperMargin['unit'] : 'px' );
$top = ( '' !== $wrapperMargin['top'] ? $wrapperMargin['top'] . $unit : '0' );
$right = ( '' !== $wrapperMargin['right'] ? $wrapperMargin['right'] . $unit : '0' );
$bottom = ( '' !== $wrapperMargin['bottom'] ? $wrapperMargin['bottom'] . $unit : '0' );
$left = ( '' !== $wrapperMargin['left'] ? $wrapperMargin['left'] . $unit : '0' );
$wrapperStyle .= "margin: {$top} {$right} {$bottom} {$left};";
} elseif ( !$fullWidth ) {
$alignMargin = ( 'left' === $alignment ? '0 auto 0 0' : (( 'right' === $alignment ? '0 0 0 auto' : '0 auto' )) );
$wrapperStyle .= "margin: {$alignMargin};";
}
$borderWidth = ( isset( $settings['border_width']['size'] ) ? (int) $settings['border_width']['size'] : 0 );
// FREE toggles (checkboxes): default 2px solid gray border, default 16px radius.
$showBorder = 'yes' === ($settings['show_border'] ?? 'no');
$roundedCorners = 'yes' === ($settings['rounded_corners'] ?? 'no');
$borderColor = ( !empty( $settings['border_color'] ) ? $settings['border_color'] : '#000000' );
$borderStyle = ( !empty( $settings['border_style'] ) ? $settings['border_style'] : 'solid' );
$borderRadiusTL = ( isset( $settings['border_radius_tl']['size'] ) ? (int) $settings['border_radius_tl']['size'] : 0 );
$borderRadiusTR = ( isset( $settings['border_radius_tr']['size'] ) ? (int) $settings['border_radius_tr']['size'] : 0 );
$borderRadiusBR = ( isset( $settings['border_radius_br']['size'] ) ? (int) $settings['border_radius_br']['size'] : 0 );
$borderRadiusBL = ( isset( $settings['border_radius_bl']['size'] ) ? (int) $settings['border_radius_bl']['size'] : 0 );
$this->enqueue_block_assets();
$blockId = 'pixvisualize-block-' . uniqid();
?>
data-before-image=""
data-after-image=""
data-initial-position=""
data-before-label=""
data-after-label=""
data-show-labels=""
data-aspect-ratio=""
data-height=""
data-full-width=""
data-orientation=""
data-container-shadow=""
data-container-border=""
data-container-rounded=""
data-alignment=""
data-caption-title=""
data-caption-text=""
data-show-caption=""
id=""
>
<#
var beforeImage = ( settings.before_image && settings.before_image.url ) ? settings.before_image.url : '';
var afterImage = ( settings.after_image && settings.after_image.url ) ? settings.after_image.url : '';
var initialPosition = ( settings.initial_position && settings.initial_position.size ) ? settings.initial_position.size : 50;
var height = ( settings.height && settings.height.size ) ? settings.height.size : 450;
var beforeLabel = settings.before_label || 'Before';
var afterLabel = settings.after_label || 'After';
var showLabels = 'yes' === settings.show_labels;
var aspectRatio = settings.aspect_ratio || '16/9';
var fullWidth = 'yes' === settings.full_width;
var orientation = 'horizontal';
var handleStyle = 'circle';
var handleIconSvg = '';
var handleSize = 24;
var labelPositionBefore = 'top-left';
var labelPositionAfter = 'top-right';
var labelBackground = '#00000080';
var labelColor = '#ffffff';
var labelFontSize = 12;
var labelFontWeight = 600;
var labelPaddingX = 10;
var labelPaddingY = 5;
var labelBorderRadius = 4;
var separatorThickness = 2;
var separatorColor = '#ffffff';
var separatorShadow = true;
var containerShadow = 'yes' === settings.container_shadow;
var containerShadowColor = '#000000';
var containerShadowHorizontal = 0;
var containerShadowVertical = 8;
var containerShadowBlur = 20;
var containerShadowSpread = 0;
var containerShadowOpacity = 35;
var shadowAlpha = Math.max( 0, Math.min( 1, containerShadowOpacity / 100 ) );
var shadowHex = containerShadowColor.trim().match( /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/ );
var shadowRgb = 'rgba(0, 0, 0, ' + shadowAlpha + ')';
if ( shadowHex ) {
var hex = shadowHex[ 1 ];
if ( 3 === hex.length ) {
hex = hex.split( '' ).map( function ( ch ) { return ch + ch; } ).join( '' );
}
var hr = parseInt( hex.substr( 0, 2 ), 16 );
var hg = parseInt( hex.substr( 2, 2 ), 16 );
var hb = parseInt( hex.substr( 4, 2 ), 16 );
shadowRgb = 'rgba(' + hr + ', ' + hg + ', ' + hb + ', ' + shadowAlpha + ')';
}
var containerShadowStyle = containerShadow ? ('box-shadow: ' + containerShadowHorizontal + 'px ' + containerShadowVertical + 'px ' + containerShadowBlur + 'px ' + containerShadowSpread + 'px ' + shadowRgb + ';') : '';
var borderWidth = 0;
var borderColor = '#000000';
var borderStyle = 'solid';
var borderRadiusTL = 0;
var borderRadiusTR = 0;
var borderRadiusBR = 0;
var borderRadiusBL = 0;
// FREE toggles (switchers): default 2px solid gray border, default 16px radius.
var showBorder = 'yes' === settings.show_border;
var roundedCorners = 'yes' === settings.rounded_corners;
var effBorderWidth = borderWidth > 0 ? borderWidth : ( showBorder ? 2 : 0 );
var effBorderColor = borderWidth > 0 ? borderColor : '#E5E7EB';
var effBorderStyle = borderWidth > 0 ? borderStyle : 'solid';
var effRadiusTL = borderRadiusTL > 0 ? borderRadiusTL : ( roundedCorners ? 16 : 0 );
var effRadiusTR = borderRadiusTR > 0 ? borderRadiusTR : ( roundedCorners ? 16 : 0 );
var effRadiusBR = borderRadiusBR > 0 ? borderRadiusBR : ( roundedCorners ? 16 : 0 );
var effRadiusBL = borderRadiusBL > 0 ? borderRadiusBL : ( roundedCorners ? 16 : 0 );
var borderStyleStr = effBorderWidth > 0 ? ('border: ' + effBorderWidth + 'px ' + effBorderStyle + ' ' + effBorderColor + ';') : '';
var borderRadiusStyle = 'border-radius: ' + effRadiusTL + 'px ' + effRadiusTR + 'px ' + effRadiusBR + 'px ' + effRadiusBL + 'px;';
var isTransparentHandle = 'transparent' === handleStyle || 'arrows' === handleStyle;
// Single synchronized control: bar + disc share separatorColor.
// Legacy handle_background still wins when set (old content).
var handleBgSetting = '';
var discBg = handleBgSetting || ( isTransparentHandle ? 'transparent' : separatorColor );
var handlePaddingRaw = 0;
var handlePadding = Math.min( 20, Math.max( 0, handlePaddingRaw ) );
var iconBg = discBg;
var iconColor = '#666666';
var groupedFilter = separatorShadow ? 'filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.6));' : '';
// Inner spacing: on the icon box for custom SVG (glued to the
// edge), on the glyph box otherwise. Zero keeps history.
var isCustomIcon = 'custom' === handleStyle && handleIconSvg;
var iconPadding = isCustomIcon ? handlePadding : 0;
var glyphPadding = isCustomIcon ? 0 : handlePadding;
var isVertical = 'vertical' === orientation;
var ratioParts = aspectRatio.split('/');
var ratioW = parseFloat( ratioParts[0] ) || 16;
var ratioH = parseFloat( ratioParts[1] ) || 9;
var clampedHeight = Math.min( 2000, Math.max( 100, height ) );
var boxWidth = Math.round( clampedHeight * ratioW / ratioH );
// Contain layout: fit inside the parent (width AND fixed height)
// with the ratio respected — 21/9 fills the width, 9/21 fills
// the height. Full width is useless with a fixed parent.
// NOTE: no fixed height in the style below on purpose. Setting
// width + height + aspect-ratio together over-constrains CSS
// (aspect-ratio is then ignored) and wide ratios break in narrow
// columns. Width + aspect-ratio lets the browser derive the
// contained height; the preview script caps it when the parent
// has a fixed height (same rule as the front view.js).
var containerWidth = 'min(100%, ' + boxWidth + 'px)';
var containerMargin = '0 auto';
var hasImage = beforeImage || afterImage;
var handleClass = isVertical
? 'pixvisualize-elementor-preview__handle pixvisualize-elementor-preview__handle--vertical'
: 'pixvisualize-elementor-preview__handle pixvisualize-elementor-preview__handle--horizontal';
// Caption + card look (mirrors the front-end CompareSlider rule:
// with a caption the container border/shadow wrap the whole card,
// without it they stay on the slider alone).
var showCaption = 'yes' === settings.show_caption;
var captionTitle = settings.caption_title || '';
var captionText = settings.caption_text || '';
var captionBg = '#ffffff';
var captionTitleColor = '#1e293b';
var captionTextColor = '#64748b';
var captionAlignment = 'center';
var captionTitleSize = 22;
var captionTextSize = 15;
var legacyCaptionPadding = 16;
var captionPaddingX = 16;
var captionPaddingY = 16;
var captionVisible = showCaption && ( captionTitle || captionText );
var cardShadow = captionVisible && containerShadow;
// Card look: with a caption the container bottom corners move to
// the caption (the slider bottom stays square).
var captionBottomBR = effRadiusBR;
var captionBottomBL = effRadiusBL;
var boxWidthAttr = captionVisible ? 'width: 100%; margin: 0;' : ( 'width: ' + containerWidth + '; margin: ' + containerMargin + ';' );
var boxShadowAttr = cardShadow ? '' : containerShadowStyle;
var boxBorderAttr = borderStyleStr;
var boxRadiusAttr = borderRadiusStyle;
if ( captionVisible ) {
boxBorderAttr = effBorderWidth > 0
? ( 'border-top: ' + effBorderWidth + 'px ' + effBorderStyle + ' ' + effBorderColor + '; border-left: ' + effBorderWidth + 'px ' + effBorderStyle + ' ' + effBorderColor + '; border-right: ' + effBorderWidth + 'px ' + effBorderStyle + ' ' + effBorderColor + ';' )
: '';
boxRadiusAttr = 'border-radius: ' + effRadiusTL + 'px ' + effRadiusTR + 'px 0 0;';
}
var boxHeightAttr = hasImage ? ( 'height: ' + clampedHeight + 'px;' ) : '';
// boxHeightAttr is kept for reference only: it is NOT printed in
// the preview style (see contain NOTE above). The effective height
// is exposed via data-height for the preview script.
var cardAttr = captionVisible
? ( 'width: ' + containerWidth + '; margin: ' + containerMargin + '; max-width: 100%; box-sizing: border-box;' + ( cardShadow ? ( ' ' + containerShadowStyle + ' border-radius: ' + effRadiusTL + 'px ' + effRadiusTR + 'px ' + captionBottomBR + 'px ' + captionBottomBL + 'px;' ) : '' ) )
: '';
var captionBoxAttr = 'width: 100%; max-width: 100%; margin: 0; box-sizing: border-box; background-color: ' + captionBg + '; text-align: ' + captionAlignment + '; padding: ' + captionPaddingY + 'px ' + captionPaddingX + 'px; border-radius: 0 0 ' + captionBottomBR + 'px ' + captionBottomBL + 'px;' + ( effBorderWidth > 0
? ( ' border-left: ' + effBorderWidth + 'px ' + effBorderStyle + ' ' + effBorderColor + '; border-right: ' + effBorderWidth + 'px ' + effBorderStyle + ' ' + effBorderColor + '; border-bottom: ' + effBorderWidth + 'px ' + effBorderStyle + ' ' + effBorderColor + ';' )
: ' border-top: 1px solid #e5e7eb;' );
var captionTitleAttr = 'color: ' + captionTitleColor + '; font-size: ' + captionTitleSize + 'px; font-weight: 700; line-height: 1.25; margin: ' + ( captionText ? '0 0 4px' : '0' ) + ';';
var captionTextAttr = 'color: ' + captionTextColor + '; font-size: ' + captionTextSize + 'px; line-height: 1.5; margin: 0;';
// Handle icon (same geometry as the front-end Handle component:
// 24px circle, 20px chevrons, 34px double arrows).
var isArrowsHandle = 'arrows' === handleStyle;
var arrowInner = Math.max( 12, Math.round( handleSize * 0.65 ) );
var arrowSize = isArrowsHandle ? handleSize + 10 : arrowInner;
var arrowPaths = '';
if ( isArrowsHandle ) {
arrowPaths = isVertical
? ''
: '';
} else {
arrowPaths = isVertical
? ''
: '';
}
var lineStyle = isVertical
? 'height: ' + separatorThickness + 'px; width: 100%;'
: 'width: ' + separatorThickness + 'px; height: 100%;';
lineStyle += ' background-color: ' + separatorColor + '; box-shadow: none;';
var beforePosStyle = 'top: 12px; left: 12px;';
if ( 'top-right' === labelPositionBefore ) { beforePosStyle = 'top: 12px; right: 12px;'; }
if ( 'bottom-left' === labelPositionBefore ) { beforePosStyle = 'bottom: 12px; left: 12px;'; }
if ( 'bottom-right' === labelPositionBefore ) { beforePosStyle = 'bottom: 12px; right: 12px;'; }
var afterPosStyle = 'top: 12px; right: 12px;';
if ( 'top-left' === labelPositionAfter ) { afterPosStyle = 'top: 12px; left: 12px;'; }
if ( 'bottom-left' === labelPositionAfter ) { afterPosStyle = 'bottom: 12px; left: 12px;'; }
if ( 'bottom-right' === labelPositionAfter ) { afterPosStyle = 'bottom: 12px; right: 12px;'; }
var labelStyle = 'background-color: ' + labelBackground + '; color: ' + labelColor + '; font-size: ' + labelFontSize + 'px; font-weight: ' + labelFontWeight + '; padding: ' + labelPaddingY + 'px ' + labelPaddingX + 'px; border-radius: ' + labelBorderRadius + 'px;';
#>
<# if ( captionVisible ) { #><# } #>
style="{{ boxWidthAttr }} aspect-ratio: {{ aspectRatio }}; --pc-pos: {{ initialPosition }}%; {{ boxShadowAttr }} {{ boxBorderAttr }} {{ boxRadiusAttr }}"
>
<# if ( afterImage ) { #>

<# } #>
<# if ( beforeImage ) { #>

<# } #>
<# if ( hasImage ) { #>
<# if ( 'line' !== handleStyle ) { #>
<# if ( 'custom' === handleStyle && handleIconSvg ) { #>
{{{ handleIconSvg }}}
<# } else { #>
<# } #>
<# } #>
<# } #>
<# if ( showLabels ) { #>
<# if ( beforeImage ) { #>
{{ beforeLabel }}
<# } #>
<# if ( afterImage ) { #>
{{ afterLabel }}
<# } #>
<# } #>
<# if ( ! hasImage ) { #>
<# } #>
<# if ( captionVisible ) { #>
<# if ( captionTitle ) { #>
{{ captionTitle }}
<# } #>
<# if ( captionText ) { #>
{{ captionText }}
<# } #>
<# } #>