Join our Facebook Group
ScriptsTribe • Sngine • Community
Join

Fóra

The great place to discuss topics with other users

User LeaderBoard + Badges

'
Join the Conversation Odeslat odpověď
Claudiu Ditzy
Member
Joined: 2025-01-05 03:01:46
2025-05-29 11:28:06

Got this error:

Parse error: syntax error, unexpected token "public", expecting end of file in C:\xampp\htdocs\sngine404\includes\class-user.php on line 24344

Jane Marcia
Admin
Joined: 2025-05-17 02:14:16
2025-05-30 12:21:38

I leave here for free my leaderboard + Badge

 

class-user.php

 

 // LEADERBOARD
  public function getLeaderboardData($user_id) {
    global $db;

    // Fetch top 100 users with activity stats
    $result = $db->query("
        SELECT user_id, user_name, user_points, user_registered,
               RANK() OVER (ORDER BY user_points DESC) AS user_rank
        FROM users
        WHERE user_points > 0
        ORDER BY user_points DESC
        LIMIT 100
    ") or _error(SQL_ERROR_THROWEN);

    $leaderboard = [];
    while ($row = $result->fetch_assoc()) {
        $row['badges'] = $this->assignBadges($row);
        $leaderboard[] = $row;
    }

    // Identify current user and rank
    $user_index = array_search($user_id, array_column($leaderboard, 'user_id'));
    $current_user = null;
    $next_user = null;

    if ($user_index !== false) {
        $current_user = $leaderboard[$user_index];
        $next_user = $user_index > 0 ? $leaderboard[$user_index - 1] : null;
    } else {
        // User is not in top 100
        $user_row = $db->query("
            SELECT user_id, user_name, user_points, user_registered,
                   (SELECT COUNT(*) + 1 FROM users WHERE user_points > u.user_points) AS user_rank
            FROM users u
            WHERE user_id = '{$user_id}'
            LIMIT 1
        ") or _error(SQL_ERROR_THROWEN);

        if ($user_row->num_rows > 0) {
            $current_user = $user_row->fetch_assoc();
            $current_user['badges'] = $this->assignBadges($current_user);

            // Next user
            $next_row = $db->query("
                SELECT user_name, user_points
                FROM users
                WHERE user_points > '{$current_user['user_points']}'
                ORDER BY user_points ASC
                LIMIT 1
            ") or _error(SQL_ERROR_THROWEN);

            if ($next_row->num_rows > 0) {
                $next_user = $next_row->fetch_assoc();
            }
        }
    }

    return [
        'top_users' => $leaderboard,
        'current_user' => $current_user,
        'next_user' => $next_user
    ];
}

public static function getAllBadges()
{
    $site_launch = '2025-05-24';

    return [
        [
            'key' => 'veteran',
            'label' => 'Veteran',
            'icon' => '/content/uploads/leaderboard/veteran.svg',
            'description' => 'Registered for over 1 year.',
            'requirement' => 'Account age ≥ 1 year',
            'unlocked' => function($user) {
                $years = (strtotime('now') - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
                return $years >= 1;
            },
            'level' => function($user) {
                $years = (strtotime('now') - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
                if ($years >= 3) return 'gold';
                if ($years >= 2) return 'silver';
                if ($years >= 1) return 'bronze';
                return null;
            }
        ],
        [
            'key' => 'top_points',
            'label' => 'Active Member',
            'icon' => '/content/uploads/leaderboard/contributor.svg',
            'description' => 'Earned at least 1000 points.',
            'requirement' => 'Points ≥ 1000',
            'unlocked' => function($user) {
                return $user['user_points'] >= 1000;
            },
            'level' => function($user) {
                if ($user['user_points'] >= 5000) return 'gold';
                if ($user['user_points'] >= 2500) return 'silver';
                if ($user['user_points'] >= 1000) return 'bronze';
                return null;
            }
        ],
        [
            'key' => 'early_supporter',
            'label' => 'Supporter',
            'icon' => '/content/uploads/leaderboard/early.svg',
            'description' => 'Joined in the first 3 months.',
            'requirement' => 'Registered before 2025-08-24',
            'unlocked' => function($user) use ($site_launch) {
                return strtotime($user['user_registered']) <= strtotime("$site_launch +3 months");
            },
            'level' => function($user) use ($site_launch) {
                $joined = strtotime($user['user_registered']);
                if ($joined <= strtotime("$site_launch +1 month")) return 'gold';
                if ($joined <= strtotime("$site_launch +2 months")) return 'silver';
                if ($joined <= strtotime("$site_launch +3 months")) return 'bronze';
                return null;
            }
        ]
    ];
}


public static function assignBadges($user)
{
    $all = self::getAllBadges();
    $user_badges = [];

    foreach ($all as $badge) {
        $badge_levels = [];

        if ($badge['key'] === 'top_points') {
            $badge_levels = [
                'bronze' => [
                    'requirement' => 1000,
                    'unlocked' => $user['user_points'] >= 1000,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
                'silver' => [
                    'requirement' => 2500,
                    'unlocked' => $user['user_points'] >= 2500,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
                'gold' => [
                    'requirement' => 5000,
                    'unlocked' => $user['user_points'] >= 5000,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
            ];
        }

        if ($badge['key'] === 'veteran') {
            $years = (time() - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
            $badge_levels = [
                'bronze' => [
                    'requirement' => 1,
                    'unlocked' => $years >= 1,
                    'progress' => $years,
                ],
                'silver' => [
                    'requirement' => 2,
                    'unlocked' => $years >= 2,
                    'progress' => $years,
                ],
                'gold' => [
                    'requirement' => 3,
                    'unlocked' => $years >= 3,
                    'progress' => $years,
                ],
            ];
        }

        if ($badge['key'] === 'early_supporter') {
           $registered = strtotime($user['user_registered']);
           $badge_levels = [
        'bronze' => [
            'requirement' => strtotime("2025-08-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-08-24"),
        ],
        'silver' => [
            'requirement' => strtotime("2025-07-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-07-24"),
        ],
        'gold' => [
            'requirement' => strtotime("2025-06-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-06-24"),
        ],
    ];
  }
       
        $unlocked_level = null;
        foreach (['gold', 'silver', 'bronze'] as $lvl) {
            if (!empty($badge_levels[$lvl]['unlocked'])) {
                $unlocked_level = $lvl;
                break;
            }
        }

        $user_badges[] = [
            'key' => $badge['key'],
            'label' => $badge['label'],
            'icon' => $badge['icon'],
            'description' => $badge['description'],
            'requirement' => $badge['requirement'],
            'unlocked' => call_user_func($badge['unlocked'], $user),
            'unlocked_level' => $unlocked_level,
            'levels' => $badge_levels
        ];
    }

    return $user_badges;
  }
 
 

leaderboard.php

<?php

/**
 * notifications
 *
 * @package Sngine
 * @author Zamblek
 */

// fetch bootloader
require('bootloader.php');

// user access
user_access();


// page header
page_header(__("TOP 100 ACTIVE MEMBERS"));

try {

    $leaderboard_data = $user->getLeaderboardData($user->_data['user_id']);
    $smarty->assign('leaderboard', $leaderboard_data);

    $user_badges = User::assignBadges($user->_data);
    $smarty->assign('user_badges', $user_badges);

} catch (Exception $e) {
  _error(__("Error"), $e->getMessage());
}

// page footer
page_footer('leaderboard');
 
leaderboard.tpl

<h4 class="mt-3 mb-3 text-center">{__("TOP 100 ACTIVE MEMBERS")} (<a href="{$system['system_url']}/settings/points">INFO</a>)</h4>
    

    <div class="badge-gallery">
        {foreach from=$user_badges item=badge}
    <div class="badgepp badge-container">
        {if $badge.unlocked_level}
            <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$badge.unlocked_level}.png" class="badge-iconpp" width="40" height="40" />
        {else}
            <h6 class="text-uppercase text-muted">{__("No badge unlocked")}</h6>
        {/if}

        <div class="badge-info">
            <h5 class="mb-1">{__($badge.label)}</h5>
            <p class="text-muted mb-2">{__($badge.description)}</p>

            {foreach from=$badge.levels key=level item=level_data}
                <div class="badge-level-entry badge-level-{$level} {if $level_data.unlocked}unlocked{else}locked{/if}">
                    <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$level}.png" alt="{$level}" class="me-1" width="20" height="20" />
                        <strong class="text-capitalize">{$level}</strong> – 

                    {if $badge.key == 'veteran'}
                        <strong>{$level_data.requirement} {__("years")}</strong>
                        {if !$level_data.unlocked}
                            – <strong class="text-danger">{__("Needs")} {math equation="x - y" x=$level_data.requirement y=$level_data.progress format="%.1f"} {__("years")}</strong>
                        {/if}
                    
                    {elseif $badge.key == 'early_supporter'}
                        {__("Joined before")} {$level_data.requirement|date_format:"%e %B, %Y"}
                        {if !$level_data.unlocked}
                            – <span class="text-danger">{__("Too late")}</span>
                        {/if}

                    {else}
                        (<strong>{$level_data.requirement} {__("points")}</strong>)
                        {if !$level_data.unlocked}
                            – <strong class="text-danger">{__("Needs")} {math equation="x - y" x=$level_data.requirement y=$level_data.progress|default:0 format="%d"}</strong>
                        {/if}
                    {/if}
                </div>
            {/foreach}
        </div>
    </div>
        {/foreach}
    </div>

    {if $leaderboard.current_user}
        <div class="alert alert-info mt-4">
            {__("You are ranked")} <strong>#{$leaderboard.current_user.user_rank}</strong> {__("with")} <strong>{$leaderboard.current_user.user_points}</strong> {__("points")}.
            {if $leaderboard.next_user}
                <br>
                {__("Next rank")}: <strong>{$leaderboard.next_user.user_name}</strong> {__("with")} <strong>{$leaderboard.next_user.user_points}</strong> {__("points")}.
                <br>
                {__("Needed to pass")}: <strong>{$leaderboard.next_user.user_points - $leaderboard.current_user.user_points + 1}</strong> {__("points")}
            {/if}
        </div>
    {/if}

    <div class="leaderboardp">

        {foreach $leaderboard.top_users as $user}
        <a href="{$system['system_url']}/{$user.user_name}">
        <div class="user-rowp {if $user.user_id == $leaderboard.current_user.user_id}highlightp{/if}">
            <div class="rankp">
                {if $user.user_rank == 1}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#ffb02e" d="M15.99 30c5.545 0 10.04-4.607 10.04-10.29S21.535 9.42 15.99 9.42S5.95 14.027 5.95 19.71S10.445 30 15.99 30"/><path fill="#6d4534" d="M14.076 16.041a1 1 0 0 1 1-1H16a1 1 0 0 1 1 1V23a1 1 0 1 1-2 0v-5.962a1 1 0 0 1-.924-.997"/><path fill="#fcd53f" d="M16 28.76c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/></g></svg>
                {elseif $user.user_rank == 2}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#bebebe" d="M15.96 30.001c5.545 0 10.04-4.607 10.04-10.29s-4.495-10.29-10.04-10.29s-10.04 4.607-10.04 10.29s4.495 10.29 10.04 10.29"/><path fill="#e6e6e6" d="M15.96 28.761c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/><path fill="#636363" d="M17.838 23.95h-3.97a1 1 0 0 1-.91-.58a1 1 0 0 1 .13-1.07l3.3-4.05c.26-.32.14-.66.1-.76a.64.64 0 0 0-.58-.4h-.05c-.32 0-.61.15-.8.41c-.32.45-.95.56-1.41.24a1.01 1.01 0 0 1-.24-1.41c.56-.79 1.47-1.26 2.44-1.26h.13c1.02.05 1.91.66 2.33 1.59c.43.96.29 2.05-.37 2.86l-1.95 2.4h1.84c.56 0 1.01.45 1.01 1.01s-.44 1.02-1 1.02"/></g></svg>
                {elseif $user.user_rank == 3}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#d3883e" d="M15.99 30c5.545 0 10.04-4.607 10.04-10.29S21.535 9.42 15.99 9.42S5.95 14.027 5.95 19.71S10.445 30 15.99 30"/><path fill="#f3ad61" d="M16 28.76c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/><path fill="#402a32" d="m17.402 18.275l1.048-1.81a.98.98 0 0 0 0-.977a.97.97 0 0 0-.844-.488h-3.092a.977.977 0 0 0 0 1.953h1.403l-.752 1.311a.96.96 0 0 0-.214.6c0 .54.438.977.977.977c.61 0 1.108.498 1.108 1.108s-.498 1.109-1.108 1.109c-.468 0-.885-.295-1.038-.733a.975.975 0 0 0-1.24-.59a.966.966 0 0 0-.59 1.241A3.06 3.06 0 0 0 15.928 24a3.053 3.053 0 0 0 3.05-3.05a3.07 3.07 0 0 0-1.576-2.675"/></g></svg>
                {elseif $user.user_rank >= 4}
                 {$user.user_rank}
                {/if}
            </div>
            <div class="user-infop">
                <div class="usernamep">
                    {if $user.user_id == $leaderboard.current_user.user_id}
                        <span class="badge bg-primary ms-1">{__("YOU ARE HERE")}</span>
                    {else}
                        {$user.user_name}
                    {/if}
                </div>
                <div class="badgesp">
                    {foreach $user.badges as $badge}
                        {if $badge.unlocked_level}
                            <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$badge.unlocked_level}.png" class="badge-iconpp" width="20" height="20" />
                        {/if}
                    {/foreach}
                </div>
            </div>
            <div class="pointsp">{$user.user_points}</div>
        </div>
        </a>
        {/foreach}

    </div>

 

style.css

/* LEADERBOARD PUNCTE */
.leaderboardp {
    max-width: 800px;
    margin: 0 auto;
    border-radius: 10px;
    overflow: hidden;
}

.rankp,
.pointsp {
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.user-rowp {
    box-sizing: border-box;
    display: flex;
    justify-content: space-between;
    align-items: center; /* <- This is the key change */
    background: #fff;
    padding: 15px 20px;
    border-bottom: 1px solid #ddd;
    transition: background 0.3s;
}

body.night-mode .user-rowp {
  background: #212121;
  border-bottom: 1px solid #485259;
}

.user-rowp.highlightp {
    position: relative;
    background: #ffe9f0;
}

body.night-mode .user-rowp.highlightp {
  background: #1D1D1D;
}

.user-rowp.highlightp::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 4px;
    height: 100%;
    background-color: #ff3399;
}

.rankp {
    font-weight: bold;
    min-width: 40px;
    color: #1D1D1D;
    font-size: 1.3rem;
}

body.night-mode .rankp {
   color: #f8f8f8;
}

.user-infop {
    flex-grow: 1;
    padding: 0 20px;
}

.usernamep {
    font-size: 1.1em;
    font-weight: bold;
    margin-bottom: 5px;
}

.badgesp {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
}

.badgep {
    font-size: 0.8em;
    padding: 3px 8px;
    border-radius: 12px;
    background: #ccc;
    color: #fff;
}

.badgep.unlockedp {
    background-color: #28a745;
}

.badgep.lockedp {
    background-color: #6c757d;
}

.pointsp {
    min-width: 100px;
    text-align: right;
    font-weight: bold;
    color: #28a745;
    font-size: 1.4rem;
}

.badge.lockedpp {
    opacity: 0.5;
}
.badgepp {
    padding: 5px;
    border-radius: 8px;
    display: flex;
    flex-direction: column;
    align-items: start;
    margin-bottom: 10px;
}
.badge-iconpp {
  display: inline-block;
  vertical-align: middle;
  width: 32px;
  height: 32px;
}

.locked {
  background-color: #e9ecef;
}


.badge-container {
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 15px;
    margin-bottom: 20px;
    background: #fff;
    display: flex;
    gap: 15px;
}

body.night-mode .badge-container {
  background: #212121;
  border: 1px solid #485259;
}

.badge-iconpp {
    flex-shrink: 0;
}

.badge-info {
    flex-grow: 1;
}

.badge-level-entry {
    margin-top: 5px;
}

.badge-level-entry.unlocked {
    color: #28a745;
    font-weight: bold;
}

.badge-level-entry.locked {
    color: #6c757d;
}

 

Claudiu Ditzy
Member
Joined: 2025-01-05 03:01:46
2025-05-30 16:37:21

I leave here for free my leaderboard + Badge

 

class-user.php

 

 // LEADERBOARD
  public function getLeaderboardData($user_id) {
    global $db;

    // Fetch top 100 users with activity stats
    $result = $db->query("
        SELECT user_id, user_name, user_points, user_registered,
               RANK() OVER (ORDER BY user_points DESC) AS user_rank
        FROM users
        WHERE user_points > 0
        ORDER BY user_points DESC
        LIMIT 100
    ") or _error(SQL_ERROR_THROWEN);

    $leaderboard = [];
    while ($row = $result->fetch_assoc()) {
        $row['badges'] = $this->assignBadges($row);
        $leaderboard[] = $row;
    }

    // Identify current user and rank
    $user_index = array_search($user_id, array_column($leaderboard, 'user_id'));
    $current_user = null;
    $next_user = null;

    if ($user_index !== false) {
        $current_user = $leaderboard[$user_index];
        $next_user = $user_index > 0 ? $leaderboard[$user_index - 1] : null;
    } else {
        // User is not in top 100
        $user_row = $db->query("
            SELECT user_id, user_name, user_points, user_registered,
                   (SELECT COUNT(*) + 1 FROM users WHERE user_points > u.user_points) AS user_rank
            FROM users u
            WHERE user_id = '{$user_id}'
            LIMIT 1
        ") or _error(SQL_ERROR_THROWEN);

        if ($user_row->num_rows > 0) {
            $current_user = $user_row->fetch_assoc();
            $current_user['badges'] = $this->assignBadges($current_user);

            // Next user
            $next_row = $db->query("
                SELECT user_name, user_points
                FROM users
                WHERE user_points > '{$current_user['user_points']}'
                ORDER BY user_points ASC
                LIMIT 1
            ") or _error(SQL_ERROR_THROWEN);

            if ($next_row->num_rows > 0) {
                $next_user = $next_row->fetch_assoc();
            }
        }
    }

    return [
        'top_users' => $leaderboard,
        'current_user' => $current_user,
        'next_user' => $next_user
    ];
}

public static function getAllBadges()
{
    $site_launch = '2025-05-24';

    return [
        [
            'key' => 'veteran',
            'label' => 'Veteran',
            'icon' => '/content/uploads/leaderboard/veteran.svg',
            'description' => 'Registered for over 1 year.',
            'requirement' => 'Account age ≥ 1 year',
            'unlocked' => function($user) {
                $years = (strtotime('now') - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
                return $years >= 1;
            },
            'level' => function($user) {
                $years = (strtotime('now') - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
                if ($years >= 3) return 'gold';
                if ($years >= 2) return 'silver';
                if ($years >= 1) return 'bronze';
                return null;
            }
        ],
        [
            'key' => 'top_points',
            'label' => 'Active Member',
            'icon' => '/content/uploads/leaderboard/contributor.svg',
            'description' => 'Earned at least 1000 points.',
            'requirement' => 'Points ≥ 1000',
            'unlocked' => function($user) {
                return $user['user_points'] >= 1000;
            },
            'level' => function($user) {
                if ($user['user_points'] >= 5000) return 'gold';
                if ($user['user_points'] >= 2500) return 'silver';
                if ($user['user_points'] >= 1000) return 'bronze';
                return null;
            }
        ],
        [
            'key' => 'early_supporter',
            'label' => 'Supporter',
            'icon' => '/content/uploads/leaderboard/early.svg',
            'description' => 'Joined in the first 3 months.',
            'requirement' => 'Registered before 2025-08-24',
            'unlocked' => function($user) use ($site_launch) {
                return strtotime($user['user_registered']) <= strtotime("$site_launch +3 months");
            },
            'level' => function($user) use ($site_launch) {
                $joined = strtotime($user['user_registered']);
                if ($joined <= strtotime("$site_launch +1 month")) return 'gold';
                if ($joined <= strtotime("$site_launch +2 months")) return 'silver';
                if ($joined <= strtotime("$site_launch +3 months")) return 'bronze';
                return null;
            }
        ]
    ];
}


public static function assignBadges($user)
{
    $all = self::getAllBadges();
    $user_badges = [];

    foreach ($all as $badge) {
        $badge_levels = [];

        if ($badge['key'] === 'top_points') {
            $badge_levels = [
                'bronze' => [
                    'requirement' => 1000,
                    'unlocked' => $user['user_points'] >= 1000,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
                'silver' => [
                    'requirement' => 2500,
                    'unlocked' => $user['user_points'] >= 2500,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
                'gold' => [
                    'requirement' => 5000,
                    'unlocked' => $user['user_points'] >= 5000,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
            ];
        }

        if ($badge['key'] === 'veteran') {
            $years = (time() - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
            $badge_levels = [
                'bronze' => [
                    'requirement' => 1,
                    'unlocked' => $years >= 1,
                    'progress' => $years,
                ],
                'silver' => [
                    'requirement' => 2,
                    'unlocked' => $years >= 2,
                    'progress' => $years,
                ],
                'gold' => [
                    'requirement' => 3,
                    'unlocked' => $years >= 3,
                    'progress' => $years,
                ],
            ];
        }

        if ($badge['key'] === 'early_supporter') {
           $registered = strtotime($user['user_registered']);
           $badge_levels = [
        'bronze' => [
            'requirement' => strtotime("2025-08-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-08-24"),
        ],
        'silver' => [
            'requirement' => strtotime("2025-07-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-07-24"),
        ],
        'gold' => [
            'requirement' => strtotime("2025-06-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-06-24"),
        ],
    ];
  }
       
        $unlocked_level = null;
        foreach (['gold', 'silver', 'bronze'] as $lvl) {
            if (!empty($badge_levels[$lvl]['unlocked'])) {
                $unlocked_level = $lvl;
                break;
            }
        }

        $user_badges[] = [
            'key' => $badge['key'],
            'label' => $badge['label'],
            'icon' => $badge['icon'],
            'description' => $badge['description'],
            'requirement' => $badge['requirement'],
            'unlocked' => call_user_func($badge['unlocked'], $user),
            'unlocked_level' => $unlocked_level,
            'levels' => $badge_levels
        ];
    }

    return $user_badges;
  }
 
 

leaderboard.php

<?php

/**
 * notifications
 *
 * @package Sngine
 * @author Zamblek
 */

// fetch bootloader
require('bootloader.php');

// user access
user_access();


// page header
page_header(__("TOP 100 ACTIVE MEMBERS"));

try {

    $leaderboard_data = $user->getLeaderboardData($user->_data['user_id']);
    $smarty->assign('leaderboard', $leaderboard_data);

    $user_badges = User::assignBadges($user->_data);
    $smarty->assign('user_badges', $user_badges);

} catch (Exception $e) {
  _error(__("Error"), $e->getMessage());
}

// page footer
page_footer('leaderboard');
 
leaderboard.tpl

<h4 class="mt-3 mb-3 text-center">{__("TOP 100 ACTIVE MEMBERS")} (<a href="{$system['system_url']}/settings/points">INFO</a>)</h4>
    

    <div class="badge-gallery">
        {foreach from=$user_badges item=badge}
    <div class="badgepp badge-container">
        {if $badge.unlocked_level}
            <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$badge.unlocked_level}.png" class="badge-iconpp" width="40" height="40" />
        {else}
            <h6 class="text-uppercase text-muted">{__("No badge unlocked")}</h6>
        {/if}

        <div class="badge-info">
            <h5 class="mb-1">{__($badge.label)}</h5>
            <p class="text-muted mb-2">{__($badge.description)}</p>

            {foreach from=$badge.levels key=level item=level_data}
                <div class="badge-level-entry badge-level-{$level} {if $level_data.unlocked}unlocked{else}locked{/if}">
                    <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$level}.png" alt="{$level}" class="me-1" width="20" height="20" />
                        <strong class="text-capitalize">{$level}</strong> – 

                    {if $badge.key == 'veteran'}
                        <strong>{$level_data.requirement} {__("years")}</strong>
                        {if !$level_data.unlocked}
                            – <strong class="text-danger">{__("Needs")} {math equation="x - y" x=$level_data.requirement y=$level_data.progress format="%.1f"} {__("years")}</strong>
                        {/if}
                    
                    {elseif $badge.key == 'early_supporter'}
                        {__("Joined before")} {$level_data.requirement|date_format:"%e %B, %Y"}
                        {if !$level_data.unlocked}
                            – <span class="text-danger">{__("Too late")}</span>
                        {/if}

                    {else}
                        (<strong>{$level_data.requirement} {__("points")}</strong>)
                        {if !$level_data.unlocked}
                            – <strong class="text-danger">{__("Needs")} {math equation="x - y" x=$level_data.requirement y=$level_data.progress|default:0 format="%d"}</strong>
                        {/if}
                    {/if}
                </div>
            {/foreach}
        </div>
    </div>
        {/foreach}
    </div>

    {if $leaderboard.current_user}
        <div class="alert alert-info mt-4">
            {__("You are ranked")} <strong>#{$leaderboard.current_user.user_rank}</strong> {__("with")} <strong>{$leaderboard.current_user.user_points}</strong> {__("points")}.
            {if $leaderboard.next_user}
                <br>
                {__("Next rank")}: <strong>{$leaderboard.next_user.user_name}</strong> {__("with")} <strong>{$leaderboard.next_user.user_points}</strong> {__("points")}.
                <br>
                {__("Needed to pass")}: <strong>{$leaderboard.next_user.user_points - $leaderboard.current_user.user_points + 1}</strong> {__("points")}
            {/if}
        </div>
    {/if}

    <div class="leaderboardp">

        {foreach $leaderboard.top_users as $user}
        <a href="{$system['system_url']}/{$user.user_name}">
        <div class="user-rowp {if $user.user_id == $leaderboard.current_user.user_id}highlightp{/if}">
            <div class="rankp">
                {if $user.user_rank == 1}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#ffb02e" d="M15.99 30c5.545 0 10.04-4.607 10.04-10.29S21.535 9.42 15.99 9.42S5.95 14.027 5.95 19.71S10.445 30 15.99 30"/><path fill="#6d4534" d="M14.076 16.041a1 1 0 0 1 1-1H16a1 1 0 0 1 1 1V23a1 1 0 1 1-2 0v-5.962a1 1 0 0 1-.924-.997"/><path fill="#fcd53f" d="M16 28.76c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/></g></svg>
                {elseif $user.user_rank == 2}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#bebebe" d="M15.96 30.001c5.545 0 10.04-4.607 10.04-10.29s-4.495-10.29-10.04-10.29s-10.04 4.607-10.04 10.29s4.495 10.29 10.04 10.29"/><path fill="#e6e6e6" d="M15.96 28.761c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/><path fill="#636363" d="M17.838 23.95h-3.97a1 1 0 0 1-.91-.58a1 1 0 0 1 .13-1.07l3.3-4.05c.26-.32.14-.66.1-.76a.64.64 0 0 0-.58-.4h-.05c-.32 0-.61.15-.8.41c-.32.45-.95.56-1.41.24a1.01 1.01 0 0 1-.24-1.41c.56-.79 1.47-1.26 2.44-1.26h.13c1.02.05 1.91.66 2.33 1.59c.43.96.29 2.05-.37 2.86l-1.95 2.4h1.84c.56 0 1.01.45 1.01 1.01s-.44 1.02-1 1.02"/></g></svg>
                {elseif $user.user_rank == 3}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#d3883e" d="M15.99 30c5.545 0 10.04-4.607 10.04-10.29S21.535 9.42 15.99 9.42S5.95 14.027 5.95 19.71S10.445 30 15.99 30"/><path fill="#f3ad61" d="M16 28.76c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/><path fill="#402a32" d="m17.402 18.275l1.048-1.81a.98.98 0 0 0 0-.977a.97.97 0 0 0-.844-.488h-3.092a.977.977 0 0 0 0 1.953h1.403l-.752 1.311a.96.96 0 0 0-.214.6c0 .54.438.977.977.977c.61 0 1.108.498 1.108 1.108s-.498 1.109-1.108 1.109c-.468 0-.885-.295-1.038-.733a.975.975 0 0 0-1.24-.59a.966.966 0 0 0-.59 1.241A3.06 3.06 0 0 0 15.928 24a3.053 3.053 0 0 0 3.05-3.05a3.07 3.07 0 0 0-1.576-2.675"/></g></svg>
                {elseif $user.user_rank >= 4}
                 {$user.user_rank}
                {/if}
            </div>
            <div class="user-infop">
                <div class="usernamep">
                    {if $user.user_id == $leaderboard.current_user.user_id}
                        <span class="badge bg-primary ms-1">{__("YOU ARE HERE")}</span>
                    {else}
                        {$user.user_name}
                    {/if}
                </div>
                <div class="badgesp">
                    {foreach $user.badges as $badge}
                        {if $badge.unlocked_level}
                            <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$badge.unlocked_level}.png" class="badge-iconpp" width="20" height="20" />
                        {/if}
                    {/foreach}
                </div>
            </div>
            <div class="pointsp">{$user.user_points}</div>
        </div>
        </a>
        {/foreach}

    </div>

 

style.css

/* LEADERBOARD PUNCTE */
.leaderboardp {
    max-width: 800px;
    margin: 0 auto;
    border-radius: 10px;
    overflow: hidden;
}

.rankp,
.pointsp {
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.user-rowp {
    box-sizing: border-box;
    display: flex;
    justify-content: space-between;
    align-items: center; /* <- This is the key change */
    background: #fff;
    padding: 15px 20px;
    border-bottom: 1px solid #ddd;
    transition: background 0.3s;
}

body.night-mode .user-rowp {
  background: #212121;
  border-bottom: 1px solid #485259;
}

.user-rowp.highlightp {
    position: relative;
    background: #ffe9f0;
}

body.night-mode .user-rowp.highlightp {
  background: #1D1D1D;
}

.user-rowp.highlightp::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 4px;
    height: 100%;
    background-color: #ff3399;
}

.rankp {
    font-weight: bold;
    min-width: 40px;
    color: #1D1D1D;
    font-size: 1.3rem;
}

body.night-mode .rankp {
   color: #f8f8f8;
}

.user-infop {
    flex-grow: 1;
    padding: 0 20px;
}

.usernamep {
    font-size: 1.1em;
    font-weight: bold;
    margin-bottom: 5px;
}

.badgesp {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
}

.badgep {
    font-size: 0.8em;
    padding: 3px 8px;
    border-radius: 12px;
    background: #ccc;
    color: #fff;
}

.badgep.unlockedp {
    background-color: #28a745;
}

.badgep.lockedp {
    background-color: #6c757d;
}

.pointsp {
    min-width: 100px;
    text-align: right;
    font-weight: bold;
    color: #28a745;
    font-size: 1.4rem;
}

.badge.lockedpp {
    opacity: 0.5;
}
.badgepp {
    padding: 5px;
    border-radius: 8px;
    display: flex;
    flex-direction: column;
    align-items: start;
    margin-bottom: 10px;
}
.badge-iconpp {
  display: inline-block;
  vertical-align: middle;
  width: 32px;
  height: 32px;
}

.locked {
  background-color: #e9ecef;
}


.badge-container {
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 15px;
    margin-bottom: 20px;
    background: #fff;
    display: flex;
    gap: 15px;
}

body.night-mode .badge-container {
  background: #212121;
  border: 1px solid #485259;
}

.badge-iconpp {
    flex-shrink: 0;
}

.badge-info {
    flex-grow: 1;
}

.badge-level-entry {
    margin-top: 5px;
}

.badge-level-entry.unlocked {
    color: #28a745;
    font-weight: bold;
}

.badge-level-entry.locked {
    color: #6c757d;
}

 

Customer Service
Member
Joined: 2025-05-22 01:19:13
2025-05-31 05:32:18

I leave here for free my leaderboard + Badge

 

class-user.php

 

 // LEADERBOARD
  public function getLeaderboardData($user_id) {
    global $db;

    // Fetch top 100 users with activity stats
    $result = $db->query("
        SELECT user_id, user_name, user_points, user_registered,
               RANK() OVER (ORDER BY user_points DESC) AS user_rank
        FROM users
        WHERE user_points > 0
        ORDER BY user_points DESC
        LIMIT 100
    ") or _error(SQL_ERROR_THROWEN);

    $leaderboard = [];
    while ($row = $result->fetch_assoc()) {
        $row['badges'] = $this->assignBadges($row);
        $leaderboard[] = $row;
    }

    // Identify current user and rank
    $user_index = array_search($user_id, array_column($leaderboard, 'user_id'));
    $current_user = null;
    $next_user = null;

    if ($user_index !== false) {
        $current_user = $leaderboard[$user_index];
        $next_user = $user_index > 0 ? $leaderboard[$user_index - 1] : null;
    } else {
        // User is not in top 100
        $user_row = $db->query("
            SELECT user_id, user_name, user_points, user_registered,
                   (SELECT COUNT(*) + 1 FROM users WHERE user_points > u.user_points) AS user_rank
            FROM users u
            WHERE user_id = '{$user_id}'
            LIMIT 1
        ") or _error(SQL_ERROR_THROWEN);

        if ($user_row->num_rows > 0) {
            $current_user = $user_row->fetch_assoc();
            $current_user['badges'] = $this->assignBadges($current_user);

            // Next user
            $next_row = $db->query("
                SELECT user_name, user_points
                FROM users
                WHERE user_points > '{$current_user['user_points']}'
                ORDER BY user_points ASC
                LIMIT 1
            ") or _error(SQL_ERROR_THROWEN);

            if ($next_row->num_rows > 0) {
                $next_user = $next_row->fetch_assoc();
            }
        }
    }

    return [
        'top_users' => $leaderboard,
        'current_user' => $current_user,
        'next_user' => $next_user
    ];
}

public static function getAllBadges()
{
    $site_launch = '2025-05-24';

    return [
        [
            'key' => 'veteran',
            'label' => 'Veteran',
            'icon' => '/content/uploads/leaderboard/veteran.svg',
            'description' => 'Registered for over 1 year.',
            'requirement' => 'Account age ≥ 1 year',
            'unlocked' => function($user) {
                $years = (strtotime('now') - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
                return $years >= 1;
            },
            'level' => function($user) {
                $years = (strtotime('now') - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
                if ($years >= 3) return 'gold';
                if ($years >= 2) return 'silver';
                if ($years >= 1) return 'bronze';
                return null;
            }
        ],
        [
            'key' => 'top_points',
            'label' => 'Active Member',
            'icon' => '/content/uploads/leaderboard/contributor.svg',
            'description' => 'Earned at least 1000 points.',
            'requirement' => 'Points ≥ 1000',
            'unlocked' => function($user) {
                return $user['user_points'] >= 1000;
            },
            'level' => function($user) {
                if ($user['user_points'] >= 5000) return 'gold';
                if ($user['user_points'] >= 2500) return 'silver';
                if ($user['user_points'] >= 1000) return 'bronze';
                return null;
            }
        ],
        [
            'key' => 'early_supporter',
            'label' => 'Supporter',
            'icon' => '/content/uploads/leaderboard/early.svg',
            'description' => 'Joined in the first 3 months.',
            'requirement' => 'Registered before 2025-08-24',
            'unlocked' => function($user) use ($site_launch) {
                return strtotime($user['user_registered']) <= strtotime("$site_launch +3 months");
            },
            'level' => function($user) use ($site_launch) {
                $joined = strtotime($user['user_registered']);
                if ($joined <= strtotime("$site_launch +1 month")) return 'gold';
                if ($joined <= strtotime("$site_launch +2 months")) return 'silver';
                if ($joined <= strtotime("$site_launch +3 months")) return 'bronze';
                return null;
            }
        ]
    ];
}


public static function assignBadges($user)
{
    $all = self::getAllBadges();
    $user_badges = [];

    foreach ($all as $badge) {
        $badge_levels = [];

        if ($badge['key'] === 'top_points') {
            $badge_levels = [
                'bronze' => [
                    'requirement' => 1000,
                    'unlocked' => $user['user_points'] >= 1000,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
                'silver' => [
                    'requirement' => 2500,
                    'unlocked' => $user['user_points'] >= 2500,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
                'gold' => [
                    'requirement' => 5000,
                    'unlocked' => $user['user_points'] >= 5000,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
            ];
        }

        if ($badge['key'] === 'veteran') {
            $years = (time() - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
            $badge_levels = [
                'bronze' => [
                    'requirement' => 1,
                    'unlocked' => $years >= 1,
                    'progress' => $years,
                ],
                'silver' => [
                    'requirement' => 2,
                    'unlocked' => $years >= 2,
                    'progress' => $years,
                ],
                'gold' => [
                    'requirement' => 3,
                    'unlocked' => $years >= 3,
                    'progress' => $years,
                ],
            ];
        }

        if ($badge['key'] === 'early_supporter') {
           $registered = strtotime($user['user_registered']);
           $badge_levels = [
        'bronze' => [
            'requirement' => strtotime("2025-08-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-08-24"),
        ],
        'silver' => [
            'requirement' => strtotime("2025-07-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-07-24"),
        ],
        'gold' => [
            'requirement' => strtotime("2025-06-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-06-24"),
        ],
    ];
  }
       
        $unlocked_level = null;
        foreach (['gold', 'silver', 'bronze'] as $lvl) {
            if (!empty($badge_levels[$lvl]['unlocked'])) {
                $unlocked_level = $lvl;
                break;
            }
        }

        $user_badges[] = [
            'key' => $badge['key'],
            'label' => $badge['label'],
            'icon' => $badge['icon'],
            'description' => $badge['description'],
            'requirement' => $badge['requirement'],
            'unlocked' => call_user_func($badge['unlocked'], $user),
            'unlocked_level' => $unlocked_level,
            'levels' => $badge_levels
        ];
    }

    return $user_badges;
  }
 
 

leaderboard.php

<?php

/**
 * notifications
 *
 * @package Sngine
 * @author Zamblek
 */

// fetch bootloader
require('bootloader.php');

// user access
user_access();


// page header
page_header(__("TOP 100 ACTIVE MEMBERS"));

try {

    $leaderboard_data = $user->getLeaderboardData($user->_data['user_id']);
    $smarty->assign('leaderboard', $leaderboard_data);

    $user_badges = User::assignBadges($user->_data);
    $smarty->assign('user_badges', $user_badges);

} catch (Exception $e) {
  _error(__("Error"), $e->getMessage());
}

// page footer
page_footer('leaderboard');
 
leaderboard.tpl

<h4 class="mt-3 mb-3 text-center">{__("TOP 100 ACTIVE MEMBERS")} (<a href="{$system['system_url']}/settings/points">INFO</a>)</h4>
    

    <div class="badge-gallery">
        {foreach from=$user_badges item=badge}
    <div class="badgepp badge-container">
        {if $badge.unlocked_level}
            <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$badge.unlocked_level}.png" class="badge-iconpp" width="40" height="40" />
        {else}
            <h6 class="text-uppercase text-muted">{__("No badge unlocked")}</h6>
        {/if}

        <div class="badge-info">
            <h5 class="mb-1">{__($badge.label)}</h5>
            <p class="text-muted mb-2">{__($badge.description)}</p>

            {foreach from=$badge.levels key=level item=level_data}
                <div class="badge-level-entry badge-level-{$level} {if $level_data.unlocked}unlocked{else}locked{/if}">
                    <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$level}.png" alt="{$level}" class="me-1" width="20" height="20" />
                        <strong class="text-capitalize">{$level}</strong> – 

                    {if $badge.key == 'veteran'}
                        <strong>{$level_data.requirement} {__("years")}</strong>
                        {if !$level_data.unlocked}
                            – <strong class="text-danger">{__("Needs")} {math equation="x - y" x=$level_data.requirement y=$level_data.progress format="%.1f"} {__("years")}</strong>
                        {/if}
                    
                    {elseif $badge.key == 'early_supporter'}
                        {__("Joined before")} {$level_data.requirement|date_format:"%e %B, %Y"}
                        {if !$level_data.unlocked}
                            – <span class="text-danger">{__("Too late")}</span>
                        {/if}

                    {else}
                        (<strong>{$level_data.requirement} {__("points")}</strong>)
                        {if !$level_data.unlocked}
                            – <strong class="text-danger">{__("Needs")} {math equation="x - y" x=$level_data.requirement y=$level_data.progress|default:0 format="%d"}</strong>
                        {/if}
                    {/if}
                </div>
            {/foreach}
        </div>
    </div>
        {/foreach}
    </div>

    {if $leaderboard.current_user}
        <div class="alert alert-info mt-4">
            {__("You are ranked")} <strong>#{$leaderboard.current_user.user_rank}</strong> {__("with")} <strong>{$leaderboard.current_user.user_points}</strong> {__("points")}.
            {if $leaderboard.next_user}
                <br>
                {__("Next rank")}: <strong>{$leaderboard.next_user.user_name}</strong> {__("with")} <strong>{$leaderboard.next_user.user_points}</strong> {__("points")}.
                <br>
                {__("Needed to pass")}: <strong>{$leaderboard.next_user.user_points - $leaderboard.current_user.user_points + 1}</strong> {__("points")}
            {/if}
        </div>
    {/if}

    <div class="leaderboardp">

        {foreach $leaderboard.top_users as $user}
        <a href="{$system['system_url']}/{$user.user_name}">
        <div class="user-rowp {if $user.user_id == $leaderboard.current_user.user_id}highlightp{/if}">
            <div class="rankp">
                {if $user.user_rank == 1}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#ffb02e" d="M15.99 30c5.545 0 10.04-4.607 10.04-10.29S21.535 9.42 15.99 9.42S5.95 14.027 5.95 19.71S10.445 30 15.99 30"/><path fill="#6d4534" d="M14.076 16.041a1 1 0 0 1 1-1H16a1 1 0 0 1 1 1V23a1 1 0 1 1-2 0v-5.962a1 1 0 0 1-.924-.997"/><path fill="#fcd53f" d="M16 28.76c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/></g></svg>
                {elseif $user.user_rank == 2}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#bebebe" d="M15.96 30.001c5.545 0 10.04-4.607 10.04-10.29s-4.495-10.29-10.04-10.29s-10.04 4.607-10.04 10.29s4.495 10.29 10.04 10.29"/><path fill="#e6e6e6" d="M15.96 28.761c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/><path fill="#636363" d="M17.838 23.95h-3.97a1 1 0 0 1-.91-.58a1 1 0 0 1 .13-1.07l3.3-4.05c.26-.32.14-.66.1-.76a.64.64 0 0 0-.58-.4h-.05c-.32 0-.61.15-.8.41c-.32.45-.95.56-1.41.24a1.01 1.01 0 0 1-.24-1.41c.56-.79 1.47-1.26 2.44-1.26h.13c1.02.05 1.91.66 2.33 1.59c.43.96.29 2.05-.37 2.86l-1.95 2.4h1.84c.56 0 1.01.45 1.01 1.01s-.44 1.02-1 1.02"/></g></svg>
                {elseif $user.user_rank == 3}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#d3883e" d="M15.99 30c5.545 0 10.04-4.607 10.04-10.29S21.535 9.42 15.99 9.42S5.95 14.027 5.95 19.71S10.445 30 15.99 30"/><path fill="#f3ad61" d="M16 28.76c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/><path fill="#402a32" d="m17.402 18.275l1.048-1.81a.98.98 0 0 0 0-.977a.97.97 0 0 0-.844-.488h-3.092a.977.977 0 0 0 0 1.953h1.403l-.752 1.311a.96.96 0 0 0-.214.6c0 .54.438.977.977.977c.61 0 1.108.498 1.108 1.108s-.498 1.109-1.108 1.109c-.468 0-.885-.295-1.038-.733a.975.975 0 0 0-1.24-.59a.966.966 0 0 0-.59 1.241A3.06 3.06 0 0 0 15.928 24a3.053 3.053 0 0 0 3.05-3.05a3.07 3.07 0 0 0-1.576-2.675"/></g></svg>
                {elseif $user.user_rank >= 4}
                 {$user.user_rank}
                {/if}
            </div>
            <div class="user-infop">
                <div class="usernamep">
                    {if $user.user_id == $leaderboard.current_user.user_id}
                        <span class="badge bg-primary ms-1">{__("YOU ARE HERE")}</span>
                    {else}
                        {$user.user_name}
                    {/if}
                </div>
                <div class="badgesp">
                    {foreach $user.badges as $badge}
                        {if $badge.unlocked_level}
                            <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$badge.unlocked_level}.png" class="badge-iconpp" width="20" height="20" />
                        {/if}
                    {/foreach}
                </div>
            </div>
            <div class="pointsp">{$user.user_points}</div>
        </div>
        </a>
        {/foreach}

    </div>

 

style.css

/* LEADERBOARD PUNCTE */
.leaderboardp {
    max-width: 800px;
    margin: 0 auto;
    border-radius: 10px;
    overflow: hidden;
}

.rankp,
.pointsp {
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.user-rowp {
    box-sizing: border-box;
    display: flex;
    justify-content: space-between;
    align-items: center; /* <- This is the key change */
    background: #fff;
    padding: 15px 20px;
    border-bottom: 1px solid #ddd;
    transition: background 0.3s;
}

body.night-mode .user-rowp {
  background: #212121;
  border-bottom: 1px solid #485259;
}

.user-rowp.highlightp {
    position: relative;
    background: #ffe9f0;
}

body.night-mode .user-rowp.highlightp {
  background: #1D1D1D;
}

.user-rowp.highlightp::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 4px;
    height: 100%;
    background-color: #ff3399;
}

.rankp {
    font-weight: bold;
    min-width: 40px;
    color: #1D1D1D;
    font-size: 1.3rem;
}

body.night-mode .rankp {
   color: #f8f8f8;
}

.user-infop {
    flex-grow: 1;
    padding: 0 20px;
}

.usernamep {
    font-size: 1.1em;
    font-weight: bold;
    margin-bottom: 5px;
}

.badgesp {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
}

.badgep {
    font-size: 0.8em;
    padding: 3px 8px;
    border-radius: 12px;
    background: #ccc;
    color: #fff;
}

.badgep.unlockedp {
    background-color: #28a745;
}

.badgep.lockedp {
    background-color: #6c757d;
}

.pointsp {
    min-width: 100px;
    text-align: right;
    font-weight: bold;
    color: #28a745;
    font-size: 1.4rem;
}

.badge.lockedpp {
    opacity: 0.5;
}
.badgepp {
    padding: 5px;
    border-radius: 8px;
    display: flex;
    flex-direction: column;
    align-items: start;
    margin-bottom: 10px;
}
.badge-iconpp {
  display: inline-block;
  vertical-align: middle;
  width: 32px;
  height: 32px;
}

.locked {
  background-color: #e9ecef;
}


.badge-container {
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 15px;
    margin-bottom: 20px;
    background: #fff;
    display: flex;
    gap: 15px;
}

body.night-mode .badge-container {
  background: #212121;
  border: 1px solid #485259;
}

.badge-iconpp {
    flex-shrink: 0;
}

.badge-info {
    flex-grow: 1;
}

.badge-level-entry {
    margin-top: 5px;
}

.badge-level-entry.unlocked {
    color: #28a745;
    font-weight: bold;
}

.badge-level-entry.locked {
    color: #6c757d;
}

 

Israel Unya
Member
Joined: 2024-11-25 09:55:35
2025-06-02 20:17:16

I leave here for free my leaderboard + Badge

 

class-user.php

 

 // LEADERBOARD
  public function getLeaderboardData($user_id) {
    global $db;

    // Fetch top 100 users with activity stats
    $result = $db->query("
        SELECT user_id, user_name, user_points, user_registered,
               RANK() OVER (ORDER BY user_points DESC) AS user_rank
        FROM users
        WHERE user_points > 0
        ORDER BY user_points DESC
        LIMIT 100
    ") or _error(SQL_ERROR_THROWEN);

    $leaderboard = [];
    while ($row = $result->fetch_assoc()) {
        $row['badges'] = $this->assignBadges($row);
        $leaderboard[] = $row;
    }

    // Identify current user and rank
    $user_index = array_search($user_id, array_column($leaderboard, 'user_id'));
    $current_user = null;
    $next_user = null;

    if ($user_index !== false) {
        $current_user = $leaderboard[$user_index];
        $next_user = $user_index > 0 ? $leaderboard[$user_index - 1] : null;
    } else {
        // User is not in top 100
        $user_row = $db->query("
            SELECT user_id, user_name, user_points, user_registered,
                   (SELECT COUNT(*) + 1 FROM users WHERE user_points > u.user_points) AS user_rank
            FROM users u
            WHERE user_id = '{$user_id}'
            LIMIT 1
        ") or _error(SQL_ERROR_THROWEN);

        if ($user_row->num_rows > 0) {
            $current_user = $user_row->fetch_assoc();
            $current_user['badges'] = $this->assignBadges($current_user);

            // Next user
            $next_row = $db->query("
                SELECT user_name, user_points
                FROM users
                WHERE user_points > '{$current_user['user_points']}'
                ORDER BY user_points ASC
                LIMIT 1
            ") or _error(SQL_ERROR_THROWEN);

            if ($next_row->num_rows > 0) {
                $next_user = $next_row->fetch_assoc();
            }
        }
    }

    return [
        'top_users' => $leaderboard,
        'current_user' => $current_user,
        'next_user' => $next_user
    ];
}

public static function getAllBadges()
{
    $site_launch = '2025-05-24';

    return [
        [
            'key' => 'veteran',
            'label' => 'Veteran',
            'icon' => '/content/uploads/leaderboard/veteran.svg',
            'description' => 'Registered for over 1 year.',
            'requirement' => 'Account age ≥ 1 year',
            'unlocked' => function($user) {
                $years = (strtotime('now') - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
                return $years >= 1;
            },
            'level' => function($user) {
                $years = (strtotime('now') - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
                if ($years >= 3) return 'gold';
                if ($years >= 2) return 'silver';
                if ($years >= 1) return 'bronze';
                return null;
            }
        ],
        [
            'key' => 'top_points',
            'label' => 'Active Member',
            'icon' => '/content/uploads/leaderboard/contributor.svg',
            'description' => 'Earned at least 1000 points.',
            'requirement' => 'Points ≥ 1000',
            'unlocked' => function($user) {
                return $user['user_points'] >= 1000;
            },
            'level' => function($user) {
                if ($user['user_points'] >= 5000) return 'gold';
                if ($user['user_points'] >= 2500) return 'silver';
                if ($user['user_points'] >= 1000) return 'bronze';
                return null;
            }
        ],
        [
            'key' => 'early_supporter',
            'label' => 'Supporter',
            'icon' => '/content/uploads/leaderboard/early.svg',
            'description' => 'Joined in the first 3 months.',
            'requirement' => 'Registered before 2025-08-24',
            'unlocked' => function($user) use ($site_launch) {
                return strtotime($user['user_registered']) <= strtotime("$site_launch +3 months");
            },
            'level' => function($user) use ($site_launch) {
                $joined = strtotime($user['user_registered']);
                if ($joined <= strtotime("$site_launch +1 month")) return 'gold';
                if ($joined <= strtotime("$site_launch +2 months")) return 'silver';
                if ($joined <= strtotime("$site_launch +3 months")) return 'bronze';
                return null;
            }
        ]
    ];
}


public static function assignBadges($user)
{
    $all = self::getAllBadges();
    $user_badges = [];

    foreach ($all as $badge) {
        $badge_levels = [];

        if ($badge['key'] === 'top_points') {
            $badge_levels = [
                'bronze' => [
                    'requirement' => 1000,
                    'unlocked' => $user['user_points'] >= 1000,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
                'silver' => [
                    'requirement' => 2500,
                    'unlocked' => $user['user_points'] >= 2500,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
                'gold' => [
                    'requirement' => 5000,
                    'unlocked' => $user['user_points'] >= 5000,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
            ];
        }

        if ($badge['key'] === 'veteran') {
            $years = (time() - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
            $badge_levels = [
                'bronze' => [
                    'requirement' => 1,
                    'unlocked' => $years >= 1,
                    'progress' => $years,
                ],
                'silver' => [
                    'requirement' => 2,
                    'unlocked' => $years >= 2,
                    'progress' => $years,
                ],
                'gold' => [
                    'requirement' => 3,
                    'unlocked' => $years >= 3,
                    'progress' => $years,
                ],
            ];
        }

        if ($badge['key'] === 'early_supporter') {
           $registered = strtotime($user['user_registered']);
           $badge_levels = [
        'bronze' => [
            'requirement' => strtotime("2025-08-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-08-24"),
        ],
        'silver' => [
            'requirement' => strtotime("2025-07-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-07-24"),
        ],
        'gold' => [
            'requirement' => strtotime("2025-06-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-06-24"),
        ],
    ];
  }
       
        $unlocked_level = null;
        foreach (['gold', 'silver', 'bronze'] as $lvl) {
            if (!empty($badge_levels[$lvl]['unlocked'])) {
                $unlocked_level = $lvl;
                break;
            }
        }

        $user_badges[] = [
            'key' => $badge['key'],
            'label' => $badge['label'],
            'icon' => $badge['icon'],
            'description' => $badge['description'],
            'requirement' => $badge['requirement'],
            'unlocked' => call_user_func($badge['unlocked'], $user),
            'unlocked_level' => $unlocked_level,
            'levels' => $badge_levels
        ];
    }

    return $user_badges;
  }
 
 

leaderboard.php

<?php

/**
 * notifications
 *
 * @package Sngine
 * @author Zamblek
 */

// fetch bootloader
require('bootloader.php');

// user access
user_access();


// page header
page_header(__("TOP 100 ACTIVE MEMBERS"));

try {

    $leaderboard_data = $user->getLeaderboardData($user->_data['user_id']);
    $smarty->assign('leaderboard', $leaderboard_data);

    $user_badges = User::assignBadges($user->_data);
    $smarty->assign('user_badges', $user_badges);

} catch (Exception $e) {
  _error(__("Error"), $e->getMessage());
}

// page footer
page_footer('leaderboard');
 
leaderboard.tpl

<h4 class="mt-3 mb-3 text-center">{__("TOP 100 ACTIVE MEMBERS")} (<a href="{$system['system_url']}/settings/points">INFO</a>)</h4>
    

    <div class="badge-gallery">
        {foreach from=$user_badges item=badge}
    <div class="badgepp badge-container">
        {if $badge.unlocked_level}
            <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$badge.unlocked_level}.png" class="badge-iconpp" width="40" height="40" />
        {else}
            <h6 class="text-uppercase text-muted">{__("No badge unlocked")}</h6>
        {/if}

        <div class="badge-info">
            <h5 class="mb-1">{__($badge.label)}</h5>
            <p class="text-muted mb-2">{__($badge.description)}</p>

            {foreach from=$badge.levels key=level item=level_data}
                <div class="badge-level-entry badge-level-{$level} {if $level_data.unlocked}unlocked{else}locked{/if}">
                    <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$level}.png" alt="{$level}" class="me-1" width="20" height="20" />
                        <strong class="text-capitalize">{$level}</strong> – 

                    {if $badge.key == 'veteran'}
                        <strong>{$level_data.requirement} {__("years")}</strong>
                        {if !$level_data.unlocked}
                            – <strong class="text-danger">{__("Needs")} {math equation="x - y" x=$level_data.requirement y=$level_data.progress format="%.1f"} {__("years")}</strong>
                        {/if}
                    
                    {elseif $badge.key == 'early_supporter'}
                        {__("Joined before")} {$level_data.requirement|date_format:"%e %B, %Y"}
                        {if !$level_data.unlocked}
                            – <span class="text-danger">{__("Too late")}</span>
                        {/if}

                    {else}
                        (<strong>{$level_data.requirement} {__("points")}</strong>)
                        {if !$level_data.unlocked}
                            – <strong class="text-danger">{__("Needs")} {math equation="x - y" x=$level_data.requirement y=$level_data.progress|default:0 format="%d"}</strong>
                        {/if}
                    {/if}
                </div>
            {/foreach}
        </div>
    </div>
        {/foreach}
    </div>

    {if $leaderboard.current_user}
        <div class="alert alert-info mt-4">
            {__("You are ranked")} <strong>#{$leaderboard.current_user.user_rank}</strong> {__("with")} <strong>{$leaderboard.current_user.user_points}</strong> {__("points")}.
            {if $leaderboard.next_user}
                <br>
                {__("Next rank")}: <strong>{$leaderboard.next_user.user_name}</strong> {__("with")} <strong>{$leaderboard.next_user.user_points}</strong> {__("points")}.
                <br>
                {__("Needed to pass")}: <strong>{$leaderboard.next_user.user_points - $leaderboard.current_user.user_points + 1}</strong> {__("points")}
            {/if}
        </div>
    {/if}

    <div class="leaderboardp">

        {foreach $leaderboard.top_users as $user}
        <a href="{$system['system_url']}/{$user.user_name}">
        <div class="user-rowp {if $user.user_id == $leaderboard.current_user.user_id}highlightp{/if}">
            <div class="rankp">
                {if $user.user_rank == 1}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#ffb02e" d="M15.99 30c5.545 0 10.04-4.607 10.04-10.29S21.535 9.42 15.99 9.42S5.95 14.027 5.95 19.71S10.445 30 15.99 30"/><path fill="#6d4534" d="M14.076 16.041a1 1 0 0 1 1-1H16a1 1 0 0 1 1 1V23a1 1 0 1 1-2 0v-5.962a1 1 0 0 1-.924-.997"/><path fill="#fcd53f" d="M16 28.76c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/></g></svg>
                {elseif $user.user_rank == 2}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#bebebe" d="M15.96 30.001c5.545 0 10.04-4.607 10.04-10.29s-4.495-10.29-10.04-10.29s-10.04 4.607-10.04 10.29s4.495 10.29 10.04 10.29"/><path fill="#e6e6e6" d="M15.96 28.761c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/><path fill="#636363" d="M17.838 23.95h-3.97a1 1 0 0 1-.91-.58a1 1 0 0 1 .13-1.07l3.3-4.05c.26-.32.14-.66.1-.76a.64.64 0 0 0-.58-.4h-.05c-.32 0-.61.15-.8.41c-.32.45-.95.56-1.41.24a1.01 1.01 0 0 1-.24-1.41c.56-.79 1.47-1.26 2.44-1.26h.13c1.02.05 1.91.66 2.33 1.59c.43.96.29 2.05-.37 2.86l-1.95 2.4h1.84c.56 0 1.01.45 1.01 1.01s-.44 1.02-1 1.02"/></g></svg>
                {elseif $user.user_rank == 3}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#d3883e" d="M15.99 30c5.545 0 10.04-4.607 10.04-10.29S21.535 9.42 15.99 9.42S5.95 14.027 5.95 19.71S10.445 30 15.99 30"/><path fill="#f3ad61" d="M16 28.76c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/><path fill="#402a32" d="m17.402 18.275l1.048-1.81a.98.98 0 0 0 0-.977a.97.97 0 0 0-.844-.488h-3.092a.977.977 0 0 0 0 1.953h1.403l-.752 1.311a.96.96 0 0 0-.214.6c0 .54.438.977.977.977c.61 0 1.108.498 1.108 1.108s-.498 1.109-1.108 1.109c-.468 0-.885-.295-1.038-.733a.975.975 0 0 0-1.24-.59a.966.966 0 0 0-.59 1.241A3.06 3.06 0 0 0 15.928 24a3.053 3.053 0 0 0 3.05-3.05a3.07 3.07 0 0 0-1.576-2.675"/></g></svg>
                {elseif $user.user_rank >= 4}
                 {$user.user_rank}
                {/if}
            </div>
            <div class="user-infop">
                <div class="usernamep">
                    {if $user.user_id == $leaderboard.current_user.user_id}
                        <span class="badge bg-primary ms-1">{__("YOU ARE HERE")}</span>
                    {else}
                        {$user.user_name}
                    {/if}
                </div>
                <div class="badgesp">
                    {foreach $user.badges as $badge}
                        {if $badge.unlocked_level}
                            <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$badge.unlocked_level}.png" class="badge-iconpp" width="20" height="20" />
                        {/if}
                    {/foreach}
                </div>
            </div>
            <div class="pointsp">{$user.user_points}</div>
        </div>
        </a>
        {/foreach}

    </div>

 

style.css

/* LEADERBOARD PUNCTE */
.leaderboardp {
    max-width: 800px;
    margin: 0 auto;
    border-radius: 10px;
    overflow: hidden;
}

.rankp,
.pointsp {
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.user-rowp {
    box-sizing: border-box;
    display: flex;
    justify-content: space-between;
    align-items: center; /* <- This is the key change */
    background: #fff;
    padding: 15px 20px;
    border-bottom: 1px solid #ddd;
    transition: background 0.3s;
}

body.night-mode .user-rowp {
  background: #212121;
  border-bottom: 1px solid #485259;
}

.user-rowp.highlightp {
    position: relative;
    background: #ffe9f0;
}

body.night-mode .user-rowp.highlightp {
  background: #1D1D1D;
}

.user-rowp.highlightp::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 4px;
    height: 100%;
    background-color: #ff3399;
}

.rankp {
    font-weight: bold;
    min-width: 40px;
    color: #1D1D1D;
    font-size: 1.3rem;
}

body.night-mode .rankp {
   color: #f8f8f8;
}

.user-infop {
    flex-grow: 1;
    padding: 0 20px;
}

.usernamep {
    font-size: 1.1em;
    font-weight: bold;
    margin-bottom: 5px;
}

.badgesp {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
}

.badgep {
    font-size: 0.8em;
    padding: 3px 8px;
    border-radius: 12px;
    background: #ccc;
    color: #fff;
}

.badgep.unlockedp {
    background-color: #28a745;
}

.badgep.lockedp {
    background-color: #6c757d;
}

.pointsp {
    min-width: 100px;
    text-align: right;
    font-weight: bold;
    color: #28a745;
    font-size: 1.4rem;
}

.badge.lockedpp {
    opacity: 0.5;
}
.badgepp {
    padding: 5px;
    border-radius: 8px;
    display: flex;
    flex-direction: column;
    align-items: start;
    margin-bottom: 10px;
}
.badge-iconpp {
  display: inline-block;
  vertical-align: middle;
  width: 32px;
  height: 32px;
}

.locked {
  background-color: #e9ecef;
}


.badge-container {
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 15px;
    margin-bottom: 20px;
    background: #fff;
    display: flex;
    gap: 15px;
}

body.night-mode .badge-container {
  background: #212121;
  border: 1px solid #485259;
}

.badge-iconpp {
    flex-shrink: 0;
}

.badge-info {
    flex-grow: 1;
}

.badge-level-entry {
    margin-top: 5px;
}

.badge-level-entry.unlocked {
    color: #28a745;
    font-weight: bold;
}

.badge-level-entry.locked {
    color: #6c757d;
}

 

Mou Rad
Member
Joined: 2025-06-05 13:39:12
2025-06-05 13:57:47

I leave here for free my leaderboard + Badge

 

class-user.php

 

 // LEADERBOARD
  public function getLeaderboardData($user_id) {
    global $db;

    // Fetch top 100 users with activity stats
    $result = $db->query("
        SELECT user_id, user_name, user_points, user_registered,
               RANK() OVER (ORDER BY user_points DESC) AS user_rank
        FROM users
        WHERE user_points > 0
        ORDER BY user_points DESC
        LIMIT 100
    ") or _error(SQL_ERROR_THROWEN);

    $leaderboard = [];
    while ($row = $result->fetch_assoc()) {
        $row['badges'] = $this->assignBadges($row);
        $leaderboard[] = $row;
    }

    // Identify current user and rank
    $user_index = array_search($user_id, array_column($leaderboard, 'user_id'));
    $current_user = null;
    $next_user = null;

    if ($user_index !== false) {
        $current_user = $leaderboard[$user_index];
        $next_user = $user_index > 0 ? $leaderboard[$user_index - 1] : null;
    } else {
        // User is not in top 100
        $user_row = $db->query("
            SELECT user_id, user_name, user_points, user_registered,
                   (SELECT COUNT(*) + 1 FROM users WHERE user_points > u.user_points) AS user_rank
            FROM users u
            WHERE user_id = '{$user_id}'
            LIMIT 1
        ") or _error(SQL_ERROR_THROWEN);

        if ($user_row->num_rows > 0) {
            $current_user = $user_row->fetch_assoc();
            $current_user['badges'] = $this->assignBadges($current_user);

            // Next user
            $next_row = $db->query("
                SELECT user_name, user_points
                FROM users
                WHERE user_points > '{$current_user['user_points']}'
                ORDER BY user_points ASC
                LIMIT 1
            ") or _error(SQL_ERROR_THROWEN);

            if ($next_row->num_rows > 0) {
                $next_user = $next_row->fetch_assoc();
            }
        }
    }

    return [
        'top_users' => $leaderboard,
        'current_user' => $current_user,
        'next_user' => $next_user
    ];
}

public static function getAllBadges()
{
    $site_launch = '2025-05-24';

    return [
        [
            'key' => 'veteran',
            'label' => 'Veteran',
            'icon' => '/content/uploads/leaderboard/veteran.svg',
            'description' => 'Registered for over 1 year.',
            'requirement' => 'Account age ≥ 1 year',
            'unlocked' => function($user) {
                $years = (strtotime('now') - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
                return $years >= 1;
            },
            'level' => function($user) {
                $years = (strtotime('now') - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
                if ($years >= 3) return 'gold';
                if ($years >= 2) return 'silver';
                if ($years >= 1) return 'bronze';
                return null;
            }
        ],
        [
            'key' => 'top_points',
            'label' => 'Active Member',
            'icon' => '/content/uploads/leaderboard/contributor.svg',
            'description' => 'Earned at least 1000 points.',
            'requirement' => 'Points ≥ 1000',
            'unlocked' => function($user) {
                return $user['user_points'] >= 1000;
            },
            'level' => function($user) {
                if ($user['user_points'] >= 5000) return 'gold';
                if ($user['user_points'] >= 2500) return 'silver';
                if ($user['user_points'] >= 1000) return 'bronze';
                return null;
            }
        ],
        [
            'key' => 'early_supporter',
            'label' => 'Supporter',
            'icon' => '/content/uploads/leaderboard/early.svg',
            'description' => 'Joined in the first 3 months.',
            'requirement' => 'Registered before 2025-08-24',
            'unlocked' => function($user) use ($site_launch) {
                return strtotime($user['user_registered']) <= strtotime("$site_launch +3 months");
            },
            'level' => function($user) use ($site_launch) {
                $joined = strtotime($user['user_registered']);
                if ($joined <= strtotime("$site_launch +1 month")) return 'gold';
                if ($joined <= strtotime("$site_launch +2 months")) return 'silver';
                if ($joined <= strtotime("$site_launch +3 months")) return 'bronze';
                return null;
            }
        ]
    ];
}


public static function assignBadges($user)
{
    $all = self::getAllBadges();
    $user_badges = [];

    foreach ($all as $badge) {
        $badge_levels = [];

        if ($badge['key'] === 'top_points') {
            $badge_levels = [
                'bronze' => [
                    'requirement' => 1000,
                    'unlocked' => $user['user_points'] >= 1000,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
                'silver' => [
                    'requirement' => 2500,
                    'unlocked' => $user['user_points'] >= 2500,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
                'gold' => [
                    'requirement' => 5000,
                    'unlocked' => $user['user_points'] >= 5000,
                    'progress' => isset($user['user_points']) ? (int)$user['user_points'] : 0,
                ],
            ];
        }

        if ($badge['key'] === 'veteran') {
            $years = (time() - strtotime($user['user_registered'])) / (60 * 60 * 24 * 365);
            $badge_levels = [
                'bronze' => [
                    'requirement' => 1,
                    'unlocked' => $years >= 1,
                    'progress' => $years,
                ],
                'silver' => [
                    'requirement' => 2,
                    'unlocked' => $years >= 2,
                    'progress' => $years,
                ],
                'gold' => [
                    'requirement' => 3,
                    'unlocked' => $years >= 3,
                    'progress' => $years,
                ],
            ];
        }

        if ($badge['key'] === 'early_supporter') {
           $registered = strtotime($user['user_registered']);
           $badge_levels = [
        'bronze' => [
            'requirement' => strtotime("2025-08-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-08-24"),
        ],
        'silver' => [
            'requirement' => strtotime("2025-07-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-07-24"),
        ],
        'gold' => [
            'requirement' => strtotime("2025-06-24"),
            'progress' => $registered,
            'unlocked' => $registered <= strtotime("2025-06-24"),
        ],
    ];
  }
       
        $unlocked_level = null;
        foreach (['gold', 'silver', 'bronze'] as $lvl) {
            if (!empty($badge_levels[$lvl]['unlocked'])) {
                $unlocked_level = $lvl;
                break;
            }
        }

        $user_badges[] = [
            'key' => $badge['key'],
            'label' => $badge['label'],
            'icon' => $badge['icon'],
            'description' => $badge['description'],
            'requirement' => $badge['requirement'],
            'unlocked' => call_user_func($badge['unlocked'], $user),
            'unlocked_level' => $unlocked_level,
            'levels' => $badge_levels
        ];
    }

    return $user_badges;
  }
 
 

leaderboard.php

<?php

/**
 * notifications
 *
 * @package Sngine
 * @author Zamblek
 */

// fetch bootloader
require('bootloader.php');

// user access
user_access();


// page header
page_header(__("TOP 100 ACTIVE MEMBERS"));

try {

    $leaderboard_data = $user->getLeaderboardData($user->_data['user_id']);
    $smarty->assign('leaderboard', $leaderboard_data);

    $user_badges = User::assignBadges($user->_data);
    $smarty->assign('user_badges', $user_badges);

} catch (Exception $e) {
  _error(__("Error"), $e->getMessage());
}

// page footer
page_footer('leaderboard');
 
leaderboard.tpl

<h4 class="mt-3 mb-3 text-center">{__("TOP 100 ACTIVE MEMBERS")} (<a href="{$system['system_url']}/settings/points">INFO</a>)</h4>
    

    <div class="badge-gallery">
        {foreach from=$user_badges item=badge}
    <div class="badgepp badge-container">
        {if $badge.unlocked_level}
            <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$badge.unlocked_level}.png" class="badge-iconpp" width="40" height="40" />
        {else}
            <h6 class="text-uppercase text-muted">{__("No badge unlocked")}</h6>
        {/if}

        <div class="badge-info">
            <h5 class="mb-1">{__($badge.label)}</h5>
            <p class="text-muted mb-2">{__($badge.description)}</p>

            {foreach from=$badge.levels key=level item=level_data}
                <div class="badge-level-entry badge-level-{$level} {if $level_data.unlocked}unlocked{else}locked{/if}">
                    <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$level}.png" alt="{$level}" class="me-1" width="20" height="20" />
                        <strong class="text-capitalize">{$level}</strong> – 

                    {if $badge.key == 'veteran'}
                        <strong>{$level_data.requirement} {__("years")}</strong>
                        {if !$level_data.unlocked}
                            – <strong class="text-danger">{__("Needs")} {math equation="x - y" x=$level_data.requirement y=$level_data.progress format="%.1f"} {__("years")}</strong>
                        {/if}
                    
                    {elseif $badge.key == 'early_supporter'}
                        {__("Joined before")} {$level_data.requirement|date_format:"%e %B, %Y"}
                        {if !$level_data.unlocked}
                            – <span class="text-danger">{__("Too late")}</span>
                        {/if}

                    {else}
                        (<strong>{$level_data.requirement} {__("points")}</strong>)
                        {if !$level_data.unlocked}
                            – <strong class="text-danger">{__("Needs")} {math equation="x - y" x=$level_data.requirement y=$level_data.progress|default:0 format="%d"}</strong>
                        {/if}
                    {/if}
                </div>
            {/foreach}
        </div>
    </div>
        {/foreach}
    </div>

    {if $leaderboard.current_user}
        <div class="alert alert-info mt-4">
            {__("You are ranked")} <strong>#{$leaderboard.current_user.user_rank}</strong> {__("with")} <strong>{$leaderboard.current_user.user_points}</strong> {__("points")}.
            {if $leaderboard.next_user}
                <br>
                {__("Next rank")}: <strong>{$leaderboard.next_user.user_name}</strong> {__("with")} <strong>{$leaderboard.next_user.user_points}</strong> {__("points")}.
                <br>
                {__("Needed to pass")}: <strong>{$leaderboard.next_user.user_points - $leaderboard.current_user.user_points + 1}</strong> {__("points")}
            {/if}
        </div>
    {/if}

    <div class="leaderboardp">

        {foreach $leaderboard.top_users as $user}
        <a href="{$system['system_url']}/{$user.user_name}">
        <div class="user-rowp {if $user.user_id == $leaderboard.current_user.user_id}highlightp{/if}">
            <div class="rankp">
                {if $user.user_rank == 1}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#ffb02e" d="M15.99 30c5.545 0 10.04-4.607 10.04-10.29S21.535 9.42 15.99 9.42S5.95 14.027 5.95 19.71S10.445 30 15.99 30"/><path fill="#6d4534" d="M14.076 16.041a1 1 0 0 1 1-1H16a1 1 0 0 1 1 1V23a1 1 0 1 1-2 0v-5.962a1 1 0 0 1-.924-.997"/><path fill="#fcd53f" d="M16 28.76c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/></g></svg>
                {elseif $user.user_rank == 2}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#bebebe" d="M15.96 30.001c5.545 0 10.04-4.607 10.04-10.29s-4.495-10.29-10.04-10.29s-10.04 4.607-10.04 10.29s4.495 10.29 10.04 10.29"/><path fill="#e6e6e6" d="M15.96 28.761c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/><path fill="#636363" d="M17.838 23.95h-3.97a1 1 0 0 1-.91-.58a1 1 0 0 1 .13-1.07l3.3-4.05c.26-.32.14-.66.1-.76a.64.64 0 0 0-.58-.4h-.05c-.32 0-.61.15-.8.41c-.32.45-.95.56-1.41.24a1.01 1.01 0 0 1-.24-1.41c.56-.79 1.47-1.26 2.44-1.26h.13c1.02.05 1.91.66 2.33 1.59c.43.96.29 2.05-.37 2.86l-1.95 2.4h1.84c.56 0 1.01.45 1.01 1.01s-.44 1.02-1 1.02"/></g></svg>
                {elseif $user.user_rank == 3}<svg xmlns="http://www.w3.org/2000/svg" width="38" height="38" viewBox="0 0 32 32"><g fill="none"><path fill="#0074ba" d="m18.768 11.51l-5.22-8.58c-.34-.58-.95-.93-1.62-.93h-6.59c-1.45 0-2.36 1.56-1.65 2.82a16.7 16.7 0 0 0 5.43 5.78c.76.59 1.7.91 2.67.91z"/><path fill="#00a6ed" d="M26.658 2h-6.59c-.67 0-1.28.35-1.62.93l-5.22 8.58h6.99c.97 0 1.9-.32 2.67-.91c2.25-1.46 4.11-3.44 5.43-5.78c.7-1.26-.21-2.82-1.66-2.82"/><path fill="#d3883e" d="M15.99 30c5.545 0 10.04-4.607 10.04-10.29S21.535 9.42 15.99 9.42S5.95 14.027 5.95 19.71S10.445 30 15.99 30"/><path fill="#f3ad61" d="M16 28.76c-2.36 0-4.58-.94-6.24-2.65a9.1 9.1 0 0 1-2.59-6.4c0-2.42.92-4.69 2.59-6.4a8.69 8.69 0 0 1 12.49 0c3.44 3.53 3.44 9.27 0 12.8c-1.68 1.71-3.9 2.65-6.25 2.65m-.01-16.87c-1.95 0-3.91.76-5.39 2.29a7.87 7.87 0 0 0-2.23 5.53c0 2.09.79 4.05 2.23 5.53a7.48 7.48 0 0 0 5.39 2.29c2.04 0 3.95-.81 5.39-2.29c2.97-3.05 2.97-8.01 0-11.06a7.46 7.46 0 0 0-5.39-2.29"/><path fill="#402a32" d="m17.402 18.275l1.048-1.81a.98.98 0 0 0 0-.977a.97.97 0 0 0-.844-.488h-3.092a.977.977 0 0 0 0 1.953h1.403l-.752 1.311a.96.96 0 0 0-.214.6c0 .54.438.977.977.977c.61 0 1.108.498 1.108 1.108s-.498 1.109-1.108 1.109c-.468 0-.885-.295-1.038-.733a.975.975 0 0 0-1.24-.59a.966.966 0 0 0-.59 1.241A3.06 3.06 0 0 0 15.928 24a3.053 3.053 0 0 0 3.05-3.05a3.07 3.07 0 0 0-1.576-2.675"/></g></svg>
                {elseif $user.user_rank >= 4}
                 {$user.user_rank}
                {/if}
            </div>
            <div class="user-infop">
                <div class="usernamep">
                    {if $user.user_id == $leaderboard.current_user.user_id}
                        <span class="badge bg-primary ms-1">{__("YOU ARE HERE")}</span>
                    {else}
                        {$user.user_name}
                    {/if}
                </div>
                <div class="badgesp">
                    {foreach $user.badges as $badge}
                        {if $badge.unlocked_level}
                            <img src="{$system['system_url']}/content/uploads/leaderboard/{$badge.key}_{$badge.unlocked_level}.png" class="badge-iconpp" width="20" height="20" />
                        {/if}
                    {/foreach}
                </div>
            </div>
            <div class="pointsp">{$user.user_points}</div>
        </div>
        </a>
        {/foreach}

    </div>

 

style.css

/* LEADERBOARD PUNCTE */
.leaderboardp {
    max-width: 800px;
    margin: 0 auto;
    border-radius: 10px;
    overflow: hidden;
}

.rankp,
.pointsp {
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.user-rowp {
    box-sizing: border-box;
    display: flex;
    justify-content: space-between;
    align-items: center; /* <- This is the key change */
    background: #fff;
    padding: 15px 20px;
    border-bottom: 1px solid #ddd;
    transition: background 0.3s;
}

body.night-mode .user-rowp {
  background: #212121;
  border-bottom: 1px solid #485259;
}

.user-rowp.highlightp {
    position: relative;
    background: #ffe9f0;
}

body.night-mode .user-rowp.highlightp {
  background: #1D1D1D;
}

.user-rowp.highlightp::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 4px;
    height: 100%;
    background-color: #ff3399;
}

.rankp {
    font-weight: bold;
    min-width: 40px;
    color: #1D1D1D;
    font-size: 1.3rem;
}

body.night-mode .rankp {
   color: #f8f8f8;
}

.user-infop {
    flex-grow: 1;
    padding: 0 20px;
}

.usernamep {
    font-size: 1.1em;
    font-weight: bold;
    margin-bottom: 5px;
}

.badgesp {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
}

.badgep {
    font-size: 0.8em;
    padding: 3px 8px;
    border-radius: 12px;
    background: #ccc;
    color: #fff;
}

.badgep.unlockedp {
    background-color: #28a745;
}

.badgep.lockedp {
    background-color: #6c757d;
}

.pointsp {
    min-width: 100px;
    text-align: right;
    font-weight: bold;
    color: #28a745;
    font-size: 1.4rem;
}

.badge.lockedpp {
    opacity: 0.5;
}
.badgepp {
    padding: 5px;
    border-radius: 8px;
    display: flex;
    flex-direction: column;
    align-items: start;
    margin-bottom: 10px;
}
.badge-iconpp {
  display: inline-block;
  vertical-align: middle;
  width: 32px;
  height: 32px;
}

.locked {
  background-color: #e9ecef;
}


.badge-container {
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 15px;
    margin-bottom: 20px;
    background: #fff;
    display: flex;
    gap: 15px;
}

body.night-mode .badge-container {
  background: #212121;
  border: 1px solid #485259;
}

.badge-iconpp {
    flex-shrink: 0;
}

.badge-info {
    flex-grow: 1;
}

.badge-level-entry {
    margin-top: 5px;
}

.badge-level-entry.unlocked {
    color: #28a745;
    font-weight: bold;
}

.badge-level-entry.locked {
    color: #6c757d;
}

 

ScriptsTribe https://scriptstribe.com