18 results

@using System;
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent

<root>
    <div class="crosshair" style="
		position: absolute;
		left: @( IsPercentage ? $"{Position.x}%"  : Position.x );
		top: @( IsPercentage ? $"{Position.y}%" : Position.y );
		transform: translate(-50%, -50%);
	">
		<div class="center-dot-border-wrapper" style="
			display: @(CenterDot ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
			z-index: 100;
		">
			<div class="center-dot" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(CenterDotOpacity));
				padding: @(CenterDotThickness)px;
			"></div>
		</div>
		<div class="inner-top-border-wrapper" style="
			position: absolute;
    		top: -@(InnerLinesOffset)px;
    		left: 50%;
    		transform: translateX(-50%);
			display:@(ShowInnerLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="inner-line top" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(InnerLineOpacity));
				padding-left: @(InnerLineThickness)px;
    			padding-top: @(InnerLineLenght)px;
			"></div>
		</div>
		<div class="inner-bottom-border-wrapper" style="
			position: absolute;
    		bottom: -@(InnerLinesOffset)px;
    		left: 50%;
    		transform: translateX(-50%);
			display:@(ShowInnerLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="inner-line bottom" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(InnerLineOpacity));
				padding-left: @(InnerLineThickness)px;
    			padding-bottom: @(InnerLineLenght)px;
			"></div>
		</div>
		<div class="inner-left-border-wrapper" style="
			position: absolute;
			left: -@(InnerLinesOffset)px;
			top: 50%;
			transform: translateY(-50%);
			display:@(ShowInnerLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="inner-line left" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(InnerLineOpacity));
				padding-top: @(InnerLineThickness)px;
				padding-left: @(InnerLineLenght)px;
			"></div>
		</div>
		<div class="inner-right-border-wrapper" style="
			position: absolute;
			right: -@(InnerLinesOffset)px;
			top: 50%;
			transform: translateY(-50%);
			display: @(ShowInnerLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="inner-line right" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(InnerLineOpacity));
				padding-top: @(InnerLineThickness)px;
				padding-right: @(InnerLineLenght)px;
			"></div>
		</div>
		<div class="outer-top-border-wrapper" style="
			position: absolute;
			top: -@(OuterLineOffset)px;
			left: 50%;
			transform: translateX(-50%);
			display: @(ShowOuterLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="outer-line top" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(OuterLineOpacity));
				padding-left: @(OuterLineThickness)px;
				padding-top: @(OuterLineLenght)px;
			"></div>
		</div>
		<div class="outer-bottom-border-wrapper" style="
			position: absolute;
			bottom: -@(OuterLineOffset)px;
			left: 50%;
			transform: translateX(-50%);
			display: @(ShowOuterLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="outer-line bottom" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(OuterLineOpacity));
				padding-left: @(OuterLineThickness)px;
				padding-bottom: @(OuterLineLenght)px;
			"></div>
		</div>
		<div class="outer-left-border-wrapper" style="
			position: absolute;
			left: -@(OuterLineOffset)px;
			top: 50%;
			transform: translateY(-50%);
			display: @(ShowOuterLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="outer-line left" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(OuterLineOpacity));
				padding-top: @(OuterLineThickness)px;
				padding-left: @(OuterLineLenght)px;
			"></div>
		</div>
		<div class="outer-right-border-wrapper" style="
			position: absolute;
			right: -@(OuterLineOffset)px;
			top: 50%;
			transform: translateY(-50%);
			display: @(ShowOuterLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="outer-line right" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(OuterLineOpacity));
				padding-top: @(OuterLineThickness)px;
				padding-right: @(OuterLineLenght)px;
			"></div>
		</div>
    </div>
</root>

@code
{
	// Position properties
	[Property] 
	[Category("Position")] public bool IsPercentage { get; set; } = true;
	[Property]
	[Category("Position")] public Vector2 Position { get; set; } = new Vector2(50, 50);

	// Crosshair properties
	[Property]
	[Category("Crosshair")] public Color Color { get; set; } = Color.FromRgb(0x2EFF00);
	[Property]
	[Category("Crosshair")] public bool Outline { get; set; } = true;
	[Property]
	[Range(0f, 1f, 0.001f)]
	[Category("Crosshair")] public float OutlineOpacity { get; set; } = 1f;
	[Property]
	[Range(1, 10, 1)]
	[Category("Crosshair")] public int OutlineThickness { get; set; } = 2;
	[Property]
	[Category("Crosshair")] public bool CenterDot { get; set; } = true;
	[Property]
	[Range(0f, 1f, 0.001f)]
	[Category("Crosshair")] public float CenterDotOpacity { get; set; } = 1f;
	[Property]
	[Range(1, 10, 1)]
	[Category("Crosshair")] public int CenterDotThickness { get; set; } = 1;

	// Inner Lines properties
	[Property]
	[Category("Inner Lines")] public bool ShowInnerLines { get; set; } = true;
	[Property]
	[Range(0f, 1f, 0.001f)]
	[Category("Inner Lines")] public float InnerLineOpacity { get; set; } = 1f;
	[Property]
	[Range(0, 20, 1)]
	[Category("Inner Lines")] public int InnerLineLenght { get; set; } = 10;
	[Property]
	[Range(0, 10, 1)]
	[Category("Inner Lines")] public int InnerLineThickness { get; set; } = 1;
	[Property]
	[Range(0, 20, 1)]
	[Category("Inner Lines")] public int InnerLinesOffset { get; set; } = 2;

	// Outer Lines properties
	[Property]
	[Category("Outer Lines")] public bool ShowOuterLines { get; set; } = false;
	[Property]
	[Range(0f, 1f, 0.001f)]
	[Category("Outer Lines")] public float OuterLineOpacity { get; set; } = 0.5f;
	[Property]
	[Range(0, 20, 1)]
	[Category("Outer Lines")] public int OuterLineLenght { get; set; }
	[Property]
	[Range(0, 10, 1)]
	[Category("Outer Lines")] public int OuterLineThickness { get; set; }
	[Property]
	[Range(0, 20, 1)]
	[Category("Outer Lines")] public int OuterLineOffset { get; set; }

	// Code properties
	[Property]
	[Category("Code")] public string Code { get; set; }

	// Member variables
	private string _cachedCode = null;

	public string EncodeCrosshairParameters()
	{
		// Convert boolean values to 1 or 0
		string EncodeBool(bool value) => value ? "1" : "0";
		// Convert float values to a shortened string representation (up to 2 decimal places)
		string EncodeFloat(float value) => Math.Round(value, 2).ToString("0.##");
		// Convert integers directly to string
		string EncodeInt(int value) => value.ToString();

		// Concatenate all parameters into a shortened format
		var parameters = $"{EncodeBool(IsPercentage)}," +
							$"{EncodeFloat(Position.x)},{EncodeFloat(Position.y)}," +
							$"{EncodeFloat(Color.r)},{EncodeFloat(Color.g)},{EncodeFloat(Color.b)}," +
							$"{EncodeBool(Outline)},{EncodeFloat(OutlineOpacity)},{EncodeInt(OutlineThickness)}," +
							$"{EncodeBool(CenterDot)},{EncodeFloat(CenterDotOpacity)},{EncodeInt(CenterDotThickness)}," +
							$"{EncodeBool(ShowInnerLines)},{EncodeFloat(InnerLineOpacity)},{EncodeInt(InnerLineLenght)}," +
							$"{EncodeInt(InnerLineThickness)},{EncodeInt(InnerLinesOffset)}," +
							$"{EncodeBool(ShowOuterLines)},{EncodeFloat(OuterLineOpacity)},{EncodeInt(OuterLineLenght)}," +
							$"{EncodeInt(OuterLineThickness)},{EncodeInt(OuterLineOffset)}";
		return parameters;
	}

	public void DecodeCrosshairParameters(string encodedString)
	{
		var parameters = encodedString.Split(',');

		// Convert "1" or "0" back to boolean
		bool DecodeBool(string value) => value == "1";
		// Convert string back to float
		float DecodeFloat(string value) => float.Parse(value);
		// Convert string back to int
		int DecodeInt(string value) => int.Parse(value);

		// Assign the decoded values back to the properties
		IsPercentage = DecodeBool(parameters[0]);
		Position = new Vector2(DecodeFloat(parameters[1]), DecodeFloat(parameters[2]));
		Color = new Color(DecodeFloat(parameters[3]), DecodeFloat(parameters[4]), DecodeFloat(parameters[5]));
		Outline = DecodeBool(parameters[6]);
		OutlineOpacity = DecodeFloat(parameters[7]);
		OutlineThickness = DecodeInt(parameters[8]);
		CenterDot = DecodeBool(parameters[9]);
		CenterDotOpacity = DecodeFloat(parameters[10]);
		CenterDotThickness = DecodeInt(parameters[11]);
		ShowInnerLines = DecodeBool(parameters[12]);
		InnerLineOpacity = DecodeFloat(parameters[13]);
		InnerLineLenght = DecodeInt(parameters[14]);
		InnerLineThickness = DecodeInt(parameters[15]);
		InnerLinesOffset = DecodeInt(parameters[16]);
		ShowOuterLines = DecodeBool(parameters[17]);
		OuterLineOpacity = DecodeFloat(parameters[18]);
		OuterLineLenght = DecodeInt(parameters[19]);
		OuterLineThickness = DecodeInt(parameters[20]);
		OuterLineOffset = DecodeInt(parameters[21]);
	}

	protected override void OnStart()
	{
		if (Code == null)
		{
			Code = EncodeCrosshairParameters();
		}
		DecodeCrosshairParameters(Code);
		_cachedCode = Code;
	}

	protected override void OnUpdate()
	{
		if (Code != _cachedCode)
		{
			DecodeCrosshairParameters(Code);
		}
		Code = EncodeCrosshairParameters();
		_cachedCode = Code;
	}

	/// <summary>
	/// the hash determines if the system should be rebuilt. If it changes, it will be rebuilt
	/// </summary>
	protected override int BuildHash()
	{
		var positionHash = System.HashCode.Combine( 
			IsPercentage,
			Position
		);

		var crosshairHash = System.HashCode.Combine( 
			Color,
			Outline,
			OutlineOpacity,
			OutlineThickness,
			CenterDot,
			CenterDotOpacity,
			CenterDotThickness
		);

		var innerLinesHash = System.HashCode.Combine(
			ShowInnerLines,
			InnerLineOpacity,
			InnerLineLenght,
			InnerLineThickness,
			InnerLinesOffset
		);

		var outerLinesHash = System.HashCode.Combine(
			ShowOuterLines,
			OuterLineOpacity,
			OuterLineLenght,
			OuterLineThickness,
			OuterLineOffset
		);

		var codeHash = Code.GetHashCode();
		
		return System.HashCode.Combine(
			positionHash,
			crosshairHash,
			innerLinesHash,
			outerLinesHash,
			codeHash
		);
	}
}
@using System
@using System.Threading.Tasks
@using Sandbox
@using Sandbox.UI
@inherits WebPanel

<style>
	/* .scss files don't work in libraries. https://github.com/Facepunch/sbox-issues/issues/4813 */
	prompt {
		width: 100%;
		pointer-events: all;
	}
</style>

@code {
	public readonly TaskCompletionSource<bool> Purchased;
	
	public Prompt( string URL )
	{
		Url = URL;
		Purchased = new TaskCompletionSource<bool>();

		if ( !Game.ActiveScene.Components.TryGet( out ScreenPanel panel ) )
		{
			panel = Game.ActiveScene.Components.Create<ScreenPanel>();
			panel.GetPanel().ElementName = "monetization";
			panel.ZIndex = int.MaxValue;
		}

		Parent = panel.GetPanel();
	}
	
	public override void OnDeleted()
	{
		if ( !Game.ActiveScene.Components.TryGet( out ScreenPanel panel ) )
		{
			return;
		}
		
		if ( panel.GetPanel().HasChildren )
		{
			return;
		}
		
		panel.Destroy();
	}

	protected override void OnAfterTreeRender( bool firstTime )
	{
		// This was the only way I could think of to get the
		// Steam browser to send data back data to the game.
		// If you have a better way let me know.
		
		switch ( Surface.PageTitle )
		{
			case "finished": Delete();
				break;
			case "purchased": Purchased.TrySetResult( true );
				break;
			case "cancelled": Purchased.TrySetResult( false );
				break;
		}
	}
	
	protected override int BuildHash() => HashCode.Combine( Surface.PageTitle );
}
@using Sandbox;
@using Sandbox.Audio;
@using Sandbox.UI;
@using Sandbox.Services;
@using System;
@using System.Collections.Generic;
@using System.Linq;
@inherits PanelComponent

<root class="menu">
    <div class="container">
        <div class="title">
            <span>Options</span>
        </div>
        @if (activeTab != null)
        {
            <div class="content tabs-container">
                <div class="tabs-group">
                    @foreach (var tab in tabs)
                    {
                        if (tab == null)
                            continue;

                        <div class="button red @(tab == activeTab ? "active" : "inactive")" onclick="@(() => activeTab = tab)">
                            @tab.tabName
                        </div>
                    }
                </div>
            </div>
            <div class="content tab-content" CanDragScroll="false">
                <div class="table">
                    @foreach (var group in activeTab.groups)
                    {
                        <div class="row title">
                            <div class="label">@group.groupName</div>
                        </div>

                        foreach (var element in group.elements)
                        {
                            var elementType = element.GetType();

                            <div class="row setting">
                                <div class="column key">
                                    <div class="label">@element.displayName:</div>
                                </div>

                                @if (element is UIToggle)
                                {
                                    var toggleElement = (UIToggle)element;
                                    <div class="column value">
                                        <div class="buttons">
                                            <div class="button @GetToggleColor(true, toggleElement.getter())" onclick="@(() => toggleElement.setter(false))">Off</div>
                                            <div class="button @GetToggleColor(false, toggleElement.getter())" onclick="@(() => toggleElement.setter(true))">On</div>
                                        </div>
                                    </div>
                                }
                                @if (element is UISlider)
                                {
                                    var sliderElement = (UISlider)element;
                                    <div class="column value">
                                        <div class="slider">
                                            <SliderControl Value="@(sliderElement.getter())" OnValueChanged="@(sliderElement.setter)" Min="@(sliderElement.min)" Max="@(sliderElement.max)" Step="@(sliderElement.step)"></SliderControl>
                                        </div>
                                    </div>
                                }
                                @if (element is UICyclerBase)
                                {
                                    var cyclerElement = (UICyclerBase)element;
                                    <div class="column value">
                                        <div class="cycling-selector">
                                            <div class="cycling-controls">
                                                <button class="arrow left" onclick="@(() => cyclerElement.CycleLeft())">&#8249;</button>
                                                <span class="value" id="current-value">@cyclerElement.onGet()</span>
                                                <button class="arrow right" onclick="@(() => cyclerElement.CycleRight())">&#8250;</button>
                                            </div>
                                        </div>
                                    </div>
                                }
                            </div>
                        }
                    }
                </div>
            </div>
        }
        <div class="menu buttons">
            <div class="button red" onclick="@(() => ButtonClose())">
                Close
            </div>
        </div>
    </div>
</root>

@code
{
    public static string GetToggleColor(bool isOff, bool value)
    {
        if (isOff)
        {
            return value ? "gray" : "red";
        }
        return value ? "green" : "gray";
    }


    public List<UITab> tabs { get; set; } = new List<UITab>();
    public UITab activeTab { get; set; }

    protected override void OnAwake()
    {
        base.OnAwake();

        var sortedValues = EasySaveNonGenericBase.typeToInst.Values.OrderBy(v => (v.uiTab != null) ? v.uiTab.order : 99999).ToList();

        foreach (var sortedValue in sortedValues)
        {
            if (sortedValue == null)
                continue;

            tabs.Add(sortedValue.uiTab);
        }

        if (tabs != null && tabs.Count() > 0)
        {
            activeTab = tabs[0];
        }
    }

    protected override void OnUpdate()
    {
        base.OnUpdate();

        if (Input.EscapePressed)
        {
            Input.EscapePressed = false;
            ButtonClose();
        }
    }

    [ConCmd("open_options")]
    public static void OpenOptions()
    {
        var inst = Game.ActiveScene.Components.Get<OptionsScreen>(true);
        if (inst != null)
        {
            inst.Enabled = true;
        }
        else
        {
            var optionsScreenGO = Game.ActiveScene.CreateObject();
            var screenPanel = optionsScreenGO.Components.Create<ScreenPanel>();
            var optionsScreen = optionsScreenGO.Components.Create<OptionsScreen>();
        }
    }

    void ButtonClose()
    {
        var soundHandle = Sound.Play("ui.navigate.back");
        soundHandle.TargetMixer = Mixer.FindMixerByName("UI");
        Close();
    }

    public void Close()
    {
        this.Enabled = false;
    }

    public bool wasMouseVisible { get; set; } = false;
    protected override void OnEnabled()
    {
        wasMouseVisible = Mouse.Visible;
        Mouse.Visible = true;
    }

    protected override void OnDisabled()
    {
        EasySaveNonGenericBase.SaveAll();
        Mouse.Visible = wasMouseVisible;
    }

    /// <summary>
    /// update every second
    /// </summary>
    protected override int BuildHash() => System.HashCode.Combine(RealTime.Now.CeilToInt());
}
@using Sandbox;
@inherits PanelComponent

@if (InSequence)
{
	<root>
		<div class="Main">
			<div style="opacity: @Opacity">
				<img src="@CurrentEntry.TexturePath" style="transform: scale(@Scale);">
			</div>
		</div>
	</root>
}
@using Sandbox.UI;
@namespace Duccsoft
@inherits Panel

<root class=@OverlayClass>
    <image id="spinner" src="ui/img/BufferingCircle.png"/>
    <div id="bottomBar">
        <div class="button symbol" onclick=@TogglePause>@PlayButtonIcon</div>
        <div id="timecodeArea" class=@TimecodeAreaClass>
            <div class="progress time">@ProgressText</div>
            <div class="progress center">/</div>
            <div class="progress time right">@DurationText</div>
        </div>
        <SliderControl id="progressBar" Min=@(0) Max=@DurationSeconds 
            Value:bind=@PlaybackTime OnValueChanged=@ProgressBarChanged ShowValueTooltip=@false/>
        <div class="button symbol" onclick=@ToggleMute>@VolumeButtonIcon</div>
    </div>
</root>
@using System.Collections.Generic;
@using System.Linq;
@using Sandbox;
@using Sandbox.UI;
@namespace TwitchAPI.Examples

<root>
    <div class="username">
        <div class="badges">
            @foreach (var badge in ChatMessage.User.Badges)
            {
                <div class="badge @badge" />
            }
        </div>
        @if (HasAvatarImages)
        {
            <img class="avatar" [email protected]() />
        }
        <label class="name" style="color: @ChatMessage.User.Color.Hex">@ChatMessage.Username</label>
        <label>:</label>
    </div>
    <div class="message">
        @foreach (var fragment in Fragments)
        {
            if (fragment.Emote is not null)
            {
                <img class="emote" src="@fragment.Emote" />
            }
            else
            {
                <label>@fragment.Text</label>
            }
        }
    </div>
</root>

@code
{
    public TwitchChatMessage ChatMessage { get; set; }
    public float Lifetime { get; set; } = 10f;
    public float FadeTime { get; set; } = 0.5f;
    public bool HasAvatarImages { get; set; } = false;
    List<MessageFragment> Fragments { get; set; } = new();

    TimeSince TimeSinceCreated = 0f;

    record MessageFragment(string Text, string Emote);

    protected override void OnParametersSet()
    {
        base.OnParametersSet();

        Fragments.Clear();
        var text = ChatMessage.Message;
        var emotes = ChatMessage.Emotes;
        var currentEmote = emotes.FirstOrDefault();
        var currentIndex = 0;
        while (currentEmote is not null)
        {
            var textPrior = text.Substring(currentIndex, currentEmote.StartingCharacter - currentIndex);
            if (!string.IsNullOrWhiteSpace(textPrior))
            {
                foreach (var word in textPrior.Split(' '))
                {
                    if (string.IsNullOrWhiteSpace(word)) continue;
                    Fragments.Add(new MessageFragment(word, null));
                }
            }

            var emoteText = text.Substring(currentEmote.StartingCharacter, currentEmote.EndingCharacter - currentEmote.StartingCharacter + 1);
            Fragments.Add(new MessageFragment(emoteText, currentEmote.GetImageUrl()));

            currentIndex = currentEmote.EndingCharacter + 1;
            currentEmote = emotes.FirstOrDefault(e => e.StartingCharacter >= currentIndex);
        }

        var textAfter = text.Substring(currentIndex);
        if (!string.IsNullOrWhiteSpace(textAfter))
        {
            foreach (var word in textAfter.Split(' '))
            {
                if (string.IsNullOrWhiteSpace(word)) continue;
                Fragments.Add(new MessageFragment(word, null));
            }
        }
    }

    public override void Tick()
    {
        if (TimeSinceCreated >= Lifetime)
        {
            var opacity = 1f - ((TimeSinceCreated - Lifetime) / FadeTime);
            if (opacity <= 0f)
            {
                Delete();
            }
            else
            {
                Style.Opacity = opacity;
            }
        }
    }
}
@using System;
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent

<root>
    <div class="crosshair" style="
		position: absolute;
		left: @( IsPercentage ? $"{Position.x}%"  : Position.x );
		top: @( IsPercentage ? $"{Position.y}%" : Position.y );
		transform: translate(-50%, -50%);
	">
		<div class="center-dot-border-wrapper" style="
			display: @(CenterDot ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
			z-index: 100;
		">
			<div class="center-dot" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(CenterDotOpacity));
				padding: @(CenterDotThickness)px;
			"></div>
		</div>
		<div class="inner-top-border-wrapper" style="
			position: absolute;
    		top: -@(InnerLinesOffset)px;
    		left: 50%;
    		transform: translateX(-50%);
			display:@(ShowInnerLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="inner-line top" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(InnerLineOpacity));
				padding-left: @(InnerLineThickness)px;
    			padding-top: @(InnerLineLenght)px;
			"></div>
		</div>
		<div class="inner-bottom-border-wrapper" style="
			position: absolute;
    		bottom: -@(InnerLinesOffset)px;
    		left: 50%;
    		transform: translateX(-50%);
			display:@(ShowInnerLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="inner-line bottom" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(InnerLineOpacity));
				padding-left: @(InnerLineThickness)px;
    			padding-bottom: @(InnerLineLenght)px;
			"></div>
		</div>
		<div class="inner-left-border-wrapper" style="
			position: absolute;
			left: -@(InnerLinesOffset)px;
			top: 50%;
			transform: translateY(-50%);
			display:@(ShowInnerLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="inner-line left" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(InnerLineOpacity));
				padding-top: @(InnerLineThickness)px;
				padding-left: @(InnerLineLenght)px;
			"></div>
		</div>
		<div class="inner-right-border-wrapper" style="
			position: absolute;
			right: -@(InnerLinesOffset)px;
			top: 50%;
			transform: translateY(-50%);
			display: @(ShowInnerLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="inner-line right" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(InnerLineOpacity));
				padding-top: @(InnerLineThickness)px;
				padding-right: @(InnerLineLenght)px;
			"></div>
		</div>
		<div class="outer-top-border-wrapper" style="
			position: absolute;
			top: -@(OuterLineOffset)px;
			left: 50%;
			transform: translateX(-50%);
			display: @(ShowOuterLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="outer-line top" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(OuterLineOpacity));
				padding-left: @(OuterLineThickness)px;
				padding-top: @(OuterLineLenght)px;
			"></div>
		</div>
		<div class="outer-bottom-border-wrapper" style="
			position: absolute;
			bottom: -@(OuterLineOffset)px;
			left: 50%;
			transform: translateX(-50%);
			display: @(ShowOuterLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="outer-line bottom" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(OuterLineOpacity));
				padding-left: @(OuterLineThickness)px;
				padding-bottom: @(OuterLineLenght)px;
			"></div>
		</div>
		<div class="outer-left-border-wrapper" style="
			position: absolute;
			left: -@(OuterLineOffset)px;
			top: 50%;
			transform: translateY(-50%);
			display: @(ShowOuterLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="outer-line left" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(OuterLineOpacity));
				padding-top: @(OuterLineThickness)px;
				padding-left: @(OuterLineLenght)px;
			"></div>
		</div>
		<div class="outer-right-border-wrapper" style="
			position: absolute;
			right: -@(OuterLineOffset)px;
			top: 50%;
			transform: translateY(-50%);
			display: @(ShowOuterLines ? "flex" : "none");
			border: @(Outline ? $"{OutlineThickness}px solid rgba(0, 0, 0, {OutlineOpacity})" : "none");
		">
			<div class="outer-line right" style="
				background-color: rgba(@(Color.r * 255), @(Color.g * 255), @(Color.b * 255), @(OuterLineOpacity));
				padding-top: @(OuterLineThickness)px;
				padding-right: @(OuterLineLenght)px;
			"></div>
		</div>
    </div>
</root>

@code
{
	// Position properties
	[Property] 
	[Category("Position")] public bool IsPercentage { get; set; } = true;
	[Property]
	[Category("Position")] public Vector2 Position { get; set; } = new Vector2(50, 50);

	// Crosshair properties
	[Property]
	[Category("Crosshair")] public Color Color { get; set; } = Color.FromRgb(0x2EFF00);
	[Property]
	[Category("Crosshair")] public bool Outline { get; set; } = true;
	[Property]
	[Range(0f, 1f, 0.001f)]
	[Category("Crosshair")] public float OutlineOpacity { get; set; } = 1f;
	[Property]
	[Range(1, 10, 1)]
	[Category("Crosshair")] public int OutlineThickness { get; set; } = 2;
	[Property]
	[Category("Crosshair")] public bool CenterDot { get; set; } = true;
	[Property]
	[Range(0f, 1f, 0.001f)]
	[Category("Crosshair")] public float CenterDotOpacity { get; set; } = 1f;
	[Property]
	[Range(1, 10, 1)]
	[Category("Crosshair")] public int CenterDotThickness { get; set; } = 1;

	// Inner Lines properties
	[Property]
	[Category("Inner Lines")] public bool ShowInnerLines { get; set; } = true;
	[Property]
	[Range(0f, 1f, 0.001f)]
	[Category("Inner Lines")] public float InnerLineOpacity { get; set; } = 1f;
	[Property]
	[Range(0, 20, 1)]
	[Category("Inner Lines")] public int InnerLineLenght { get; set; } = 10;
	[Property]
	[Range(0, 10, 1)]
	[Category("Inner Lines")] public int InnerLineThickness { get; set; } = 1;
	[Property]
	[Range(0, 20, 1)]
	[Category("Inner Lines")] public int InnerLinesOffset { get; set; } = 2;

	// Outer Lines properties
	[Property]
	[Category("Outer Lines")] public bool ShowOuterLines { get; set; } = false;
	[Property]
	[Range(0f, 1f, 0.001f)]
	[Category("Outer Lines")] public float OuterLineOpacity { get; set; } = 0.5f;
	[Property]
	[Range(0, 20, 1)]
	[Category("Outer Lines")] public int OuterLineLenght { get; set; }
	[Property]
	[Range(0, 10, 1)]
	[Category("Outer Lines")] public int OuterLineThickness { get; set; }
	[Property]
	[Range(0, 20, 1)]
	[Category("Outer Lines")] public int OuterLineOffset { get; set; }

	// Code properties
	[Property]
	[Category("Code")] public string Code { get; set; }

	// Member variables
	private string _cachedCode = null;

	public string EncodeCrosshairParameters()
	{
		// Convert boolean values to 1 or 0
		string EncodeBool(bool value) => value ? "1" : "0";
		// Convert float values to a shortened string representation (up to 2 decimal places)
		string EncodeFloat(float value) => Math.Round(value, 2).ToString("0.##");
		// Convert integers directly to string
		string EncodeInt(int value) => value.ToString();

		// Concatenate all parameters into a shortened format
		var parameters = $"{EncodeBool(IsPercentage)}," +
							$"{EncodeFloat(Position.x)},{EncodeFloat(Position.y)}," +
							$"{EncodeFloat(Color.r)},{EncodeFloat(Color.g)},{EncodeFloat(Color.b)}," +
							$"{EncodeBool(Outline)},{EncodeFloat(OutlineOpacity)},{EncodeInt(OutlineThickness)}," +
							$"{EncodeBool(CenterDot)},{EncodeFloat(CenterDotOpacity)},{EncodeInt(CenterDotThickness)}," +
							$"{EncodeBool(ShowInnerLines)},{EncodeFloat(InnerLineOpacity)},{EncodeInt(InnerLineLenght)}," +
							$"{EncodeInt(InnerLineThickness)},{EncodeInt(InnerLinesOffset)}," +
							$"{EncodeBool(ShowOuterLines)},{EncodeFloat(OuterLineOpacity)},{EncodeInt(OuterLineLenght)}," +
							$"{EncodeInt(OuterLineThickness)},{EncodeInt(OuterLineOffset)}";
		return parameters;
	}

	public void DecodeCrosshairParameters(string encodedString)
	{
		var parameters = encodedString.Split(',');

		// Convert "1" or "0" back to boolean
		bool DecodeBool(string value) => value == "1";
		// Convert string back to float
		float DecodeFloat(string value) => float.Parse(value);
		// Convert string back to int
		int DecodeInt(string value) => int.Parse(value);

		// Assign the decoded values back to the properties
		IsPercentage = DecodeBool(parameters[0]);
		Position = new Vector2(DecodeFloat(parameters[1]), DecodeFloat(parameters[2]));
		Color = new Color(DecodeFloat(parameters[3]), DecodeFloat(parameters[4]), DecodeFloat(parameters[5]));
		Outline = DecodeBool(parameters[6]);
		OutlineOpacity = DecodeFloat(parameters[7]);
		OutlineThickness = DecodeInt(parameters[8]);
		CenterDot = DecodeBool(parameters[9]);
		CenterDotOpacity = DecodeFloat(parameters[10]);
		CenterDotThickness = DecodeInt(parameters[11]);
		ShowInnerLines = DecodeBool(parameters[12]);
		InnerLineOpacity = DecodeFloat(parameters[13]);
		InnerLineLenght = DecodeInt(parameters[14]);
		InnerLineThickness = DecodeInt(parameters[15]);
		InnerLinesOffset = DecodeInt(parameters[16]);
		ShowOuterLines = DecodeBool(parameters[17]);
		OuterLineOpacity = DecodeFloat(parameters[18]);
		OuterLineLenght = DecodeInt(parameters[19]);
		OuterLineThickness = DecodeInt(parameters[20]);
		OuterLineOffset = DecodeInt(parameters[21]);
	}

	protected override void OnStart()
	{
		if (Code == null)
		{
			Code = EncodeCrosshairParameters();
		}
		DecodeCrosshairParameters(Code);
		_cachedCode = Code;
	}

	protected override void OnUpdate()
	{
		if (Code != _cachedCode)
		{
			DecodeCrosshairParameters(Code);
		}
		Code = EncodeCrosshairParameters();
		_cachedCode = Code;
	}

	/// <summary>
	/// the hash determines if the system should be rebuilt. If it changes, it will be rebuilt
	/// </summary>
	protected override int BuildHash()
	{
		var positionHash = System.HashCode.Combine( 
			IsPercentage,
			Position
		);

		var crosshairHash = System.HashCode.Combine( 
			Color,
			Outline,
			OutlineOpacity,
			OutlineThickness,
			CenterDot,
			CenterDotOpacity,
			CenterDotThickness
		);

		var innerLinesHash = System.HashCode.Combine(
			ShowInnerLines,
			InnerLineOpacity,
			InnerLineLenght,
			InnerLineThickness,
			InnerLinesOffset
		);

		var outerLinesHash = System.HashCode.Combine(
			ShowOuterLines,
			OuterLineOpacity,
			OuterLineLenght,
			OuterLineThickness,
			OuterLineOffset
		);

		var codeHash = Code.GetHashCode();
		
		return System.HashCode.Combine(
			positionHash,
			crosshairHash,
			innerLinesHash,
			outerLinesHash,
			codeHash
		);
	}
}
@using System.Collections.Generic;
@using System.Linq;
@using Sandbox;
@using Sandbox.UI;
@namespace TwitchAPI.Examples

<root>
    <div class="username">
        <div class="badges">
            @foreach (var badge in ChatMessage.User.Badges)
            {
                <div class="badge @badge" />
            }
        </div>
        @if (HasAvatarImages)
        {
            <img class="avatar" [email protected]() />
        }
        <label class="name" style="color: @ChatMessage.User.Color.Hex">@ChatMessage.Username</label>
        <label>:</label>
    </div>
    <div class="message">
        @foreach (var fragment in Fragments)
        {
            if (fragment.Emote is not null)
            {
                <img class="emote" src="@fragment.Emote" />
            }
            else
            {
                <label>@fragment.Text</label>
            }
        }
    </div>
</root>

@code
{
    public TwitchChatMessage ChatMessage { get; set; }
    public float Lifetime { get; set; } = 10f;
    public float FadeTime { get; set; } = 0.5f;
    public bool HasAvatarImages { get; set; } = false;
    List<MessageFragment> Fragments { get; set; } = new();

    TimeSince TimeSinceCreated = 0f;

    record MessageFragment(string Text, string Emote);

    protected override void OnParametersSet()
    {
        base.OnParametersSet();

        Fragments.Clear();
        var text = ChatMessage.Message;
        var emotes = ChatMessage.Emotes;
        var currentEmote = emotes.FirstOrDefault();
        var currentIndex = 0;
        while (currentEmote is not null)
        {
            var textPrior = text.Substring(currentIndex, currentEmote.StartingCharacter - currentIndex);
            if (!string.IsNullOrWhiteSpace(textPrior))
            {
                foreach (var word in textPrior.Split(' '))
                {
                    if (string.IsNullOrWhiteSpace(word)) continue;
                    Fragments.Add(new MessageFragment(word, null));
                }
            }

            var emoteText = text.Substring(currentEmote.StartingCharacter, currentEmote.EndingCharacter - currentEmote.StartingCharacter + 1);
            Fragments.Add(new MessageFragment(emoteText, currentEmote.GetImageUrl()));

            currentIndex = currentEmote.EndingCharacter + 1;
            currentEmote = emotes.FirstOrDefault(e => e.StartingCharacter >= currentIndex);
        }

        var textAfter = text.Substring(currentIndex);
        if (!string.IsNullOrWhiteSpace(textAfter))
        {
            foreach (var word in textAfter.Split(' '))
            {
                if (string.IsNullOrWhiteSpace(word)) continue;
                Fragments.Add(new MessageFragment(word, null));
            }
        }
    }

    public override void Tick()
    {
        if (TimeSinceCreated >= Lifetime)
        {
            var opacity = 1f - ((TimeSinceCreated - Lifetime) / FadeTime);
            if (opacity <= 0f)
            {
                Delete();
            }
            else
            {
                Style.Opacity = opacity;
            }
        }
    }
}
@using Sandbox;
@inherits PanelComponent

@if (InSequence)
{
	<root>
		<div class="Main">
			<div style="opacity: @Opacity">
				<img src="@CurrentEntry.TexturePath" style="transform: scale(@Scale);">
			</div>
		</div>
	</root>
}
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent

@if ( Network.OwnerConnection is Connection owner && owner != Connection.Local )
{
    <root>

        <div class="card">
            <div class="avatar" style="background-image: url( avatar:@owner.SteamId )"></div>
            <div class="name">@owner.DisplayName</div>
        </div>

    </root>
}

@code
{
    protected override void OnEnabled()
    {
        base.OnEnabled();
    }
    protected override int BuildHash() => System.HashCode.Combine(Network.OwnerConnection);
}
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent

<root>
	<div class="title">@MyStringValue</div>
</root>

@code
{

	[Property, TextArea] public string MyStringValue { get; set; } = "Hello World!";

	/// <summary>
	/// the hash determines if the system should be rebuilt. If it changes, it will be rebuilt
	/// </summary>
	protected override int BuildHash() => System.HashCode.Combine( MyStringValue );
}
@using System
@using System.Threading.Tasks
@using Sandbox
@using Sandbox.UI
@inherits WebPanel

<style>
	/* .scss files don't work in libraries. https://github.com/Facepunch/sbox-issues/issues/4813 */
	prompt {
		width: 100%;
		pointer-events: all;
	}
</style>

@code {
	public readonly TaskCompletionSource<bool> Purchased;
	
	public Prompt( string URL )
	{
		Url = URL;
		Purchased = new TaskCompletionSource<bool>();

		if ( !Game.ActiveScene.Components.TryGet( out ScreenPanel panel ) )
		{
			panel = Game.ActiveScene.Components.Create<ScreenPanel>();
			panel.GetPanel().ElementName = "monetization";
			panel.ZIndex = int.MaxValue;
		}

		Parent = panel.GetPanel();
	}
	
	public override void OnDeleted()
	{
		if ( !Game.ActiveScene.Components.TryGet( out ScreenPanel panel ) )
		{
			return;
		}
		
		if ( panel.GetPanel().HasChildren )
		{
			return;
		}
		
		panel.Destroy();
	}

	protected override void OnAfterTreeRender( bool firstTime )
	{
		// This was the only way I could think of to get the
		// Steam browser to send data back data to the game.
		// If you have a better way let me know.
		
		switch ( Surface.PageTitle )
		{
			case "finished": Delete();
				break;
			case "purchased": Purchased.TrySetResult( true );
				break;
			case "cancelled": Purchased.TrySetResult( false );
				break;
		}
	}
	
	protected override int BuildHash() => HashCode.Combine( Surface.PageTitle );
}
@using Sandbox.UI;
@namespace Duccsoft
@inherits Panel

<root class=@OverlayClass>
    <image id="spinner" src="ui/img/BufferingCircle.png"/>
    <div id="bottomBar">
        <div class="button symbol" onclick=@TogglePause>@PlayButtonIcon</div>
        <div id="timecodeArea" class=@TimecodeAreaClass>
            <div class="progress time">@ProgressText</div>
            <div class="progress center">/</div>
            <div class="progress time right">@DurationText</div>
        </div>
        <SliderControl id="progressBar" Min=@(0) Max=@DurationSeconds 
            Value:bind=@PlaybackTime OnValueChanged=@ProgressBarChanged ShowValueTooltip=@false/>
        <div class="button symbol" onclick=@ToggleMute>@VolumeButtonIcon</div>
    </div>
</root>
@using Sandbox.UI;
@namespace Duccsoft
@inherits Panel

<root class="video-texture">
    @if ( ShowControls )
    {
        <VideoControlOverlay VideoPanel=@this AutoHide=@AutoHideControls AutoHideDelay=@AutoHideDelay/>
    }
</root>
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
@namespace TwitchAPI.Examples

<root>
</root>

@code
{
	/// <summary>
	/// The username of the Twitch Chat to connect to.
	/// </summary>
	[Property] string Username { get; set; }

	/// <summary>
	/// Whether or not to include the user's avatar images in the chat.
	/// </summary>
	[Property] bool HasAvatarImages { get; set; } = false;

	/// <summary>
	/// How long a message should be displayed for (in seconds).
	/// </summary>
	[Property, Group("Timing")] float MessageTime { get; set; } = 30f;

	/// <summary>
	/// How long a message should take to fade out (in seconds).
	/// </summary>
	[Property, Group("Timing")] float FadeTime { get; set; } = 0.5f;

	/// <summary>
	/// The position of the chat on the screen.
	/// </summary>
	[Property, Group("Styling")] Vector2 ChatPosition { get; set; } = new(5f, 95f);

	/// <summary>
	/// The size of the chat. Set to (0, 0) to scale automatically.
	/// </summary>
	[Property, Group("Styling")] Vector2 ChatSize { get; set; } = new(400f, 600f);

	/// <summary>
	/// The background color of the chat.
	/// </summary>
	[Property, Group("Styling")] Color BackgroundColor { get; set; } = Color.Transparent;

	/// <summary>
	/// The color of the text in the chat.
	/// </summary>
	[Property, Group("Styling")] Color TextColor { get; set; } = Color.White;

	/// <summary>
	/// The size of the text in the chat.
	/// </summary>
	[Property, Group("Styling")] float TextSize { get; set; } = 16f;

	/// <summary>
	/// The font family to use for the text in the chat.
	/// </summary>
	[Property, Group("Styling")] string FontFamily { get; set; } = "Poppins";

	/// <summary>
	/// The size of the gaps between messages in the chat.
	/// </summary>
	[Property, Group("Styling")] float GapSize { get; set; } = 8f;

	/// <summary>
	/// The padding around messages in the chat.
	/// </summary>
	[Property, Group("Styling")] float Padding { get; set; } = 8f;

	/// <summary>
	/// Whether or not to reverse the order of messages in the chat.
	/// </summary>
	[Property, Group("Styling")] bool ReverseOrder { get; set; } = false;

	/// <summary>
	/// Whether or not to use custom images for subscriber badges.
	/// </summary>
	[Property, Group("Badges")] bool HasCustomSubscriberBadges { get; set; } = false;

	/// <summary>
	/// The custom subscriber badges to use.
	/// </summary>
	[Property, Group("Badges"), InlineEditor, ShowIf(nameof(TwitchChatExample.HasCustomSubscriberBadges), true)] CustomSubscriberBadges SubscriberBadges { get; set; } = new();

	TwitchChatConnection Twitch;

	protected override void OnEnabled()
	{
		if (string.IsNullOrWhiteSpace(Username)) return;
		Twitch = new TwitchChatConnection(Username);
		Twitch.OnMessageReceived += OnMessageReceived;
		Twitch.OnMessageRemoved += OnMessageRemoved;
		Twitch.OnChatCleared += OnChatCleared;
	}

	protected override void OnDisabled()
	{
		if (Twitch is not null)
		{
			Twitch.OnMessageReceived -= OnMessageReceived;
			Twitch.OnMessageRemoved -= OnMessageRemoved;
			Twitch.OnChatCleared -= OnChatCleared;
			Twitch.Dispose();
		}
	}

	protected override void OnUpdate()
	{
		Panel.Style.BackgroundColor = BackgroundColor;
		Panel.Style.FontColor = TextColor;
		Panel.Style.FontSize = TextSize;
		Panel.Style.FontFamily = FontFamily;
		Panel.Style.RowGap = GapSize;
		Panel.Style.Padding = Padding;
		Panel.Style.FlexDirection = ReverseOrder ? FlexDirection.ColumnReverse : FlexDirection.Column;
		Panel.Style.JustifyContent = Justify.FlexEnd;

		if (ChatSize == 0)
		{
			Panel.Style.Width = Length.Auto;
			Panel.Style.Height = Length.Auto;
		}
		else
		{
			Panel.Style.Width = Length.Pixels(ChatSize.x);
			Panel.Style.Height = Length.Pixels(ChatSize.y);
		}

		Panel.Style.MaxWidth = Panel.Style.Width;
		Panel.Style.MaxHeight = Panel.Style.Height;

		if (ChatPosition.x < 50f)
		{
			Panel.Style.Left = Length.Percent(ChatPosition.x);
			Panel.Style.Right = default;
		}
		else
		{
			Panel.Style.Left = default;
			Panel.Style.Right = Length.Percent((100f - ChatPosition.x));
		}

		if (ChatPosition.y < 50f)
		{
			Panel.Style.Top = Length.Percent(ChatPosition.y);
			Panel.Style.Bottom = default;
		}
		else
		{
			Panel.Style.Top = default;
			Panel.Style.Bottom = Length.Percent((100f - ChatPosition.y));
		}
	}

	void OnMessageReceived(TwitchChatMessage message)
	{
		var entry = Panel.AddChild<TwitchChatEntry>();
		entry.ChatMessage = message;
		entry.Lifetime = MessageTime;
		entry.HasAvatarImages = HasAvatarImages;
	}

	void OnMessageRemoved(TwitchChatMessage message)
	{
		foreach (var child in Panel.Children)
		{
			if (child is TwitchChatEntry entry && entry.ChatMessage == message)
			{
				entry.Delete();
				break;
			}
		}
	}

	void OnChatCleared()
	{
		Panel.DeleteChildren();
	}

	protected override int BuildHash() => System.HashCode.Combine(Username);
}
@using Sandbox.UI;
@namespace Duccsoft
@inherits Panel

<root class="video-texture">
    @if ( ShowControls )
    {
        <VideoControlOverlay VideoPanel=@this AutoHide=@AutoHideControls AutoHideDelay=@AutoHideDelay/>
    }
</root>
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
@namespace TwitchAPI.Examples

<root>
</root>

@code
{
	/// <summary>
	/// The username of the Twitch Chat to connect to.
	/// </summary>
	[Property] string Username { get; set; }

	/// <summary>
	/// Whether or not to include the user's avatar images in the chat.
	/// </summary>
	[Property] bool HasAvatarImages { get; set; } = false;

	/// <summary>
	/// How long a message should be displayed for (in seconds).
	/// </summary>
	[Property, Group("Timing")] float MessageTime { get; set; } = 30f;

	/// <summary>
	/// How long a message should take to fade out (in seconds).
	/// </summary>
	[Property, Group("Timing")] float FadeTime { get; set; } = 0.5f;

	/// <summary>
	/// The position of the chat on the screen.
	/// </summary>
	[Property, Group("Styling")] Vector2 ChatPosition { get; set; } = new(5f, 95f);

	/// <summary>
	/// The size of the chat. Set to (0, 0) to scale automatically.
	/// </summary>
	[Property, Group("Styling")] Vector2 ChatSize { get; set; } = new(400f, 600f);

	/// <summary>
	/// The background color of the chat.
	/// </summary>
	[Property, Group("Styling")] Color BackgroundColor { get; set; } = Color.Transparent;

	/// <summary>
	/// The color of the text in the chat.
	/// </summary>
	[Property, Group("Styling")] Color TextColor { get; set; } = Color.White;

	/// <summary>
	/// The size of the text in the chat.
	/// </summary>
	[Property, Group("Styling")] float TextSize { get; set; } = 16f;

	/// <summary>
	/// The font family to use for the text in the chat.
	/// </summary>
	[Property, Group("Styling")] string FontFamily { get; set; } = "Poppins";

	/// <summary>
	/// The size of the gaps between messages in the chat.
	/// </summary>
	[Property, Group("Styling")] float GapSize { get; set; } = 8f;

	/// <summary>
	/// The padding around messages in the chat.
	/// </summary>
	[Property, Group("Styling")] float Padding { get; set; } = 8f;

	/// <summary>
	/// Whether or not to reverse the order of messages in the chat.
	/// </summary>
	[Property, Group("Styling")] bool ReverseOrder { get; set; } = false;

	/// <summary>
	/// Whether or not to use custom images for subscriber badges.
	/// </summary>
	[Property, Group("Badges")] bool HasCustomSubscriberBadges { get; set; } = false;

	/// <summary>
	/// The custom subscriber badges to use.
	/// </summary>
	[Property, Group("Badges"), InlineEditor, ShowIf(nameof(TwitchChatExample.HasCustomSubscriberBadges), true)] CustomSubscriberBadges SubscriberBadges { get; set; } = new();

	TwitchChatConnection Twitch;

	protected override void OnEnabled()
	{
		if (string.IsNullOrWhiteSpace(Username)) return;
		Twitch = new TwitchChatConnection(Username);
		Twitch.OnMessageReceived += OnMessageReceived;
		Twitch.OnMessageRemoved += OnMessageRemoved;
		Twitch.OnChatCleared += OnChatCleared;
	}

	protected override void OnDisabled()
	{
		if (Twitch is not null)
		{
			Twitch.OnMessageReceived -= OnMessageReceived;
			Twitch.OnMessageRemoved -= OnMessageRemoved;
			Twitch.OnChatCleared -= OnChatCleared;
			Twitch.Dispose();
		}
	}

	protected override void OnUpdate()
	{
		Panel.Style.BackgroundColor = BackgroundColor;
		Panel.Style.FontColor = TextColor;
		Panel.Style.FontSize = TextSize;
		Panel.Style.FontFamily = FontFamily;
		Panel.Style.RowGap = GapSize;
		Panel.Style.Padding = Padding;
		Panel.Style.FlexDirection = ReverseOrder ? FlexDirection.ColumnReverse : FlexDirection.Column;
		Panel.Style.JustifyContent = Justify.FlexEnd;

		if (ChatSize == 0)
		{
			Panel.Style.Width = Length.Auto;
			Panel.Style.Height = Length.Auto;
		}
		else
		{
			Panel.Style.Width = Length.Pixels(ChatSize.x);
			Panel.Style.Height = Length.Pixels(ChatSize.y);
		}

		Panel.Style.MaxWidth = Panel.Style.Width;
		Panel.Style.MaxHeight = Panel.Style.Height;

		if (ChatPosition.x < 50f)
		{
			Panel.Style.Left = Length.Percent(ChatPosition.x);
			Panel.Style.Right = default;
		}
		else
		{
			Panel.Style.Left = default;
			Panel.Style.Right = Length.Percent((100f - ChatPosition.x));
		}

		if (ChatPosition.y < 50f)
		{
			Panel.Style.Top = Length.Percent(ChatPosition.y);
			Panel.Style.Bottom = default;
		}
		else
		{
			Panel.Style.Top = default;
			Panel.Style.Bottom = Length.Percent((100f - ChatPosition.y));
		}
	}

	void OnMessageReceived(TwitchChatMessage message)
	{
		var entry = Panel.AddChild<TwitchChatEntry>();
		entry.ChatMessage = message;
		entry.Lifetime = MessageTime;
		entry.HasAvatarImages = HasAvatarImages;
	}

	void OnMessageRemoved(TwitchChatMessage message)
	{
		foreach (var child in Panel.Children)
		{
			if (child is TwitchChatEntry entry && entry.ChatMessage == message)
			{
				entry.Delete();
				break;
			}
		}
	}

	void OnChatCleared()
	{
		Panel.DeleteChildren();
	}

	protected override int BuildHash() => System.HashCode.Combine(Username);
}
@using Sandbox;
@using Sandbox.UI;

@inherits PanelComponent

<style>
    Hud {
        pointer-events: all;
        width: 100%;
        height: 100%;
    }

	.hello {
		margin: 100px;
		font-size: 32px;
		color: red;
		text-shadow: 2px 2px 10px  black}

	.goodbye {
		margin: 200px;
		font-size: 64px;
		color: green;
		text-shadow: 5px 5px 20px  black}
</style>

<root>
	<panel class="hello" onmousedown="@Play" >
		Hello Razer. Ti gondone: @SomeNumber
	</panel>

	@if(ShowGoodbye)
	{
		<panel class="goodbye">
			Goodbye Lads!
		</panel>
	}
</root>

@code
{
	private int SomeNumber = 32;
	private bool ShowGoodbye = false;

	private void Play()
	{
		Log.Error("SEX TIME!");
		ShowGoodbye = !ShowGoodbye;
	}
}