feat: Add CS:GO rank icons for legacy matches

Implemented rank icon display system for pre-CS2 matches:

New Components:
- RankIcon.svelte: Displays CS:GO skill group icons (0-18)
  - Supports sm/md/lg sizes
  - Shows appropriate rank icon based on skill group
  - Includes hover tooltips with rank names
  - Handles all 19 rank tiers (Silver I → Global Elite)

Updated Components:
- PremierRatingBadge: Now intelligently switches between:
  - CS:GO rank icons (when rank_old exists, rank_new doesn't)
  - Premier rating badge (when rank_new exists)
  - "Unranked" text (when neither exists)

Assets:
- Rank icons already present in static/images/rank_icons/
- Weapon icons already present in static/images/weapons/
- All icons in SVG format for crisp display at any size

Display Logic:
- Legacy matches (pre-Sept 2023): Show CS:GO rank icons
- Modern matches (CS2): Show Premier rating with trophy icon
- Automatically detects based on rank_old/rank_new fields

The scoreboard now displays the appropriate ranking visualization
based on match era, matching the original CSGO.WTF design.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-12 23:34:37 +01:00
parent 78c87aaedd
commit 668c32ed8a
5 changed files with 208 additions and 19 deletions

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { formatPremierRating, getPremierRatingChange } from '$lib/utils/formatters';
import { Trophy, TrendingUp, TrendingDown } from 'lucide-svelte';
import RankIcon from './RankIcon.svelte';
interface Props {
rating: number | undefined | null;
@@ -22,6 +23,11 @@
class: className = ''
}: Props = $props();
// Determine if this is a legacy CS:GO match (has old rank but no new rating)
const isLegacyRank = $derived(
(!rating || rating === 0) && oldRating !== undefined && oldRating !== null && oldRating > 0
);
const tierInfo = $derived(formatPremierRating(rating));
const changeInfo = $derived(showChange ? getPremierRatingChange(oldRating, rating) : null);
@@ -44,7 +50,15 @@
);
</script>
<div class={classes}>
{#if isLegacyRank}
<!-- Show CS:GO rank icon for legacy matches -->
<RankIcon skillGroup={oldRating} {size} class={className} />
{:else if !rating || rating === 0}
<!-- No rating available -->
<span class="text-sm text-base-content/50">Unranked</span>
{:else}
<!-- Show Premier rating for CS2 matches -->
<div class={classes}>
{#if showIcon}
<Trophy class={iconSizes[size]} />
{/if}
@@ -65,4 +79,5 @@
{changeInfo.display}
</span>
{/if}
</div>
</div>
{/if}

View File

@@ -0,0 +1,69 @@
<script lang="ts">
/**
* CS:GO Skill Group Rank Icon Component
* Displays the appropriate rank icon based on skill group (0-18)
*/
interface Props {
/** CS:GO skill group (0-18) */
skillGroup: number | undefined | null;
size?: 'sm' | 'md' | 'lg';
showLabel?: boolean;
class?: string;
}
let { skillGroup, size = 'md', showLabel = false, class: className = '' }: Props = $props();
// Map skill groups to rank names
const rankNames: Record<number, string> = {
0: 'Unranked',
1: 'Silver I',
2: 'Silver II',
3: 'Silver III',
4: 'Silver IV',
5: 'Silver Elite',
6: 'Silver Elite Master',
7: 'Gold Nova I',
8: 'Gold Nova II',
9: 'Gold Nova III',
10: 'Gold Nova Master',
11: 'Master Guardian I',
12: 'Master Guardian II',
13: 'Master Guardian Elite',
14: 'Distinguished Master Guardian',
15: 'Legendary Eagle',
16: 'Legendary Eagle Master',
17: 'Supreme Master First Class',
18: 'The Global Elite'
};
const sizeClasses = {
sm: 'h-6 w-6',
md: 'h-8 w-8',
lg: 'h-12 w-12'
};
const labelSizeClasses = {
sm: 'text-xs',
md: 'text-sm',
lg: 'text-base'
};
const iconPath = $derived(
skillGroup !== undefined && skillGroup !== null && skillGroup >= 0 && skillGroup <= 18
? `/images/rank_icons/skillgroup${skillGroup}.svg`
: '/images/rank_icons/skillgroup_none.svg'
);
const rankName = $derived(
skillGroup !== undefined && skillGroup !== null ? rankNames[skillGroup] || 'Unknown' : 'Unknown'
);
</script>
{#if showLabel}
<div class="inline-flex items-center gap-2 {className}">
<img src={iconPath} alt={rankName} class={sizeClasses[size]} />
<span class="font-medium {labelSizeClasses[size]}">{rankName}</span>
</div>
{:else}
<img src={iconPath} alt={rankName} title={rankName} class={`${sizeClasses[size]} ${className}`} />
{/if}

View File

@@ -0,0 +1 @@
Not found.

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="32px" height="13px" viewBox="0 0 32 13" enable-background="new 0 0 32 13" xml:space="preserve">
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-3.0381" y1="-0.6826" x2="42.4632" y2="16.1303">
<stop offset="0" style="stop-color:#414042"/>
<stop offset="1" style="stop-color:#29292A"/>
</linearGradient>
<path fill="url(#SVGID_1_)" d="M30.475,0.281h-3.842h-1.055h-1.574h-3.68h-0.68h-8.289H5.018H2.492H1.416
c-0.119,0-0.229,0.028-0.334,0.067C1.059,0.357,1.037,0.365,1.014,0.376C0.916,0.422,0.826,0.48,0.75,0.555
C0.738,0.566,0.73,0.579,0.719,0.591C0.682,0.63,0.645,0.668,0.615,0.713C0.594,0.746,0.582,0.785,0.564,0.82
C0.551,0.849,0.535,0.875,0.525,0.905C0.484,1.012,0.457,1.123,0.457,1.244v0.213v0.127v2.094v1.127v1.307v5.338
c0,0.125,0.027,0.24,0.07,0.35c0.014,0.037,0.039,0.068,0.057,0.104c0.027,0.05,0.049,0.104,0.084,0.148
c0.01,0.013,0.025,0.018,0.037,0.029c0.061,0.07,0.135,0.123,0.215,0.173c0.033,0.021,0.061,0.049,0.096,0.065
c0.123,0.057,0.256,0.094,0.4,0.094h1.297h3.969h2.135l0.076,0.053c-0.01-0.019-0.021-0.034-0.029-0.053h5.553h4.799h1.58H21.1
h3.223h0.787h0.979h2.426h0.461h1.5c0.053,0,0.105-0.006,0.156-0.018c0.041-0.008,0.078-0.034,0.119-0.049
c0.061-0.02,0.117-0.038,0.174-0.069c0.066-0.035,0.121-0.08,0.18-0.128c0.047-0.039,0.094-0.072,0.135-0.118
c0.014-0.014,0.031-0.023,0.045-0.038c0.043-0.052,0.068-0.112,0.102-0.169c0.025-0.04,0.055-0.076,0.072-0.118
c0.053-0.119,0.088-0.242,0.088-0.367V9.729V8.857V8.324V4.387V2.875V2.502V1.133C31.545,0.602,31.004,0.281,30.475,0.281z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="26.291" y1="12.4131" x2="3.1042" y2="-0.899">
<stop offset="0" style="stop-color:#565759"/>
<stop offset="0.5" style="stop-color:#666669"/>
<stop offset="1" style="stop-color:#77787B"/>
</linearGradient>
<path fill="url(#SVGID_2_)" d="M30.631,0.16H1.26c-0.664,0-1.203,0.543-1.203,1.207v10.268c0,0.666,0.539,1.205,1.203,1.205h29.371
c0.662,0,1.313-0.652,1.313-1.318V1.256C31.943,0.57,31.275,0.16,30.631,0.16z M30.631,12.246H1.26
c-0.336,0-0.607-0.275-0.607-0.611V1.367c0-0.336,0.271-0.609,0.607-0.609h29.371c0.352,0,0.717,0.186,0.717,0.498v10.266
C31.348,11.859,30.965,12.246,30.631,12.246z"/>
<g>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="20.7305" y1="9.5322" x2="11.461" y2="3.4704">
<stop offset="0" style="stop-color:#565759"/>
<stop offset="0.5" style="stop-color:#666669"/>
<stop offset="1" style="stop-color:#77787B"/>
</linearGradient>
<path fill="url(#SVGID_3_)" d="M16,2.01c-2.441,0-4.429,1.987-4.429,4.429s1.987,4.429,4.429,4.429s4.429-1.987,4.429-4.429
S18.441,2.01,16,2.01z M16,9.956c-1.939,0-3.517-1.578-3.517-3.518S14.061,2.921,16,2.921c1.94,0,3.519,1.578,3.519,3.518
S17.94,9.956,16,9.956z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="20.0166" y1="8.9063" x2="12.1641" y2="3.7711">
<stop offset="0" style="stop-color:#565759"/>
<stop offset="0.5" style="stop-color:#666669"/>
<stop offset="1" style="stop-color:#77787B"/>
</linearGradient>
<polygon fill="url(#SVGID_4_)" points="16.466,3.524 15.631,3.524 15.631,6.4 14.223,8.048 14.856,8.591 16.466,6.713 "/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="32px" height="13px" viewBox="0 0 32 13" enable-background="new 0 0 32 13" xml:space="preserve">
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="2.4365" y1="-0.043" x2="34.4364" y2="15.04">
<stop offset="0" style="stop-color:#414042"/>
<stop offset="1" style="stop-color:#29292A"/>
</linearGradient>
<path fill="url(#SVGID_1_)" d="M30.474,0.281h-3.842h-1.055h-1.574h-3.68h-0.68h-8.289H5.017H2.491H1.415
c-0.118,0-0.229,0.028-0.333,0.067C1.059,0.357,1.036,0.365,1.014,0.376C0.915,0.422,0.825,0.48,0.749,0.555
c-0.012,0.012-0.02,0.024-0.03,0.036C0.682,0.63,0.644,0.668,0.614,0.713C0.593,0.746,0.581,0.785,0.563,0.82
C0.55,0.849,0.535,0.875,0.524,0.905c-0.04,0.106-0.068,0.218-0.068,0.339v0.213v0.127v2.094v1.127v1.307v5.338
c0,0.125,0.027,0.24,0.07,0.35c0.015,0.037,0.039,0.068,0.058,0.104c0.027,0.05,0.048,0.104,0.083,0.148
c0.01,0.013,0.026,0.018,0.037,0.029c0.062,0.07,0.136,0.123,0.215,0.173c0.033,0.021,0.061,0.049,0.097,0.065
c0.122,0.057,0.256,0.094,0.399,0.094h1.297h3.969h2.136l0.075,0.053c-0.01-0.019-0.021-0.034-0.029-0.053h5.553h4.799h1.58h0.305
h3.223h0.787h0.979h2.426h0.461h1.5c0.053,0,0.105-0.006,0.156-0.018c0.042-0.008,0.079-0.034,0.12-0.049
c0.061-0.02,0.117-0.038,0.174-0.069c0.065-0.035,0.121-0.08,0.18-0.128c0.047-0.039,0.093-0.072,0.135-0.118
c0.013-0.014,0.031-0.023,0.044-0.038c0.043-0.052,0.069-0.112,0.103-0.169c0.024-0.04,0.054-0.076,0.072-0.118
c0.053-0.119,0.087-0.242,0.087-0.367V9.729V8.857V8.324V4.387V2.875V2.502V1.133C31.544,0.602,31.003,0.281,30.474,0.281z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="27.7578" y1="13.084" x2="6.7578" y2="1.334">
<stop offset="0" style="stop-color:#565759"/>
<stop offset="0.5" style="stop-color:#666669"/>
<stop offset="1" style="stop-color:#77787B"/>
</linearGradient>
<path fill="url(#SVGID_2_)" d="M30.631,0.16H1.26c-0.664,0-1.203,0.543-1.203,1.207v10.268c0,0.666,0.539,1.205,1.203,1.205h29.371
c0.662,0,1.313-0.652,1.313-1.318V1.256C31.943,0.57,31.275,0.16,30.631,0.16z M30.631,12.246H1.26
c-0.336,0-0.607-0.275-0.607-0.611V1.367c0-0.336,0.271-0.609,0.607-0.609h29.371c0.352,0,0.717,0.186,0.717,0.498v10.266
C31.348,11.859,30.965,12.246,30.631,12.246z"/>
<g>
<path d="M16.673,8.211h-1.367V7.406c0-0.288,0.047-0.504,0.143-0.647c0.098-0.145,0.289-0.292,0.576-0.444
c0.064-0.032,0.201-0.102,0.408-0.21c0.209-0.108,0.369-0.19,0.48-0.246c0.184-0.104,0.275-0.231,0.275-0.384V4.311
c0-0.088-0.031-0.162-0.096-0.222c-0.064-0.061-0.141-0.091-0.229-0.091H15.7c-0.088,0-0.164,0.03-0.227,0.091
c-0.064,0.06-0.096,0.134-0.096,0.222v0.696h-1.381V3.998c0-0.352,0.121-0.649,0.365-0.894s0.539-0.366,0.883-0.366h2.053
c0.352,0,0.65,0.122,0.898,0.366s0.373,0.542,0.373,0.894v1.86c0,0.2-0.041,0.368-0.121,0.504c-0.08,0.137-0.16,0.232-0.24,0.288
c-0.078,0.056-0.215,0.141-0.406,0.252c-0.064,0.04-0.16,0.095-0.289,0.162c-0.127,0.068-0.24,0.13-0.336,0.186
c-0.096,0.057-0.172,0.101-0.229,0.133c-0.184,0.111-0.275,0.235-0.275,0.372V8.211z M16.806,10.73h-1.561V9.087h1.561V10.73z"/>
</g>
<g>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="18.7041" y1="8.6094" x2="12.9541" y2="3.2764">
<stop offset="0" style="stop-color:#565759"/>
<stop offset="0.5" style="stop-color:#666669"/>
<stop offset="1" style="stop-color:#77787B"/>
</linearGradient>
<path fill="url(#SVGID_3_)" d="M16.36,7.773h-1.367V6.969c0-0.288,0.047-0.504,0.143-0.647c0.098-0.145,0.289-0.292,0.576-0.444
c0.064-0.032,0.201-0.102,0.408-0.21c0.209-0.108,0.369-0.19,0.48-0.246c0.184-0.104,0.275-0.231,0.275-0.384V3.873
c0-0.088-0.031-0.162-0.096-0.222c-0.064-0.061-0.141-0.091-0.229-0.091h-1.164c-0.088,0-0.164,0.03-0.227,0.091
c-0.064,0.06-0.096,0.134-0.096,0.222v0.696h-1.381V3.561c0-0.352,0.121-0.649,0.365-0.894s0.539-0.366,0.883-0.366h2.053
c0.352,0,0.65,0.122,0.898,0.366s0.373,0.542,0.373,0.894v1.86c0,0.2-0.041,0.368-0.121,0.504c-0.08,0.137-0.16,0.232-0.24,0.288
c-0.078,0.056-0.215,0.141-0.406,0.252c-0.064,0.04-0.16,0.095-0.289,0.162c-0.127,0.068-0.24,0.13-0.336,0.186
c-0.096,0.057-0.172,0.101-0.229,0.133c-0.184,0.111-0.275,0.235-0.275,0.372V7.773z M16.493,10.293h-1.561V8.649h1.561V10.293z"
/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB