ui/LoadoutItemPanel.razor

A Blazor-style Razor UI component for displaying a shop/loadout item card. It computes visual state (locked, affordable, owned, gem upgrade states), displays icon, price/upgrade cost, descriptions and gem level, and exposes Item and state properties plus an OnClick action.

File Access
@namespace Sandbox
@using Sandbox;
@using Sandbox.UI;
@using System;
@inherits Panel
@attribute [StyleSheet("LoadoutItemPanel.razor.scss")]

@{
	var isGem = Item.Category == ShopItemCategory.Gem;
	var owned = ProgressManager.IsItemOwned( Item.Id );
	var coins = ProgressManager.GetCoins();
	var canAfford = coins >= Item.Price;
	var isLocked = !ProgressManager.IsItemUnlocked( Item.Id );

	string stateClass;
	if ( IsShopMode )
	{
		if ( isLocked )
			stateClass = "locked";
		else if ( isGem )
		{
			if ( !owned )
				stateClass = canAfford ? "affordable" : "too_expensive";
			else if ( ProgressManager.IsGemMaxed( Item.Id ) )
				stateClass = "gem_maxed";
			else
			{
				var upgCost = ProgressManager.GetGemUpgradeCost( Item.Id );
				stateClass = (upgCost.HasValue && coins >= upgCost) ? "gem_upgradeable" : "gem_upgrade_expensive";
			}
		}
		else
			stateClass = owned ? "owned" : canAfford ? "affordable" : "too_expensive";
	}
	else
	{
		stateClass = IsSelected ? "selected" : IsSlotFull ? "slot_full" : "";
	}

	// Compute description text
	string descText = null;
	if ( isGem && (Item.GemDescription != null || Item.GemUpgradeDescription != null) )
	{
		var displayLevel = ProgressManager.GetGemDisplayLevel( Item.Id );
		if ( !owned )
			descText = Item.GemDescription?.Invoke( 1 );
		else if ( IsShopMode && !ProgressManager.IsGemMaxed( Item.Id ) )
			descText = Item.GemUpgradeDescription?.Invoke( displayLevel + 1 ) ?? Item.GemDescription?.Invoke( displayLevel );
		else
			descText = Item.GemDescription?.Invoke( displayLevel );
	}
	else if ( Item.ItemDescription != null )
	{
		descText = Item.ItemDescription();
	}
}

<root class="@stateClass" onclick=@OnClick>
	@{
		var frameBg = Item.Category switch
		{
			ShopItemCategory.Gun   => "/textures/ui/panel/item_gun.png",
			ShopItemCategory.Charm => "/textures/ui/panel/item_charm.png",
			ShopItemCategory.Gem   => "/textures/ui/panel/item_gem.png",
			_                      => "/textures/ui/panel/panel_02.png",
		};
	}
	<div class="frame" style="background-image: url(@frameBg);"></div>
	<div class="top_row">
		<div class="item_icon" style="background-image:url(@Item.IconPath);"></div>
		<div class="top_text">
			<label class="item_name">@Item.Name</label>
			@if ( IsShopMode )
			{
				<div class="item_price">
					@if ( isGem && owned )
					{
						var upgCost = ProgressManager.GetGemUpgradeCost( Item.Id );
						@if ( upgCost.HasValue )
						{
							<div class="price_coin_icon"></div>
							<label class="price_amount">@upgCost</label>
						}
						else
						{
							<label class="owned_label">Max</label>
						}
					}
					else if ( !owned )
					{
						<div class="price_coin_icon"></div>
						<label class="price_amount">@Item.Price</label>
					}
				</div>
			}
		</div>
	</div>
	<div class="middle">
		@if ( descText != null )
		{
			<RichText class="item_desc" Text=@descText />
		}
		@if ( IsShopMode && isLocked )
		{
			var needed = ProgressManager.GetPurchasesNeeded( Item.Id );
			var catWord = Item.Category == ShopItemCategory.Gun   ? (needed == 1 ? "gun"   : "guns")
			            : Item.Category == ShopItemCategory.Charm ? (needed == 1 ? "charm" : "charms")
			            :                                            (needed == 1 ? "gem"   : "gems");
			<label class="lock_label">Buy @needed more @catWord</label>
		}
	</div>
	@if ( isGem )
	{
		var maxLevel = (Item.UpgradePrices?.Length ?? 0) + 1;
		var gemDL = ProgressManager.GetGemDisplayLevel( Item.Id );
		// In shop mode: show the level being purchased next (matches the "Lv X" price label).
		// In loadout mode: show current level.
		var displayLevel = !IsShopMode ? gemDL
		                 : !owned      ? 1
		                 : ProgressManager.GetGemUpgradeCost( Item.Id ).HasValue ? gemDL + 1 : gemDL;
		<div class="level_indicator">
			<label>@displayLevel</label><label class="level_sep">/</label><label>@maxLevel</label>
		</div>
	}
</root>

@code {
	public ShopItemDef Item { get; set; }
	public Action OnClick { get; set; }
	/// <summary>True when used in the shop — shows price and lock info, computes shop state classes.</summary>
	public bool IsShopMode { get; set; }
	/// <summary>Loadout mode only — highlights the card as the active selection.</summary>
	public bool IsSelected { get; set; }
	/// <summary>Loadout mode only — dims the card when the slot is full and this item is not selected.</summary>
	public bool IsSlotFull { get; set; }

	protected override int BuildHash()
	{
		return System.HashCode.Combine(
			Item.Id,
			IsShopMode,
			IsSelected,
			IsSlotFull,
			ProgressManager.StateVersion
		);
	}
}