Elementor #3545

// === V.S.O.P. Wall Shortcode === //
function vsop_wall_shortcode($atts){
    $atts = shortcode_atts([
        ‘per_page’ => 8,
        ‘columns’ => 3,
        ‘progress_color’ => ‘#000000’,
        ‘card_bg’ => ‘#ffffff’,
    ], $atts, ‘vsop_wall’);
    ob_start(); ?>
    <div class=”vsop-elementor-container”>
        <div class=”vsop-search-container” style=”text-align:center;margin-bottom:30px;”>
            <input type=”text” id=”vsop-search-input” placeholder=”Search member by name…” style=”padding:10px;width:250px;border-radius:5px;border:1px solid #ccc;”>
            <button id=”vsop-search-btn” style=”background:#000;color:#fff;padding:10px 15px;border:none;border-radius:5px;”>Search</button>
            <button id=”vsop-random-btn” style=”margin-left:10px;background:#555;color:#fff;padding:10px 15px;border-radius:5px;”>🎲 Random</button>
        </div>
        <div id=”vsop-grid” class=”vsop-grid” style=”display:grid;grid-template-columns:repeat(<?php echo esc_attr($atts[‘columns’]); ?>,1fr);gap:20px;”></div>
        <div style=”text-align:center;margin-top:20px;”>
            <button id=”vsop-load-more” style=”background:#000;color:#fff;padding:10px 25px;border:none;border-radius:5px;display:none;”>Load More</button>
        </div>
    </div>
    <script>
    jQuery(document).ready(function($){
        let page=1,loading=false,lastSearch=”,randomMode=false;
        function loadMembers(reset=false){
            if(loading) return; loading=true;
            if(reset){ $(‘#vsop-grid’).html(”); page=1; }
            $(‘#vsop-load-more’).hide();
            $.ajax({
                url: ‘<?php echo admin_url(‘admin-ajax.php’); ?>’,
                type: ‘GET’,
                data: { action:’vsop_load_members’, page:page, search:lastSearch, random:randomMode?1:0 },
                success: function(res){
                    if(reset){ $(‘#vsop-grid’).html(res); } else { $(‘#vsop-grid’).append(res); }
                    if($.trim(res).length>0){ $(‘#vsop-load-more’).show(); page++; }
                    else{ $(‘#vsop-load-more’).hide(); }
                    animateProgressBars();
                    loading=false;
                }
            });
        }
        $(‘#vsop-search-btn’).on(‘click’, function(){ lastSearch=$(‘#vsop-search-input’).val(); randomMode=false; loadMembers(true); });
        $(‘#vsop-random-btn’).on(‘click’, function(){ lastSearch=”; randomMode=true; loadMembers(true); });
        $(‘#vsop-load-more’).on(‘click’, function(){ loadMembers(); });
        $(window).on(‘scroll’, function(){ if($(window).scrollTop() + $(window).height() >= $(document).height() – 200) loadMembers(); });
        function animateProgressBars(){
            $(‘.vsop-progress-fill’).each(function(){
                var bar=$(this),percent=bar.data(‘percent’);
                if(bar.visible(true) && !bar.hasClass(‘animated’)){
                    bar.addClass(‘animated’); bar.animate({width:percent+’%’},1200);
                }
            });
        }
        $.fn.visible=function(partial){ var $t=$(this),$w=$(window),viewTop=$w.scrollTop(),viewBottom=viewTop+$w.height(),_top=$t.offset().top,_bottom=_top+$t.height(),compareTop=partial===true?_bottom:_top,compareBottom=partial===true?_top:_bottom; return ((compareBottom<=viewBottom)&&(compareTop>=viewTop)); }
    });
    </script>
    <style>
    .vsop-grid .vsop-card{opacity:0;transform:translateY(20px);transition:all 0.6s ease;background:<?php echo esc_attr($atts[‘card_bg’]); ?>;border-radius:10px;padding:15px;text-align:center;box-shadow:0 2px 5px rgba(0,0,0,0.1);}
    .vsop-grid .vsop-card.animated{opacity:1;transform:translateY(0);}
    .vsop-progress-fill{background:<?php echo esc_attr($atts[‘progress_color’]); ?>;height:10px;width:0;border-radius:5px;}
    </style>
    <?php
    return ob_get_clean();
}
add_shortcode(‘vsop_wall’,’vsop_wall_shortcode’);
// === AJAX loader backend (reuse from previous code) === //
add_action(‘wp_ajax_vsop_load_members’,’vsop_load_members’);
add_action(‘wp_ajax_nopriv_vsop_load_members’,’vsop_load_members’);
function vsop_load_members(){
    $paged=isset($_GET[‘page’])?intval($_GET[‘page’]):1;
    $search=isset($_GET[‘search’])?sanitize_text_field($_GET[‘search’]):”;
    $random=isset($_GET[‘random’])?intval($_GET[‘random’]):0;
    $per_page=8; $offset=($paged-1)*$per_page;
    $args=[‘role’=>’subscriber’,’meta_key’=>’vsop_approved’,’meta_value’=>’1′,’number’=>$per_page,’offset’=>$offset];
    if($search!==”){$args[‘search’]=’*’.$search.’*’;$args[‘search_columns’]=[‘display_name’];}
    if($random){$args[‘orderby’]=’rand’;$args[‘number’]=1;}
    $users=get_users($args);
    foreach($users as $user){
        $profile_image=get_user_meta($user->ID,’profile_image’,true);
        $bio=get_user_meta($user->ID,’description’,true);
        $goal=floatval(get_user_meta($user->ID,’vsop_goal_amount’,true));
        $raised=floatval(get_user_meta($user->ID,’vsop_amount_raised’,true));
        $percent=($goal>0)?min(100,($raised/$goal)*100):0;
        $donate_link=site_url(‘/support/?user_id=’.$user->ID);
        echo ‘<div class=”vsop-card”>’;
        echo ‘<img src=”‘.esc_url($profile_image).'” alt=”‘.esc_attr($user->display_name).'” style=”width:100px;height:100px;border-radius:50%;object-fit:cover;”>’;
        echo ‘<h3>’.esc_html($user->display_name).'</h3>’;
        echo ‘<p>’.esc_html($bio).'</p>’;
        echo ‘<div class=”vsop-progress-bar” style=”background:#eee;border-radius:10px;overflow:hidden;margin:10px 0;”>’;
        echo ‘<div class=”vsop-progress-fill” data-percent=”‘.$percent.'”></div></div>’;
        echo ‘<small><strong>$’.number_format($raised,2).'</strong> raised of $’.number_format($goal,2).'</small><br><br>’;
        echo ‘<a href=”‘.esc_url($donate_link).‘” class=”button” style=”background:#000;color:#fff;padding:10px 20px;border-radius:5px;text-decoration:none;”>Support</a>’;
        echo ‘</div>’;
    }
    wp_die();
}