11096 results

global using static Sandbox.Internal.GlobalGameNamespace;
global using Microsoft.AspNetCore.Components;
global using Microsoft.AspNetCore.Components.Rendering;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "Twitch Poop" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "twitchpoop" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "garry" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "garry.twitchpoop" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "6/6/2026 7:39:31 PM" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "25" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]

[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.128.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.128.0")]
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
@namespace Sandbox

@*
    The floating name tag above an avatar. StreamPlayer.Setup() points its Viewer at the right viewer,
    and we render their display name in their chat colour.
*@

<root>
    <div class="username" style="@NameStyle">
        @Viewer.DisplayName
    </div>
</root>

@code
{
    /// <summary>
    /// The viewer this tag is labelling. Set by StreamPlayer when the avatar is spawned.
    /// </summary>
    public Streamer.Viewer Viewer { get; set; }

    /// <summary>
    /// Colour the name with the viewer's chat colour, if they have one set.
    /// </summary>
    string NameStyle => Viewer.Color.HasValue ? $"color: {Viewer.Color.Value.Hex}" : null;

    /// <summary>
    /// PanelComponent rebuilds the markup whenever this hash changes - so include anything we display.
    /// </summary>
    protected override int BuildHash() => System.HashCode.Combine( Viewer.DisplayName, Viewer.Color );
}

.switchcontrol
{
    flex-direction: row;
    width: 100px;
    min-height: 24px;
    align-items: center;
    cursor: pointer;

    .switch-frame
    {
        flex-grow: 0;
        flex-shrink: 1;
        width: 48px;
        height: 16px;
        background-color: #fff1;
        margin: 0px 5px;
        align-items: center;
        border-radius: 100px;
        transition: all 0.4s linear;

        .switch-inner
        {
            position: relative;
            flex-grow: 0;
            flex-shrink: 1;
            background-color: #999;
            width: 25px;
            height: 25px;
            border-radius: 100px;
            left: 20%;
            transform: translateX( -50% );
            transition: all 0.3s ease-out;
        }
    }

    &.active
    {
        .switch-frame
        {
            background-color: #fffa;
        }

        .switch-inner
        {
            left: 80%;
            background-color: #fff;
        }
    }
}
ColorAlphaControl
{
	gap: 0.5rem;
	flex-grow: 1;
	pointer-events: all;
	background: linear-gradient( to right, black, white );
	border-radius: 4px;
	padding: 2px;
	height: 12px;
	position: relative;
	cursor: pointer;
	border: 1px solid #333;

	&:hover
	{
		border: 1px solid #08f;
	}

	&:active
	{
		border: 1px solid #fff;
	}

	.handle
	{
		top: -5px;
		bottom: -5px;
		aspect-ratio: 1;
		border-radius: 100px;
		border: 2px solid #444;
		position: absolute;
		background-color: white;
		box-shadow: 2px 2px 16px #000a;
		transform: translateX( -50% );
		pointer-events: none;
	}
}
ColorSaturationValueControl
{
	width: 240px;
	height: 240px;
	background-color: red;
	position: relative;
	border-radius: 4px;
	cursor: pointer;
	border: 1px solid #333;

	&:hover
	{
		border: 1px solid #08f;
	}

	&:active
	{
		border: 1px solid #fff;
	}

	.handle
	{
		width: 16px;
		height: 16px;
		border-radius: 100px;
		border: 2px solid #444;
		position: absolute;
		background-color: white;
		box-shadow: 2px 2px 16px #000a;
		transform: translateX( -50% ) translateY( -50% );
		pointer-events: none;
		z-index: 100;
		z-index: 100;
	}

	.gradient
	{
		position: absolute;
		width: 100%;
		height: 100%;
		border-radius: 4px;
		background: linear-gradient( to right, white, rgba( 255, 255, 255, 0 ) );

		&:after
		{
			content: "";
			position: absolute;
			width: 100%;
			height: 100%;
			border-radius: 4px;
			background: linear-gradient( to top, black, rgba( 0, 0, 0, 0 ) );
		}
	}
}

.slidercontrol
{
    flex-direction: row;
    min-width: 50px;
    position: relative;
    flex-shrink: 0;
    flex-direction: row;
    cursor: pointer;
    gap: 8px;
    flex-grow: 1;
    align-items: center;
    pointer-events: all;

    > .inner
    {
        flex-direction: column;
        flex-shrink: 1;
        flex-grow: 1;
        min-height: 32px;
        justify-content: center;

        > .values
        {
            width: 100%;
            pointer-events: none;
            font-size: 14px;
            color: #aaa;

            > .left
            {
                flex-grow: 1;
            }
        }

        > .track
        {
            position: relative;
            background-color: #888;
            height: 7px;
            margin: 8px;
            align-items: center;
            border-radius: 4px;

            > .track-active
            {
                background-color: #fff;
                position: absolute;
                height: 100%;
                left: 0px;
                border-radius: 4px;
            }

            > .thumb
            {
                position: relative;
                background-color: #fff;
                border-radius: 100px;
                width: 16px;
                height: 16px;
                transform: translateX( -50% );
            }
        }
    }

    > .entry
    {
        flex-shrink: 0;
        flex-grow: 0;
        width: 50px;

        > numberentry
        {
            background-color: transparent;

            > .content-label
            {
                padding: 0 4px;
            }
        }
    }
}


.slidercontrol .value-tooltip
{
    position: absolute;
    bottom: 150%;
    left: -8px;
    z-index: 1000;
    flex-direction: column;

    > .label
    {       
        background-color: black;
        padding: 8px 12px;
        border-radius: 8px;
    }

    >.tail
    {
        bottom: -0px;
        background-color: black;
        width: 10px;
        height: 10px;
        transform: rotateZ(45 deg) translateX( 4px );
        position: absolute;
    }
}
using System;
using Sandbox;

namespace Goo.Animation;

public record struct SmoothVector2
{
    public Vector2 Current;
    public Vector2 Target;
    public Vector2 Velocity;
    public float SmoothTime;

    public SmoothVector2(Vector2 initial, float smoothTime)
    {
        Current = initial;
        Target = initial;
        Velocity = default;
        SmoothTime = smoothTime;
    }

    public void Update(float dt)
    {
        float vx = Velocity.x, vy = Velocity.y;
        Current = new Vector2(
            MathX.SmoothDamp(Current.x, Target.x, ref vx, SmoothTime, dt),
            MathX.SmoothDamp(Current.y, Target.y, ref vy, SmoothTime, dt));
        Velocity = new Vector2(vx, vy);
    }

    public bool IsSettled =>
        MathF.Abs(Target.x - Current.x) < 0.0001f &&
        MathF.Abs(Target.y - Current.y) < 0.0001f &&
        MathF.Abs(Velocity.x) < 0.0001f &&
        MathF.Abs(Velocity.y) < 0.0001f;

    /// <summary>Advances by dt and returns true while still moving; chain calls with | (not ||) so every damper advances each frame.</summary>
    public bool Tick(float dt) { Update(dt); return !IsSettled; }
}
using Sandbox;
using Sandbox.Rendering;
using Sandbox.UI;

namespace Goo;

// Style helpers hoisted so generated Blob facades share them. Keep the early-return form: an engine-type ternary that resolves bare null via an implicit string operator silently produces magenta (Color.Parse fallback) instead of the intended absent-property. See engine-fact memories.

internal static class StyleAccumulator
{
    static StyleList Rent(StyleList current)
        => ReferenceEquals(current, StyleList.Empty)
            ? BuildContext.Current.RentStyleList()
            : current;

    public static StyleList Add(StyleList current, StyleField field, Length? value)
    {
        if (!value.HasValue) return current;
        var list = Rent(current);
        list.Add(field, StyleValue.FromLength(value.Value));
        return list;
    }

    public static StyleList Add<TEnum>(StyleList current, StyleField field, TEnum? value, System.Func<TEnum, StyleValue> wrap) where TEnum : struct
    {
        if (!value.HasValue) return current;
        var list = Rent(current);
        list.Add(field, wrap(value.Value));
        return list;
    }

    public static StyleList Add(StyleList current, StyleField field, Color? value, System.Func<Color, StyleValue> wrap)
    {
        if (!value.HasValue) return current;
        var list = Rent(current);
        list.Add(field, wrap(value.Value));
        return list;
    }

    public static StyleList Add(StyleList current, StyleField field, string? value)
    {
        if (value is null) return current;
        var list = Rent(current);
        list.Add(field, StyleValue.FromString(value));
        return list;
    }

    public static StyleList Add(StyleList current, StyleField field, float? value)
    {
        if (!value.HasValue) return current;
        var list = Rent(current);
        list.Add(field, StyleValue.FromSingle(value.Value));
        return list;
    }

    public static StyleList Add(StyleList current, StyleField field, bool? value)
    {
        if (!value.HasValue) return current;
        var list = Rent(current);
        list.Add(field, StyleValue.FromBoolean(value.Value));
        return list;
    }

    public static StyleList Add(StyleList current, StyleField field, int? value)
    {
        if (!value.HasValue) return current;
        var list = Rent(current);
        list.Add(field, StyleValue.FromInt32(value.Value));
        return list;
    }

    public static StyleList Add(StyleList current, StyleField field, Texture? value)
    {
        if (value is null) return current;
        var list = Rent(current);
        list.Add(field, StyleValue.FromTexture(value));
        return list;
    }
}
using System;
using Sandbox;

namespace Goo.FpsUI;

// ============================================================================
//  ALL demo / self-preview behaviour for the FPS UI pack lives in this ONE file.
//
//  DELETE THIS FILE to remove demo functionality.
//  deleting this file makes every Demo inspector toggle disappear
// ============================================================================
static class FpsDemo
{
    static readonly string[] Names = { "Vex", "Korr", "Juno", "Rhys", "Mara", "Dane", "Iko", "Sol" };
    static readonly (int Amount, string Action)[] Grants =
    {
        (100, "Kill"), (125, "Headshot"), (50, "Assist"), (200, "Double Kill"), (25, "Hitmarker"),
    };

    public static void Health( HealthModel m, ref float t, float dt )  // bleed in chunks, big heal when low
    {
        t += dt;
        if ( t < 2.4f ) return;
        t = 0f;
        if ( m.Health <= m.MaxHealth * 0.3f ) m.Heal( m.MaxHealth * 0.65f );
        else m.Damage( m.MaxHealth * 0.22f );
    }

    public static void Stamina( StaminaModel m, ref float t, float dt )  // sprint on/off cycle
    {
        t += dt;
        if ( t >= 3.1f ) { t = 0f; m.SetSprinting( !m.Sprinting ); }
        if ( m.Stamina <= 0f ) m.SetSprinting( false );
    }

    public static void Ammo( AmmoModel m, ref float t, float dt )  // steady fire, auto-reload on empty
    {
        t += dt;
        if ( m.Reloading ) return;
        if ( m.Mag <= 0 ) { m.Reload(); return; }
        if ( t >= 0.18f ) { t = 0f; m.Fire(); }
    }

    public static void Crosshair( CrosshairModel m, ref float t, float dt )  // steady fire cadence
    {
        t += dt;
        if ( t >= 0.18f ) { t = 0f; m.Fire(); }
    }

    public static void Hitmarker( HitmarkerModel m, ref float t, float dt )  // periodic pop
    {
        t += dt;
        if ( t >= 0.5f ) { t = 0f; m.Pop( false ); }
    }

    public static void Killfeed( KillfeedModel m, ref float t, ref int pick, float dt )  // periodic fake kills
    {
        t += dt;
        if ( t < 2.2f ) return;
        t = 0f;
        string a = Names[pick % Names.Length];
        string v = Names[(pick + 3) % Names.Length];
        // Alternate teams, occasionally make the local player the killer so the preview shows both.
        bool youKill = pick % 3 == 0;
        KillTeam at = youKill || pick % 2 == 0 ? KillTeam.Friendly : KillTeam.Enemy;
        KillTeam vt = at == KillTeam.Friendly ? KillTeam.Enemy : KillTeam.Friendly;
        pick++;
        m.Add( a, at, v, vt, attackerLocal: youKill );
    }

    public static void Xp( XpModel m, ref float t, ref int pick, float dt )  // periodic grants
    {
        t += dt;
        if ( t < 1.6f ) return;
        t = 0f;
        var g = Grants[pick % Grants.Length];
        pick++;
        m.Add( g.Amount, g.Action );
    }

    public static void Scoreboard( ScoreboardModel m, ref float t, float dt )  // loop the clock, nudge scores, cycle variant
    {
        if ( m.TimeRemaining <= 0f ) m.TimeRemaining = 600f;
        t += dt;
        if ( t < 4f ) return;
        t = 0f;
        m.Mode = m.Mode switch
        {
            ScoreboardMode.Tdm        => ScoreboardMode.Domination,
            ScoreboardMode.Domination => ScoreboardMode.Ffa,
            _                         => ScoreboardMode.Tdm,
        };
        m.FriendlyScore = (m.FriendlyScore + 7) % (m.ScoreLimit + 1);
        m.EnemyScore    = (m.EnemyScore + 5) % (m.ScoreLimit + 1);
        m.PlayerScore  += 150;
        m.LeaderScore   = Math.Max( m.LeaderScore, m.PlayerScore ) + 50;
        for ( int i = 0; i < m.Points.Length; i++ )  // rotate cap ownership
            m.Points[i] = (CapOwner)(((int)m.Points[i] + 1) % 3);
    }
}

// ---- per-widget demo hooks (the [Property] Demo toggle + timers + StepDemo body) ----

public sealed partial class HealthWidget
{
    [Property] public bool Demo { get; set; } = true;  // self-animate in editor
    float _demoT;
    partial void StepDemo( float dt, ref bool active )
    {
        if ( !Demo ) return;
        active = true;
        FpsDemo.Health( _m, ref _demoT, dt );
    }
}

public sealed partial class StaminaWidget
{
    [Property] public bool Demo { get; set; } = true;  // self-animate in editor
    float _demoT;
    partial void StepDemo( float dt, ref bool active )
    {
        if ( !Demo ) return;
        active = true;
        FpsDemo.Stamina( _m, ref _demoT, dt );
    }
}

public sealed partial class AmmoWidget
{
    [Property] public bool Demo { get; set; } = true;  // self-fire in editor
    float _demoT;
    partial void StepDemo( float dt, ref bool active )
    {
        if ( !Demo ) return;
        active = true;
        FpsDemo.Ammo( _m, ref _demoT, dt );
    }
}

public sealed partial class CrosshairWidget
{
    [Property] public bool Demo { get; set; } = true;  // self-fire in editor
    float _demoT;
    partial void StepDemo( float dt, ref bool active )
    {
        if ( !Demo ) return;
        active = true;
        FpsDemo.Crosshair( _m, ref _demoT, dt );
    }
}

public sealed partial class HitmarkerWidget
{
    [Property] public bool Demo { get; set; } = true;  // periodic pop in editor
    float _demoT;
    partial void StepDemo( float dt, ref bool active )
    {
        if ( !Demo ) return;
        active = true;
        FpsDemo.Hitmarker( _m, ref _demoT, dt );
    }
}

public sealed partial class KillfeedWidget
{
    [Property] public bool Demo { get; set; } = true;  // fake kills in editor
    float _demoT;
    int _demoPick;
    partial void StepDemo( float dt, ref bool active )
    {
        if ( !Demo ) return;
        active = true;
        FpsDemo.Killfeed( _m, ref _demoT, ref _demoPick, dt );
    }
}

public sealed partial class XpWidget
{
    [Property] public bool Demo { get; set; } = true;  // fake grants in editor
    float _demoT;
    int _demoPick;
    partial void StepDemo( float dt, ref bool active )
    {
        if ( !Demo ) return;
        active = true;
        FpsDemo.Xp( _m, ref _demoT, ref _demoPick, dt );
    }
}

public sealed partial class ScoreboardWidget
{
    [Property] public bool Demo { get; set; } = true;  // self-animate in editor
    float _demoT;
    partial void StepDemo( float dt, ref bool active )
    {
        if ( !Demo ) return;
        active = true;
        FpsDemo.Scoreboard( _m, ref _demoT, dt );
    }
}

// ---- the assembled HUD: one coordinated synthetic firefight ----

public sealed partial class FpsHud
{
    [Property, Group( "General" )] public bool Demo { get; set; } = true;  // self-run a synthetic firefight in editor

    readonly Random _demoRng = new();
    float _hpT, _stT, _kfT, _xpT, _sbT;  // per-subsystem demo timers
    int _kfPick, _xpPick;                // demo name / grant cursors
    float _triggerT, _modeT;             // synthetic trigger square-wave + fire-mode cycle

    partial void StepDemo( float dt, ref bool active )
    {
        if ( !Demo ) return;
        active = true;

        // Cycle the fire mode every few seconds, then drive a synthetic trigger square-wave (held ~1s,
        // released ~0.4s) through the real fire control so each mode visibly fires differently.
        _modeT -= dt;
        if ( _modeT <= 0f ) { _modeT = 3.0f; CycleFireMode(); }
        _fire.Mode = FireMode; _fire.BurstCount = BurstCount; _fire.Rpm = RoundsPerMinute;
        _triggerT += dt;
        bool trigger = _triggerT % 1.4f < 1.0f;
        if ( !_ammo.Reloading )
        {
            if ( _ammo.Mag <= 0 ) _ammo.Reload();
            else if ( _fire.Tick( trigger, dt ) )
            {
                _ammo.Fire(); _crosshair.Fire();
                if ( _demoRng.NextSingle() < 0.5f ) _hitmarker.Pop( _demoRng.NextSingle() < 0.15f );
            }
        }

        FpsDemo.Health( _health, ref _hpT, dt );
        FpsDemo.Stamina( _stamina, ref _stT, dt );
        FpsDemo.Killfeed( _killfeed, ref _kfT, ref _kfPick, dt );
        FpsDemo.Xp( _xp, ref _xpT, ref _xpPick, dt );
        if ( ShowScoreboard ) FpsDemo.Scoreboard( _scoreboard, ref _sbT, dt );
    }
}
using System;
using Sandbox;

namespace Goo.FpsUI;

// A drop-in shootable target for the FPS pack. Attach to any GameObject that has a Collider; the HUD's
// hitscan damages it through the engine's Component.IDamageable interface (the same path TriggerHurt uses).
// Exposes Health / IsDead / Name so FpsHud can drive the hitmarker, killfeed, and XP on a hit or kill.
// Engine-side component, not a Goo panel.
[Title( "FPS Target" ), Category( "FPS UI" ), Icon( "my_location" )]
public sealed class FpsTarget : Component, Component.IDamageable
{
    [Property] public string Name { get; set; } = "Target";                          // shown as the victim in the killfeed
    [Property, Range( 1f, 1000f )] public float MaxHealth { get; set; } = 100f;       // health restored on enable / respawn
    [Property] public bool RespawnOnDeath { get; set; } = true;                       // refill health a moment after dying
    [Property, Range( 0.5f, 10f )] public float RespawnDelay { get; set; } = 3f;      // seconds dead before respawning

    public float Health { get; private set; }
    public bool IsDead => Health <= 0f;

    float _respawnAt;

    protected override void OnEnabled() => Health = MaxHealth;

    // Engine damage entry point: TriggerHurt volumes and the HUD's hitscan both call this.
    public void OnDamage( in DamageInfo damage )
    {
        if ( IsDead ) return;                                       // already down, ignore extra hits
        Health = Math.Max( 0f, Health - damage.Damage );
        if ( IsDead && RespawnOnDeath ) _respawnAt = Time.Now + RespawnDelay;
    }

    protected override void OnUpdate()
    {
        if ( IsDead && RespawnOnDeath && Time.Now >= _respawnAt ) Health = MaxHealth;
    }
}
using System;

namespace Goo.FpsUI;

// Compass logic: a 0..360 heading plus the shortest signed angular delta the view uses to
// place cardinal ticks. Engine-free.
public sealed class CompassModel
{
    public float Heading { get; private set; }  // current yaw, normalized 0..360

    public void SetHeading( float yawDeg )       // set heading from game code (any range)
        => Heading = ((yawDeg % 360f) + 360f) % 360f;

    // Signed delta from `from` to `to`, in (-180, 180]. Used to position ticks around center.
    public static float ShortestDelta( float from, float to ) => ((to - from + 540f) % 360f) - 180f;

    public bool Tick( float dt ) => false;  // heading changes drive rebuilds, not the damper
}
using System;
using Goo;
using Sandbox;
using Sandbox.UI;
using PanelTransform = Goo.PanelTransform;   // rule 18: ambiguous with Sandbox.UI otherwise

namespace Goo.FpsUI;

// Shared, stateless view helpers. Each returns a fresh Container (never cache a Container).
static class Parts
{
    public enum Corner { TopLeft, TopCenter, TopRight, BottomLeft, BottomRight, Center }

    // Absolute fill spanning `frac` (0..1) of its Relative parent's width, full height.
    public static Container FillRect( string key, float frac, Color color, float radius ) => new()
    {
        Key = key, Position = PositionMode.Absolute, Top = 0, Left = 0,
        Height = Length.Percent( 100 ),
        Width = Length.Percent( Math.Clamp( frac, 0f, 1f ) * 100f ),
        BorderRadius = radius, BackgroundColor = color,
    };

    // Absolute full-cover tint at a given opacity (flashes, warnings).
    public static Container Overlay( string key, Color color, float opacity, float radius ) => new()
    {
        Key = key, Position = PositionMode.Absolute, Top = 0, Left = 0,
        Width = Length.Percent( 100 ), Height = Length.Percent( 100 ),
        BorderRadius = radius, BackgroundColor = color, Opacity = opacity,
    };

    // Pin `child` to a screen corner/center inside a Relative, full-size root. `m` is the edge inset.
    // Container properties are init-only (record struct), so we use `with` expressions per corner.
    public static Container Anchor( string key, Corner c, float m, Container child )
    {
        var box = new Container
        {
            Key = key, Position = PositionMode.Absolute, PointerEvents = PointerEvents.None,
            Children = { child },
        };
        return c switch
        {
            Corner.TopLeft     => box with { Top = m, Left = m },
            Corner.TopRight    => box with { Top = m, Right = m },
            Corner.BottomLeft  => box with { Bottom = m, Left = m },
            Corner.BottomRight => box with { Bottom = m, Right = m },
            Corner.TopCenter   => box with
            {
                Top = m, Left = Length.Percent( 50 ),
                Transform = PanelTransform.Translate( Length.Percent( -50 ) ?? default, Length.Percent( 0 ) ?? default ),
            },
            _ => box with  // Center
            {
                Top = Length.Percent( 50 ), Left = Length.Percent( 50 ),
                Transform = PanelTransform.Translate( Length.Percent( -50 ) ?? default, Length.Percent( -50 ) ?? default ),
            },
        };
    }

    // Pin `child` horizontally centered, offset `dy` px from the screen center (positive = down, negative = up).
    public static Container CenterOffset( string key, float dy, Container child ) => new()
    {
        Key = key, Position = PositionMode.Absolute, PointerEvents = PointerEvents.None,
        Top = Length.Percent( 50 ), Left = Length.Percent( 50 ),
        Transform = PanelTransform.Translate( Length.Percent( -50 ) ?? default, Px.Of( dy ) ),
        Children = { child },
    };

    // Pin `child`'s top-left corner at a pixel offset from the screen center (positive dx = right, positive dy = down).
    public static Container Offset( string key, float dx, float dy, Container child ) => new()
    {
        Key = key, Position = PositionMode.Absolute, PointerEvents = PointerEvents.None,
        Top = Length.Percent( 50 ), Left = Length.Percent( 50 ),
        Transform = PanelTransform.Translate( Px.Of( dx ), Px.Of( dy ) ),
        Children = { child },
    };

    // Semi-opaque rounded backing behind a HUD readout, so text/bars stay legible over the world.
    // Wraps one child and sizes to it, all card styling comes from the theme.
    public static Container Panel( string key, FpsTheme t, Container child ) => new()
    {
        Key = key, Position = PositionMode.Relative,
        Padding = t.PanelPad, BorderRadius = t.PanelRadius,
        BackgroundColor = t.BackingBg,
        Children = { child },
    };

    // A full-size, pointer-through, Relative root used by every standalone widget and the FpsHud.
    public static Container Root( string key ) => new()
    {
        Key = key, Width = Length.Percent( 100 ), Height = Length.Percent( 100 ),
        Position = PositionMode.Relative, PointerEvents = PointerEvents.None,
    };
}
using System;
using Goo;
using Sandbox;
using Sandbox.UI;

namespace Goo.FpsUI;

// Stateless presenter: the secondary (stamina/armor) bar.
static class StaminaView
{
    public const float TrackW = 320f, TrackH = 10f;

    public static Container Build( StaminaModel m, FpsTheme t )
    {
        float frac = m.ShownFraction;
        Color fill = Color.Lerp( t.Warn, t.Good, frac );

        var track = new Container
        {
            Key = "stTrack", Position = PositionMode.Relative,
            Width = TrackW, Height = TrackH, BackgroundColor = t.TrackBg,
            BorderRadius = t.Radius, Overflow = OverflowMode.Hidden,
            Children = { Parts.FillRect( "fill", frac, fill, t.Radius ) },
        };
        if ( m.Flash > 0.001f ) track.Children.Add( Parts.Overlay( "flash", Color.White, m.Flash * 0.8f, t.Radius ) );
        return track;
    }
}

// Standalone stamina bar. Call Sprinting(bool) from your movement code each frame.
public sealed partial class StaminaWidget : GooPanel<Container>
{
    [Property, Range( 1f, 1000f )] public float MaxStamina { get; set; } = 100f; // full-bar value
    [Property, Range( 0.05f, 2f )] public float DrainRate { get; set; } = 0.55f;  // fraction of max drained per second while sprinting
    [Property] public PlayerController? Player { get; set; }                      // player to gate sprint on when stamina is empty (null = skip gating)

    readonly StaminaModel _m = new();
    readonly FpsTheme _t = new();
    bool _booted;
    bool _sprintBlocked;
    float _cachedRunSpeed;

    void Boot() { _m.MaxStamina = MaxStamina; _m.Reset(); _booted = true; }

    public void Sprinting( bool on ) => _m.SetSprinting( on ); // toggle sprint drain

    // Demo-only seam: implemented in FpsDemo.cs, compiles out when that file is deleted.
    partial void StepDemo( float dt, ref bool active );

    protected override bool Tick( float dt )
    {
        if ( !_booted ) Boot();
        _m.DrainRate = DrainRate;  // live-tunable
        bool demo = false;
        StepDemo( dt, ref demo );
        if ( !demo )
        {
            Sprinting( Sandbox.Input.Down( "run" ) );
            FpsInput.ApplySprintGate( Player, _m.Stamina, ref _sprintBlocked, ref _cachedRunSpeed );
        }
        bool moving = _m.Tick( dt );
        return demo || moving;
    }

    protected override Container Build()
    {
        if ( !_booted ) Boot();
        var root = Parts.Root( "fpsStamina" );
        // Sits just above where the health bar would be (health height + gap).
        root.Children.Add( Parts.Anchor( "a", Parts.Corner.BottomLeft, _t.Margin + HealthView.TrackH + 10f, Parts.Panel( "bg", _t, StaminaView.Build( _m, _t ) ) ) );
        return root;
    }
}
using System;
using Sandbox;
using Sandbox.UI;

namespace Goo.Internal;

internal sealed class StatefulShapePanel : Panel, IStatefulEventHost
{
    internal Action<MousePanelEvent>? _onClick;
    internal Action<MousePanelEvent>? _onRightClick;
    internal Action<MousePanelEvent>? _onMiddleClick;
    internal Action<MousePanelEvent>? _onMouseEnter;
    internal Action<MousePanelEvent>? _onMouseLeave;
    internal Action<MousePanelEvent>? _onMouseDown;
    internal Action<MousePanelEvent>? _onMouseUp;
    internal Action<MousePanelEvent>? _onMouseMove;
    internal bool    _userSetPointerEvents;
    internal Action? _requestRebuild;
    public Action? RequestRebuild { set => _requestRebuild = value; }

    public void ApplyEvents(in BlobEvents events)
    {
        _onClick      = events.OnClick;
        _onRightClick = events.OnRightClick;
        _onMiddleClick = events.OnMiddleClick;
        _onMouseEnter = events.OnMouseEnter;
        _onMouseLeave = events.OnMouseLeave;
        _onMouseDown  = events.OnMouseDown;
        _onMouseUp    = events.OnMouseUp;
        _onMouseMove  = events.OnMouseMove;
    }

    public bool HasEventHandlers =>
        _onClick != null || _onRightClick != null || _onMiddleClick != null || _onMouseEnter != null || _onMouseLeave != null ||
        _onMouseDown != null || _onMouseUp != null || _onMouseMove != null;

    public bool UserSetPointerEvents
    {
        get => _userSetPointerEvents;
        set => _userSetPointerEvents = value;
    }

    // Apply a baked alpha texture as BackgroundImage. The shape color comes from
    // BackgroundColor flowing through the engine's BgTint multiplier; this method
    // is only responsible for the alpha mask.
    public void ApplyShape(BlobKind kind, in ShapeParams shape)
    {
        Style.BackgroundImage = ShapeTextureCache.GetOrBake(kind, in shape);
        Style.BackgroundSizeX = Length.Percent(100);
        Style.BackgroundSizeY = Length.Percent(100);
    }

    public void ApplyPolygon(Vector2[] points)
    {
        Style.BackgroundImage = ShapeTextureCache.GetOrBakePolygon(points);
        Style.BackgroundSizeX = Length.Percent(100);
        Style.BackgroundSizeY = Length.Percent(100);
    }

    protected override void OnClick(MousePanelEvent e)       { base.OnClick(e);       EventDispatch.Fire(_onClick, e, _requestRebuild); }
    protected override void OnRightClick(MousePanelEvent e)  { base.OnRightClick(e);  EventDispatch.Fire(_onRightClick, e, _requestRebuild); }
    protected override void OnMiddleClick(MousePanelEvent e) { base.OnMiddleClick(e); EventDispatch.Fire(_onMiddleClick, e, _requestRebuild); }
    protected override void OnMouseOver(MousePanelEvent e)   { base.OnMouseOver(e);   EventDispatch.Fire(_onMouseEnter, e, _requestRebuild); }
    protected override void OnMouseOut(MousePanelEvent e)    { base.OnMouseOut(e);    EventDispatch.Fire(_onMouseLeave, e, _requestRebuild); }
    protected override void OnMouseDown(MousePanelEvent e)   { base.OnMouseDown(e);   EventDispatch.Fire(_onMouseDown, e, _requestRebuild); }
    protected override void OnMouseUp(MousePanelEvent e)     { base.OnMouseUp(e);     EventDispatch.Fire(_onMouseUp, e, _requestRebuild); }
    protected override void OnMouseMove(MousePanelEvent e)   { base.OnMouseMove(e);   EventDispatch.Fire(_onMouseMove, e, _requestRebuild); }
}
using System;
using Sandbox.UI;

namespace Goo.Internal;

internal sealed class StatefulSvgPanel : Sandbox.UI.SvgPanel, IStatefulEventHost
{
    internal Action<MousePanelEvent>? _onClick;
    internal Action<MousePanelEvent>? _onRightClick;
    internal Action<MousePanelEvent>? _onMiddleClick;
    internal Action<MousePanelEvent>? _onMouseEnter;
    internal Action<MousePanelEvent>? _onMouseLeave;
    internal Action<MousePanelEvent>? _onMouseDown;
    internal Action<MousePanelEvent>? _onMouseUp;
    internal Action<MousePanelEvent>? _onMouseMove;
    internal bool    _userSetPointerEvents;
    internal Action? _requestRebuild;
    public Action? RequestRebuild { set => _requestRebuild = value; }

    public void ApplyEvents(in BlobEvents events)
    {
        _onClick      = events.OnClick;
        _onRightClick = events.OnRightClick;
        _onMiddleClick = events.OnMiddleClick;
        _onMouseEnter = events.OnMouseEnter;
        _onMouseLeave = events.OnMouseLeave;
        _onMouseDown  = events.OnMouseDown;
        _onMouseUp    = events.OnMouseUp;
        _onMouseMove  = events.OnMouseMove;
    }

    public bool HasEventHandlers =>
        _onClick != null || _onRightClick != null || _onMiddleClick != null || _onMouseEnter != null || _onMouseLeave != null ||
        _onMouseDown != null || _onMouseUp != null || _onMouseMove != null;

    public bool UserSetPointerEvents
    {
        get => _userSetPointerEvents;
        set => _userSetPointerEvents = value;
    }

    protected override void OnClick(MousePanelEvent e)       { base.OnClick(e);       EventDispatch.Fire(_onClick, e, _requestRebuild); }
    protected override void OnRightClick(MousePanelEvent e)  { base.OnRightClick(e);  EventDispatch.Fire(_onRightClick, e, _requestRebuild); }
    protected override void OnMiddleClick(MousePanelEvent e) { base.OnMiddleClick(e); EventDispatch.Fire(_onMiddleClick, e, _requestRebuild); }
    protected override void OnMouseOver(MousePanelEvent e)   { base.OnMouseOver(e);   EventDispatch.Fire(_onMouseEnter, e, _requestRebuild); }
    protected override void OnMouseOut(MousePanelEvent e)    { base.OnMouseOut(e);    EventDispatch.Fire(_onMouseLeave, e, _requestRebuild); }
    protected override void OnMouseDown(MousePanelEvent e)   { base.OnMouseDown(e);   EventDispatch.Fire(_onMouseDown, e, _requestRebuild); }
    protected override void OnMouseUp(MousePanelEvent e)     { base.OnMouseUp(e);     EventDispatch.Fire(_onMouseUp, e, _requestRebuild); }
    protected override void OnMouseMove(MousePanelEvent e)   { base.OnMouseMove(e);   EventDispatch.Fire(_onMouseMove, e, _requestRebuild); }
}

$primary: red !default;
$primary-alt: white !default;

$switch-padding: 6px !default;

.button.popupbutton.dropdown
{
	cursor: pointer;
	transition: all .1s ease-out;
	position: relative;

	> .dropdown_indicator
	{
		position: absolute;
		right: 8px;
	}

	&.open
	{
		border-bottom-left-radius: 1px;
		border-bottom-right-radius: 1px;
		transition: border-radius 0.2s ease-out;
	}
}

select
{
	min-height: 40px;

	> option
	{
		display: none;
	}
}
.navigator-body
{
	&.hidden
	{
		display: none;
	}
}
@import "base/_splitcontainer.scss";
@import "base/_navigator.scss";

button
{
	cursor: pointer;
}

IconPanel
{
	font-family: Material Icons;
}

.is-half
{
	width: 50%;
}

.is-third
{
	width: 33%;
}

.is-quarter
{
	width: 25%;
}

button.has-subtitle
{
	position: relative;
	flex-direction: column;
	justify-content: flex-start;
	align-items: flex-start;
	padding-left: 40px; // icon space

	.iconpanel
	{
		position: absolute;
		left: 5px;
		top: 0;
		bottom: 0;
		align-items: center;
	}

	.button-label
	{
		font-weight: bold;
	}

	.button-subtitle
	{
		font-size: 12px;
		opacity: 0.5;
		mix-blend-mode: lighten;
	}
}
.package-list
{
    flex-shrink: 1;
    flex-wrap: wrap;
    flex-grow: 1;

    h1
    {
        width: 100%;
        margin-top: 50px;
        font-size: 40px;
    }

    PackageCard
    {
        &:hover
        {
            sound-in: "ui.button.over";
        }
    }

    VirtualGrid
    {
        width: 100%;
        height: 100%;

        .cell
        {
            
        }
    }
}
namespace LobbySystem;

/// <summary>
/// Auto-hosts a lobby so Steam friends can join, and keeps one networked pawn per connection plus optional
/// bots by cloning <see cref="PlayerPrefab"/>. The pawn only has to implement <see cref="ILobbyAgent"/>.
/// Spawning is de-duped and runs in OnUpdate so a join can't fire mid-enumeration.
/// </summary>
public sealed class LobbyNetworkManager : Component, Component.INetworkListener
{
	[Property] public GameObject PlayerPrefab { get; set; }
	[Property] public int BotCount { get; set; } = 1;

	/// <summary>When true, bots only exist during an active round.</summary>
	[Property] public bool BotsOnlyDuringRound { get; set; } = true;

	[Property] public Color BotTint { get; set; } = new Color( 1f, 0.35f, 0.3f );

	// Lobby spawn ring, used before a round map loads.
	readonly Vector3[] _spawns =
	{
		new Vector3( 0f, -300f, 40f ), new Vector3( 300f, 0f, 40f ),
		new Vector3( 0f, 300f, 40f ),  new Vector3( -300f, 0f, 40f ),
		new Vector3( 250f, 250f, 40f ), new Vector3( -250f, -250f, 40f ),
	};
	int _spawnIndex;
	readonly Dictionary<Guid, GameObject> _pawns = new();
	readonly List<GameObject> _bots = new();

	bool _reconcileNow;
	TimeUntil _nextReconcile;
	TimeUntil _nextSweep;

	protected override async Task OnLoad()
	{
		// When joining a friend the engine is mid-connect and IsActive is briefly false, so poll for a
		// moment before hosting. Otherwise a joiner would spin up its own solo lobby.
		if ( Networking.IsActive ) return;
		for ( int i = 0; i < 6 && !Networking.IsActive; i++ )
			await Task.DelayRealtimeSeconds( 0.1f );
		if ( !Networking.IsActive )
			Networking.CreateLobby( new() );
	}

	void INetworkListener.OnActive( Connection channel ) => _reconcileNow = true;

	protected override void OnUpdate()
	{
		if ( !Networking.IsHost || PlayerPrefab is null ) return;

		if ( !_reconcileNow && _nextReconcile > 0f ) return;
		_reconcileNow = false;
		_nextReconcile = 0.25f;

		try
		{
			bool wantBots = !BotsOnlyDuringRound || (LobbyDirector.Current?.State == LobbyState.Active);
			ReconcileBots( wantBots ? Math.Max( 0, BotCount ) : 0 );

			foreach ( var conn in Connection.All.ToList() )
			{
				if ( conn is null || !conn.IsActive ) continue;
				if ( _pawns.TryGetValue( conn.Id, out var pawn ) && pawn.IsValid() ) continue;
				var id = conn.Id;
				_pawns[id] = FindConnectionPawn( id ) ?? SpawnPawn( false, conn.DisplayName, conn );
			}

			Sweep();
		}
		catch
		{
			// Connection or scene list changed during the pass; retry next frame.
		}
	}

	void ReconcileBots( int target )
	{
		_bots.RemoveAll( b => !b.IsValid() );
		while ( _bots.Count > target )
		{
			var b = _bots[_bots.Count - 1];
			_bots.RemoveAt( _bots.Count - 1 );
			if ( b.IsValid() ) b.Destroy();
		}
		while ( _bots.Count < target )
			_bots.Add( SpawnPawn( true, "Bot", null ) );
	}

	GameObject FindConnectionPawn( Guid id )
	{
		foreach ( var a in Scene.GetAllComponents<ILobbyAgent>() )
		{
			if ( !a.IsValid() || a.IsBot ) continue;
			if ( a is Component c && c.Network.OwnerId == id ) return c.GameObject;
		}
		return null;
	}

	void Sweep()
	{
		if ( _nextSweep > 0f ) return;
		_nextSweep = 1f;
		foreach ( var key in _pawns.Where( kv => !kv.Value.IsValid() ).Select( kv => kv.Key ).ToList() )
			_pawns.Remove( key );
	}

	GameObject SpawnPawn( bool isBot, string displayName, Connection owner )
	{
		var go = PlayerPrefab.Clone( NextSpawn() );
		go.Name = isBot ? "Bot" : $"Player - {displayName}";
		go.Enabled = true;

		var agent = go.Components.Get<ILobbyAgent>() ?? go.Components.GetInChildren<ILobbyAgent>();
		agent?.InitAgent( isBot, displayName );

		if ( isBot )
		{
			var rend = go.Components.GetInChildren<SkinnedModelRenderer>();
			if ( rend is not null ) rend.Tint = BotTint;
		}

		if ( owner is not null ) go.NetworkSpawn( owner );
		else go.NetworkSpawn();
		return go;
	}

	Vector3 NextSpawn()
	{
		int idx = _spawnIndex++;
		var dir = LobbyDirector.Current;
		if ( dir is not null && dir.UseRoundMap && dir.MapReady )
			return dir.RoundSpawnPoint( idx );
		return _spawns[idx % _spawns.Length];
	}
}
namespace LobbySystem;

/// <summary>Lifecycle of the lobby: Lobby, then Active, then Ended before looping back.</summary>
public enum LobbyState
{
	/// <summary>Free roam before and after a round. The mode button works here.</summary>
	Lobby,
	/// <summary>A round is running.</summary>
	Active,
	/// <summary>Round finished; results show before returning to the lobby.</summary>
	Ended
}
namespace LobbySystem;

/// <summary>
/// In-world button that opens the mode menu for the host, or a local suggestion menu for a client. It needs
/// a ModelRenderer to be visible and is hidden while a round is live. When the local player is within
/// <see cref="UseRange"/> and presses Use, the menu opens.
/// </summary>
public sealed class LobbyModeButton : Component
{
	[Property] public float UseRange { get; set; } = 130f;
	[Property] public bool GlowWhenInRange { get; set; } = true;
	[Property] public Color IdleTint { get; set; } = new Color( 0.85f, 0.4f, 0.15f );
	[Property] public Color ActiveTint { get; set; } = new Color( 1f, 0.85f, 0.3f );

	ModelRenderer _renderer;
	ILobbyAgent _me;

	protected override void OnStart()
	{
		_renderer = Components.Get<ModelRenderer>() ?? Components.GetInChildren<ModelRenderer>();
		if ( _renderer is not null ) _renderer.Tint = IdleTint;
	}

	protected override void OnUpdate()
	{
		var dir = LobbyDirector.Current;
		bool inLobby = dir is null || !dir.RoundLive;
		if ( _renderer is not null && _renderer.Enabled != inLobby )
			_renderer.Enabled = inLobby;
		if ( !inLobby ) return;

		var me = LocalPlayer();
		bool inRange = me is not null && WorldPosition.Distance( me.WorldPosition ) <= UseRange;

		if ( GlowWhenInRange && _renderer is not null )
			_renderer.Tint = inRange ? ActiveTint : IdleTint;

		if ( inRange && Input.Pressed( "Use" ) )
			dir?.RequestModeMenu();
	}

	ILobbyAgent LocalPlayer()
	{
		if ( _me is not null && _me.IsValid() && !_me.IsBot && !_me.IsProxy ) return _me;
		try { _me = Scene.GetAllComponents<ILobbyAgent>().FirstOrDefault( c => c.IsValid() && !c.IsBot && !c.IsProxy ); }
		catch { _me = null; }
		return _me;
	}
}
global using static Sandbox.Internal.GlobalGameNamespace;
global using Microsoft.AspNetCore.Components;
global using Microsoft.AspNetCore.Components.Rendering;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "MC Clouds" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "mcclouds" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "trend" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "trend.mcclouds" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "26" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]

[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "2026-06-16T17:04:05.4666731Z" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.124.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.124.0")]
using Sandbox;
using Sandbox.UI;
using System;

namespace SbTween;

public static class LightExtensions
{

	public static BaseTween TweenLightColor( this Light Light, Color target, float duration )
	{
		Color start = Light.LightColor;
		var tween = new BaseTween( duration );
		tween.Target = Light.GameObject;
		return TweenManager.Instance.AddTween( tween
			.OnStart( () => start = Light.LightColor )
			.OnUpdate( p => Light.LightColor = Color.Lerp( start, target, p ) ) );
	}

	public static BaseTween TweenRadius( this SpotLight light, float target, float duration )
	{
		if ( !light.IsValid() ) return null;

		float start = light.Radius;
		return TweenManager.Instance.AddTween( new BaseTween( duration )
			.OnStart( () => start = light.Radius )
			.OnUpdate( p => light.Radius = MathX.Lerp( start, target, p ) ) );
	}

	public static BaseTween TweenConeOuter( this SpotLight light, float target, float duration )
	{
		if ( !light.IsValid() ) return null;

		float start = light.ConeOuter;
		return TweenManager.Instance.AddTween( new BaseTween( duration )
			.OnStart( () => start = light.ConeOuter )
			.OnUpdate( p => light.ConeOuter = MathX.Lerp( start, target, p ) ) );
	}

	public static BaseTween TweenInnerCone( this SpotLight light, float target, float duration )
	{
		if ( !light.IsValid() ) return null;

		float start = light.ConeInner;
		return TweenManager.Instance.AddTween( new BaseTween( duration )
			.OnStart( () => start = light.ConeInner )
			.OnUpdate( p => light.ConeInner = MathX.Lerp( start, target, p ) ) );
	}

	public static BaseTween TweenAttenuation( this PointLight light, float target, float duration )
	{
		if ( !light.IsValid() ) return null;

		float start = light.Attenuation;
		return TweenManager.Instance.AddTween( new BaseTween( duration )
			.OnStart( () => start = light.Attenuation )
			.OnUpdate( p => light.Attenuation = MathX.Lerp( start, target, p ) ) );
	}

	public static BaseTween TweenRadius( this PointLight light, float target, float duration )
	{
		if ( !light.IsValid() ) return null;

		float start = light.Radius;
		return TweenManager.Instance.AddTween( new BaseTween( duration )
			.OnStart( () => start = light.Radius )
			.OnUpdate( p => light.Radius = MathX.Lerp( start, target, p ) ) );
	}

	public static BaseTween TweenAttenuation( this SpotLight light, float target, float duration )
	{
		if ( !light.IsValid() ) return null;

		float start = light.Attenuation;
		return TweenManager.Instance.AddTween( new BaseTween( duration )
			.OnStart( () => start = light.Attenuation )
			.OnUpdate( p => light.Attenuation = MathX.Lerp( start, target, p ) ) );
	}

	//FLICKERING LIGHT
	public static BaseTween TweenFlickerLight( this PointLight light, float minBrightness, float maxBrightness, float duration, float speed = 10f )
	{
		if ( !light.IsValid() ) return null;

		float time = 0f;
		return TweenManager.Instance.AddTween( new BaseTween( duration )
			.OnUpdate( _ =>
			{
				time += Time.Delta * speed;
				float noise = MathF.Sin( time * 1.3f ) * MathF.Sin( time * 2.7f ) * MathF.Sin( time * 0.9f );
				float t = (noise + 1f) * 0.5f;
				light.Attenuation = MathX.Lerp( minBrightness, maxBrightness, t );
			} ) );
	}

	public static BaseTween TweenFlickerLight( this SpotLight light, float minBrightness, float maxBrightness, float duration, float speed = 10f )
	{
		if ( !light.IsValid() ) return null;

		float time = 0f;
		return TweenManager.Instance.AddTween( new BaseTween( duration )
			.OnUpdate( _ =>
			{
				time += Time.Delta * speed;
				float noise = MathF.Sin( time * 1.3f ) * MathF.Sin( time * 2.7f ) * MathF.Sin( time * 0.9f );
				float t = (noise + 1f) * 0.5f;
				light.Attenuation = MathX.Lerp( minBrightness, maxBrightness, t );
			} ) );
	}

	//FLICKERING Color

	public static BaseTween TweenFlickerColor( this PointLight light, Color colorA, Color colorB, float duration, float speed = 10f )
	{
		if ( !light.IsValid() ) return null;

		float time = 0f;
		return TweenManager.Instance.AddTween( new BaseTween( duration )
			.OnUpdate( _ =>
			{
				time += Time.Delta * speed;
				float noise = MathF.Sin( time * 1.3f ) * MathF.Sin( time * 2.7f ) * MathF.Sin( time * 0.9f );
				float t = (noise + 1f) * 0.5f;
				light.LightColor = Color.Lerp( colorA, colorB, t );
			} ) );
	}

	public static BaseTween TweenFlickerColor( this SpotLight light, Color colorA, Color colorB, float duration, float speed = 10f )
	{
		if ( !light.IsValid() ) return null;

		float time = 0f;
		return TweenManager.Instance.AddTween( new BaseTween( duration )
			.OnUpdate( _ =>
			{
				time += Time.Delta * speed;
				float noise = MathF.Sin( time * 1.3f ) * MathF.Sin( time * 2.7f ) * MathF.Sin( time * 0.9f );
				float t = (noise + 1f) * 0.5f;
				light.LightColor = Color.Lerp( colorA, colorB, t );
			} ) );
	}

}
using Sandbox;
using System;

namespace SbTween;

public static class AudioExtensions
{
	public static BaseTween TweenVolume( this SoundPointComponent sound, float targetVolume, float duration )
	{
		float startVolume = sound.Volume;
		var tween = new BaseTween( duration );
		tween.Target = sound.GameObject;

		return TweenManager.Instance.AddTween( tween
			.OnStart( () => startVolume = sound.Volume )
			.OnUpdate( p =>
			{
				if ( !sound.IsValid() ) return;
				sound.Volume = MathX.Lerp( startVolume, targetVolume, p );
			} )
		);
	}
	
	public static BaseTween TweenPitch( this SoundPointComponent sound, float targetPitch, float duration )
	{
		float startPitch = sound.Pitch;
		var tween = new BaseTween( duration );
		tween.Target = sound.GameObject;

		return TweenManager.Instance.AddTween( tween
			.OnStart( () => startPitch = sound.Pitch )
			.OnUpdate( p =>
			{
				if ( !sound.IsValid() ) return;
				sound.Pitch = MathX.Lerp( startPitch, targetPitch, p );
			} )
		);
	}
}
using Sandbox;
using System;

namespace SbTween;

public static class MathTweenExtensions
{
	public static BaseTween TweenInCircle( this GameObject obj, float duration, Vector3 axis, float range, float speed, bool snapping = false )
	{
		Vector3 centerPos = obj.WorldPosition;
		var tween = new BaseTween( duration );
		tween.Target = obj;

		Vector3 normal = axis.Normal;
		Vector3 v1 = Vector3.Cross( normal, MathF.Abs( normal.z ) < 0.9f ? Vector3.Up : Vector3.Forward ).Normal;
		Vector3 v2 = Vector3.Cross( normal, v1 ).Normal;

		return TweenManager.Instance.AddTween( tween
			.OnUpdate( p =>
			{
				if ( !obj.IsValid() ) return;

				float angleDegrees = p * 360f * speed;
				float angleRadians = angleDegrees * (MathF.PI / 180f);

				float cos = MathF.Cos( angleRadians ) * range;
				float sin = MathF.Sin( angleRadians ) * range;

				Vector3 rotatedOffset = (v1 * cos) + (v2 * sin);
				obj.WorldPosition = centerPos + rotatedOffset;
			} )
		);
	}

	public static BaseTween TweenSpiral( this GameObject obj, float duration, Vector3 axis, float speed, float frequency )
	{
		Vector3 startPos = obj.WorldPosition;
		var tween = new BaseTween( duration );
		tween.Target = obj;

		return TweenManager.Instance.AddTween( tween
			.OnStart( () => startPos = obj.WorldPosition )
			.OnUpdate( p =>
			{
				if ( !obj.IsValid() ) return;

				float angle = p * MathF.PI * 2f * frequency;

				float currentRadius = p * speed;

				float x = MathF.Cos( angle ) * currentRadius;
				float y = MathF.Sin( angle ) * currentRadius;

				Vector3 axisOffset = axis * p;

				Vector3 circleOffset = new Vector3( x, y, 0 );

				obj.WorldPosition = startPos + axisOffset + circleOffset;
			} )
		);
	}
	
	public static BaseTween TweenPunchFloat( this GameObject obj, float v, float amplitude, float duration, int vibrations = 5, float elasticity = 1f, Action<float> setter = null )
	{
		var tween = new BaseTween( duration );
		tween.Target = obj;

		return TweenManager.Instance.AddTween( tween
			.OnUpdate( p =>
			{
				if ( !obj.IsValid() ) return;
				if ( p >= 1.0f )
				{
					setter?.Invoke( v );
					return;
				}
				
				float decay = MathF.Pow( 1f - p, elasticity * 3f );

				float omega = vibrations * MathF.PI * 2f;
				float oscillation = MathF.Sin( p * omega );

				float currentOffset = amplitude * oscillation * decay;

				setter?.Invoke( v + currentOffset );
			} )
			.OnComplete( () => setter?.Invoke( v ) )
		);
	}
	
	public static BaseTween TweenShakeFloat( this GameObject obj, float baseline, float strength, float duration, Action<float> setter = null )
	{
		var tween = new BaseTween( duration );
		tween.Target = obj;

		return TweenManager.Instance.AddTween( tween
			.OnUpdate( p =>
			{
				if ( !obj.IsValid() ) return;
				if ( p >= 1.0f )
				{
					setter?.Invoke( baseline );
					return;
				}

				float currentStrength = strength * (1.0f - p);
             
				float randomOffset = Game.Random.Float( -currentStrength, currentStrength );

				setter?.Invoke( baseline + randomOffset );
			} )
			.OnComplete( () => setter?.Invoke( baseline ) )
		);
	}
}

$primary: red !default;
$primary-alt: white !default;

$switch-padding: 6px !default;

.coloreditor
{
	flex-direction: column;
	min-width: 250px;
	max-width: 250px;

	> .canvas
	{
		border-top: 1px solid rgba( white, 0.5 );
		flex-grow: 1;
		flex-direction: column;
		padding: 10px 15px;


		> textentry.textentry
		{
			flex-grow: 0;
			flex-shrink: 0;
			width: auto;
		}
	}

	.slider
	{
		padding: 6.5px;
	}

	.slider .track
	{
		height: 14px;
		border-radius: 14px;
		left: 0px;
		right: 0px;
		transition: all 0.1s ease-out;

		.inner
		{
			display: none;
		}
	}

	.slider:hover, .slider:active
	{
		padding: 7px;

		.track
		{
			height: 20px;
			border-radius: 6px;
		}

		.thumb
		{
			width: 10px;
			height: 16px;
			margin-left: 2px;
		}
	}

	.slider .thumb
	{
		width: 10px;
		height: 10px;
		box-shadow: 0px 0px 0px transparent;
		background-color: white;
		backdrop-filter: invert( 1 );
		margin-left: 1px;
		pointer-events: none;
	}

	.alpha_slider .track
	{
		background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
	}

	.hue .track
	{
		background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);

		.inner
		{
			display: none;
		}
	}

	.satval
	{
		background: linear-gradient(to right, white, transparent);
		height: 200px;
		position: relative;

		&:active
		{
			z-index: 10;
			cursor: none;
		}

		.value_gradient
		{
			background: linear-gradient(to bottom, black, white);
			position: absolute;
			top: 0;
			left: 0;
			right: 0;
			bottom: 0;
			pointer-events: none;
			mix-blend-mode: multiply;
		}

		.thumb
		{
			z-index: 10;
		}
	}

	.presets
	{
		flex-wrap: wrap;
		align-content: center;
		justify-content: center;
		padding: 15px;
		border-top: 1px solid rgba( white, 0.5 );

		.preset
		{
			background-color: white;
			width: 16px;
			height: 16px;
			margin: 2px;
			border-radius: 20px;
			cursor: pointer;
			
			&:hover
			{
				box-shadow: 0px 0px 1px 2px white;
			}
		}
	}
}


.coloreditorrgba
{
	flex-direction: column;

	.slider
	{
		padding-right: 16px;
	}

	.slider .track
	{
		background-color: transparent;
		height: 14px;
		border-radius: 14px;
		left: 0px;
		right: 0px;

		.inner
		{
			display: none;
		}
	}

	.slider .thumb
	{
		width: 12px;
		height: 12px;
		box-shadow: 0px 0px 0px transparent;
		background-color: white;
		margin-left: 2px;
		backdrop-filter: invert( 1 );
	}

	.red_slider .track
	{
		background-image: linear-gradient( to right, rgba( #e63439, 0.1 ), rgba( #e63439, 0.9 ) );
	}

	.green_slider .track
	{
		background-image: linear-gradient( to right, rgba( #54be35, 0.1 ), rgba( #54be35, 0.9 ) );
	}

	.blue_slider .track
	{
		background-image: linear-gradient( to right, rgba( #3472e6, 0.1 ), rgba( #3472e6, 0.9 ) );
	}

	.alpha_slider .track
	{
		background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
	}
}


.coloreditorhsva
{
	flex-direction: column;

	.slider
	{
		padding-right: 16px;
	}

	.slider .track
	{
		background-color: transparent;
		height: 14px;
		border-radius: 14px;
		left: 0px;
		right: 0px;

		.inner
		{
			display: none;
		}
	}

	.slider .thumb
	{
		width: 12px;
		height: 12px;
		box-shadow: 0px 0px 0px transparent;
		background-color: white;
		margin-left: 2px;
		backdrop-filter: invert( 1 );
	}
		
	.hue_slider .track
	{
		background-image: linear-gradient( to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00 );
	}

	.alpha_slider .track
	{
		background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
	}
}
MapChooser {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    pointer-events: all;
    background: linear-gradient( 140deg, #0f141a 0%, #0a0e14 60%, #111926 100% );
    justify-content: flex-start;
    align-items: stretch;
    font-weight: bold;
    font-family: Poppins;
    color: white;
    flex-direction: column;
    padding: 48px 60px;
    gap: 24px;

    .header {
        flex-direction: column;
        gap: 6px;

        h1 {
            font-size: 48px;
            letter-spacing: 1px;
        }

        .subtitle {
            font-size: 16px;
            opacity: 0.65;
        }
    }

    .content {
        flex-grow: 1;
        flex-direction: row;
        gap: 24px;
    }

    .map-list {
        flex-direction: column;
        flex-grow: 1;
        min-width: 0;

        .map-entries {
            flex-direction: column;
            gap: 10px;
            padding: 12px;
            border-radius: 16px;
            background: #0a0c10cc;
            border: 1px solid #ffffff14;
            overflow-y: scroll;
        }

        button {
            padding: 12px 14px;
            border-radius: 12px;
            background: #ffffff0f;
            text-align: left;
            transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
            gap: 4px;
            cursor: pointer;
            flex-direction: row;
            align-items: center;
        }

            button:hover {
                transform: translateY( -1px );
                box-shadow: 0 6px 18px rgba( 0, 0, 0, 0.35 );
            }

            button.active {
                background: #db3984;
                box-shadow: 0 6px 18px rgba( 219, 57, 132, 0.35 );
            }

        .title {
            font-size: 18px;
        }

        .thumb {
            width: 84px;
            height: 48px;
            border-radius: 10px;
            background-color: #0b0f15;
            background-position: center;
            background-size: cover;
            flex-shrink: 0;
            box-shadow: inset 0 0 0 1px #ffffff12;
        }

        .text {
            flex-direction: column;
            gap: 2px;
        }

        .ident {
            font-size: 12px;
            opacity: 0.6;
        }
    }

    .settings {
        width: 360px;
        padding: 16px;
        border-radius: 16px;
        background: #0a0c10cc;
        border: 1px solid #ffffff14;
        flex-direction: column;
        gap: 14px;
    }

    .section-title {
        font-size: 18px;
        text-transform: uppercase;
        letter-spacing: 1px;
        opacity: 0.85;
    }

    .field {
        flex-direction: column;
        gap: 6px;

        label {
            font-size: 14px;
            opacity: 0.7;
        }

        textentry {
            width: 100%;
            padding: 8px 10px;
            background: #ffffff0f;
            border-radius: 8px;
        }
    }

        .field.split {
            flex-direction: row;
            gap: 12px;

            .field {
                flex-grow: 1;
            }
        }

    .privacy {
        flex-direction: row;
        gap: 8px;

        button {
            flex-grow: 1;
            padding: 8px 10px;
            border-radius: 8px;
            background: #ffffff0f;
            opacity: 0.7;
            cursor: pointer;
            transition: transform 0.12s ease, box-shadow 0.12s ease, opacity 0.12s ease, background 0.12s ease;
        }

            button:hover {
                opacity: 0.9;
                transform: translateY( -1px );
            }

            button.active {
                background: #db3984;
                opacity: 1;
                box-shadow: 0 6px 16px rgba( 219, 57, 132, 0.35 );
                transform: translateY( -1px );
            }
    }

    .selected-map {
        padding: 10px 12px;
        border-radius: 10px;
        background: #ffffff0a;
        flex-direction: column;
        gap: 4px;

        .label {
            font-size: 12px;
            opacity: 0.6;
        }
    }

    .launch {
        margin-top: 4px;
        padding: 12px;
        border-radius: 10px;
        background: #47c27c;
        color: #0b0f12;
        font-size: 18px;
    }

        .launch.disabled {
            opacity: 0.4;
            background: #ffffff1f;
            color: white;
        }
}
@import "/styles/_theme.scss";
$background-size: 8px;

PackageCard
{
	flex-shrink: 0;

	&:hover
	{
		sound-in: "ui.button.over";
	}

	&:active
	{
		sound-in: "ui.button.press";
	}

	flex-direction: column;
	position: relative;
	background-color: rgba( $default-950, 0 );
	border-radius: $rounding-default;
	transition: all 150ms ease;
	cursor: pointer;
	z-index: 0;
	height: 200px;

	.image
	{
		flex-grow: 1;
		flex-shrink: 0;
		transition: all 150ms ease;
		border: 1px solid rgba( white, 0.025 );
		aspect-ratio: 16 / 9;
		background-position: center;
		background-size: cover;
		border-radius: $rounding-small;
		position: relative;
	}

	column
	{
		padding: 8px 2px; // Optically aligned
	}

	&.list
	{
		flex-grow: 1;
		height: 64px;
		flex-direction: row;
		gap: 12px;
		padding: 4px;

		.inner column
		{
			flex-grow: 1;
		}

		.image
		{
			height: 100%;
			aspect-ratio: 1;
			flex-grow: 0;
			flex-shrink: 0;
		}

		column
		{
			gap: 3px;
			justify-content: center;
		}

		.package-title
		{
			flex-shrink: 0;
			max-width: 500px;
		}

		.package-users
		{
			top: 1px;
			right: 1px;
		}

		&:hover
		{
			background-color: $default-800;
			transform: none;

			&::after
			{
				display: none;
			}
		}
	}

	&.wide
	{
		height: 250px;

		.package-title
		{
			max-width: 300px;
		}
	}

	&.small
	{
		height: 200px;
		aspect-ratio: 3/4;

		.image
		{
			aspect-ratio: 1;
		}

		.package-title
		{
			max-width: 140px;
		}
	}

	&.tall
	{
		height: 400px;

		.image
		{
			aspect-ratio: 9/16;
		}

		.package-title
		{
			max-width: 200px;
		}
	}

	.package-title
	{
		text-overflow: ellipsis;
		max-height: 24px;
		flex-shrink: 1;
		font-size: 14px;
	}

	.package-users
	{
		position: absolute;
		bottom: 4px;
		right: 4px;
		background-color: rgba( 10, 40, 10, 0.95 );
		padding: 3px 5px;
		border-radius: 2px;
		justify-content: center;
		align-items: center;
		gap: 3px;
		font-size: 11px;
		color: #def;
		border: 1px solid #252;
		color: #2f3;

		&:before
		{
			content: '●';
			font-size: 0.5rem;
		}
	}
	// Hover effect (not for list)
	&:not(.list)
	{
		&::after
		{
			content: "";
			position: absolute;
			top: 0;
			left: 0;
			bottom: 0;
			right: 0;
			background-color: rgba( $default-950, 0 );
			transition: all 150ms ease;
			z-index: -10;
			border-radius: $rounding-large;
			pointer-events: none;
		}

		&:hover
		{
			transform: scale( 1.05 );

			&::after
			{
				background-color: $default-800;
				top: -$background-size;
				left: -$background-size;
				right: -$background-size;
				bottom: -$background-size;
				box-shadow: 0 0 25px rgba( black, 0.3 );
			}

			z-index: 100;
			background-color: $default-900;
		}
	}
}
namespace Skateboard.Entities;

public sealed class GrindPathNodeEntity : Component
{
}

$primary: red !default;
$primary-alt: white !default;

$switch-padding: 6px !default;

.checkbox.switch
{
	cursor: pointer;

	> .checkmark
	{
		font-size: 22px;
		border: 0px solid $primary;
		border-radius: 100px;
		text-align: center;
		justify-content: center;
		align-items: center;
		color: $primary-alt;
		padding: $switch-padding;
		padding-right: 32px;
		padding-left: $switch-padding;
		transition: all 0.3s ease;
		background-color: rgba( $primary, 0.1 );

		> .handle
		{
			background-color: $primary-alt;
			width: 20px;
			height: 20px;
			border-radius: 100px;
			box-shadow: 2px 2px 12px black;
		}
	}

	&.checked
	{
		> .checkmark
		{
			background-color: $primary;
			padding-left: 32px;
			padding-right: $switch-padding;
		}
	}

	&:active
	{
		transform: scale( 0.9 );
		transform-origin: 20px 50%;
	}
}

.slidercontrol
{
    flex-direction: row;
    min-width: 50px;
    position: relative;
    flex-shrink: 0;
    flex-direction: row;
    cursor: pointer;
    gap: 8px;
    flex-grow: 1;
    align-items: center;
    pointer-events: all;

    > .inner
    {
        flex-direction: column;
        flex-shrink: 1;
        flex-grow: 1;
        min-height: 32px;
        justify-content: center;

        > .values
        {
            width: 100%;
            pointer-events: none;
            font-size: 14px;
            color: #aaa;

            > .left
            {
                flex-grow: 1;
            }
        }

        > .track
        {
            position: relative;
            background-color: #888;
            height: 7px;
            margin: 8px;
            align-items: center;
            border-radius: 4px;

            > .track-active
            {
                background-color: #fff;
                position: absolute;
                height: 100%;
                left: 0px;
                border-radius: 4px;
            }

            > .thumb
            {
                position: relative;
                background-color: #fff;
                border-radius: 100px;
                width: 16px;
                height: 16px;
                transform: translateX( -50% );
            }
        }
    }

    > .entry
    {
        flex-shrink: 0;
        flex-grow: 0;
        width: 50px;

        > numberentry
        {
            background-color: transparent;

            > .content-label
            {
                padding: 0 4px;
            }
        }
    }
}


.slidercontrol .value-tooltip
{
    position: absolute;
    bottom: 150%;
    left: -8px;
    z-index: 1000;
    flex-direction: column;

    > .label
    {       
        background-color: black;
        padding: 8px 12px;
        border-radius: 8px;
    }

    >.tail
    {
        bottom: -0px;
        background-color: black;
        width: 10px;
        height: 10px;
        transform: rotateZ(45 deg) translateX( 4px );
        position: absolute;
    }
}

.switchcontrol
{
    flex-direction: row;
    width: 100px;
    min-height: 24px;
    align-items: center;
    cursor: pointer;

    .switch-frame
    {
        flex-grow: 0;
        flex-shrink: 1;
        width: 48px;
        height: 16px;
        background-color: #fff1;
        margin: 0px 5px;
        align-items: center;
        border-radius: 100px;
        transition: all 0.4s linear;

        .switch-inner
        {
            position: relative;
            flex-grow: 0;
            flex-shrink: 1;
            background-color: #999;
            width: 25px;
            height: 25px;
            border-radius: 100px;
            left: 20%;
            transform: translateX( -50% );
            transition: all 0.3s ease-out;
        }
    }

    &.active
    {
        .switch-frame
        {
            background-color: #fffa;
        }

        .switch-inner
        {
            left: 80%;
            background-color: #fff;
        }
    }
}

$primary: red !default;
$primary-alt: white !default;

$switch-padding: 6px !default;

.button.popupbutton.dropdown
{
	cursor: pointer;
	transition: all .1s ease-out;
	position: relative;

	> .dropdown_indicator
	{
		position: absolute;
		right: 8px;
	}

	&.open
	{
		border-bottom-left-radius: 1px;
		border-bottom-right-radius: 1px;
		transition: border-radius 0.2s ease-out;
	}
}

select
{
	min-height: 40px;

	> option
	{
		display: none;
	}
}

$primary: red !default;
$primary-alt: white !default;

$switch-padding: 6px !default;

.coloreditor
{
	flex-direction: column;
	min-width: 250px;
	max-width: 250px;

	> .canvas
	{
		border-top: 1px solid rgba( white, 0.5 );
		flex-grow: 1;
		flex-direction: column;
		padding: 10px 15px;


		> textentry.textentry
		{
			flex-grow: 0;
			flex-shrink: 0;
			width: auto;
		}
	}

	.slider
	{
		padding: 6.5px;
	}

	.slider .track
	{
		height: 14px;
		border-radius: 14px;
		left: 0px;
		right: 0px;
		transition: all 0.1s ease-out;

		.inner
		{
			display: none;
		}
	}

	.slider:hover, .slider:active
	{
		padding: 7px;

		.track
		{
			height: 20px;
			border-radius: 6px;
		}

		.thumb
		{
			width: 10px;
			height: 16px;
			margin-left: 2px;
		}
	}

	.slider .thumb
	{
		width: 10px;
		height: 10px;
		box-shadow: 0px 0px 0px transparent;
		background-color: white;
		backdrop-filter: invert( 1 );
		margin-left: 1px;
		pointer-events: none;
	}

	.alpha_slider .track
	{
		background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
	}

	.hue .track
	{
		background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);

		.inner
		{
			display: none;
		}
	}

	.satval
	{
		background: linear-gradient(to right, white, transparent);
		height: 200px;
		position: relative;

		&:active
		{
			z-index: 10;
			cursor: none;
		}

		.value_gradient
		{
			background: linear-gradient(to bottom, black, white);
			position: absolute;
			top: 0;
			left: 0;
			right: 0;
			bottom: 0;
			pointer-events: none;
			mix-blend-mode: multiply;
		}

		.thumb
		{
			z-index: 10;
		}
	}

	.presets
	{
		flex-wrap: wrap;
		align-content: center;
		justify-content: center;
		padding: 15px;
		border-top: 1px solid rgba( white, 0.5 );

		.preset
		{
			background-color: white;
			width: 16px;
			height: 16px;
			margin: 2px;
			border-radius: 20px;
			cursor: pointer;
			
			&:hover
			{
				box-shadow: 0px 0px 1px 2px white;
			}
		}
	}
}


.coloreditorrgba
{
	flex-direction: column;

	.slider
	{
		padding-right: 16px;
	}

	.slider .track
	{
		background-color: transparent;
		height: 14px;
		border-radius: 14px;
		left: 0px;
		right: 0px;

		.inner
		{
			display: none;
		}
	}

	.slider .thumb
	{
		width: 12px;
		height: 12px;
		box-shadow: 0px 0px 0px transparent;
		background-color: white;
		margin-left: 2px;
		backdrop-filter: invert( 1 );
	}

	.red_slider .track
	{
		background-image: linear-gradient( to right, rgba( #e63439, 0.1 ), rgba( #e63439, 0.9 ) );
	}

	.green_slider .track
	{
		background-image: linear-gradient( to right, rgba( #54be35, 0.1 ), rgba( #54be35, 0.9 ) );
	}

	.blue_slider .track
	{
		background-image: linear-gradient( to right, rgba( #3472e6, 0.1 ), rgba( #3472e6, 0.9 ) );
	}

	.alpha_slider .track
	{
		background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
	}
}


.coloreditorhsva
{
	flex-direction: column;

	.slider
	{
		padding-right: 16px;
	}

	.slider .track
	{
		background-color: transparent;
		height: 14px;
		border-radius: 14px;
		left: 0px;
		right: 0px;

		.inner
		{
			display: none;
		}
	}

	.slider .thumb
	{
		width: 12px;
		height: 12px;
		box-shadow: 0px 0px 0px transparent;
		background-color: white;
		margin-left: 2px;
		backdrop-filter: invert( 1 );
	}
		
	.hue_slider .track
	{
		background-image: linear-gradient( to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00 );
	}

	.alpha_slider .track
	{
		background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
	}
}

menupanel
{
    position: absolute;
    z-index: 1000;
    pointer-events: all;
    font-size: 12px;
    flex-shrink: 0;

    .background
    {
        position: absolute;
        left: -5000px;
        right: -5000px;
        top: -5000px;
        bottom: -5000px;
    }

    > .inner
    {
        min-width: 200px;
        min-height: 20px;
        flex-direction: column;
        font-family: Poppins;
        font-weight: bold;
        border-radius: 10px;
        box-shadow: 5px 5px 30px #000e;
        background-color: #2a2a2a;
        flex-shrink: 0;

        .spacer
        {
            height: 1px;
            background-color: #0005;
        }

        .option
        {
            color: #fffa;
            padding: 0px 8px;
            cursor: pointer;
            flex-shrink: 0;
            height: 32px;

            &:first-child
            {
                border-top-left-radius: 10px;
                border-top-right-radius: 10px;
            }

            &:last-child
            {
                border-bottom-left-radius: 10px;
                border-bottom-right-radius: 10px;
            }

            .icon
            {
                padding: 8px;
                font-family: Material Icons;
                justify-content: center;
                align-items: center;
                flex-shrink: 0;
            }

            .text
            {
                padding: 8px;
                flex-shrink: 0;
            }

            &:hover
            {
                background-color: #3472e6;
                color: #f5f8fe;
            }
        }
    }
}

$form-control-height: 28px !default;

@import "form/_checkbox.scss";
@import "form/_switch.scss";
@import "form/_dropdown.scss";
@import "form/_coloreditor.scss";
@import "form/_colorproperty.scss";

.form
{
	flex-direction: column;
	align-items: stretch;
	justify-content: flex-start;
	overflow: scroll;
}

.field-group
{
	flex-direction: column;
	flex-shrink: 0;
}

.field-header
{
	flex-shrink: 0;
}

.field
{
	color: white;
	font-size: 14px;
	flex-shrink: 0;
	flex-grow: 0;

	> .label
	{
		flex-grow: 0;
		flex-shrink: 0;
		font-weight: 600;
		opacity: 0.4;
		width: 20%;
		font-size: 13px;
	}

	> .control
	{
		flex-shrink: 0;
		flex-grow: 1;
		flex-direction: column;
	}
}

.is-vertical > .field, .field.is-vertical
{
	flex-direction: column;

	> .label
	{
		width: auto;
		height: auto;
	}
}


RootPanel
{
	font-family: Roboto;
	color: white;
}

IconPanel, i
{
	font-family: Material Icons;
	background-position: center;
	background-size: contain;
}

.tooltip
{
	background-color: rgba( black, 0.8 );
	border: 1px solid rgba( #555, 0.1 );
	padding: 10px;
	border-radius: 4px;
	color: white;
	font-family: Inter;
	font-weight: bold;
	transition: opacity 0.09s ease;
	opacity: 1;
	margin: 0px;

	&:intro, &:outro
	{
		opacity: 0;
	}
}

.textentry
{
	cursor: text;
	align-items: center;
	justify-content: center;
	white-space: nowrap;
	overflow: hidden;
	flex-direction: row;
	position: relative;
	padding: 10px;

	&.disabled
	{
		cursor: default;
	}

	&.is-multiline
	{
		align-items: flex-start;
		white-space: normal;
	}

	.content-label,
	.placeholder
	{
		flex-grow: 1;
	}

	.placeholder
	{
		opacity: 0.2;
		pointer-events: none;
	}

	.iconpanel
	{
		position: absolute;
		right: 0;
		padding-right: 10px;
		pointer-events: none;

		&.clearbutton
		{
			color: #fff;
			pointer-events: all;
			cursor: pointer;
			opacity: 0.1;

			&:hover
			{
				opacity: 0.5;
			}

			&:active
			{
				opacity: 1;
			}
		}
	}

	&:empty
	{
		.clearbutton
		{
			display: none;
		}
	}

	&.has-icon
	{
		padding-right: 30px;
	}

	.content-label,
	.prefix-label,
	.suffix-label,
	.placeholder
	{
		overflow: hidden;
	}

	.content-label
	{
		order: 1;
	}

	.suffix-label
	{
		order: 2;
	}

	.prefix-label,
	.suffix-label
	{
		flex-shrink: 0;
		opacity: 0.5;
	}

	&.invalid
	{
		box-shadow: 0px 0px 1px 2px #f8356b;
	}
}

.popup-panel
{
	background-color: #1b202c;
	flex-direction: column;
	color: #8e9ec2;
	transition: all 0.1s ease-out;
	position: absolute;
	z-index: 2000;
	min-height: 20px;
	min-width: 10px;
	overflow: scroll;
	pointer-events: all;
	box-shadow: 3px 3px 64px #000;
	border-radius: 4px;

	&.medium
	{
		max-width: 300px;
	}

	&.center-horizontal
	{
		transform-origin: top center;
	}

	&.below-center
	{
		transform: translateX( -50% );
		transform-origin: 0% 0%;
	}

	&.above-left
	{
		transform-origin: 0% 100%;
	}

	&.above-center
	{
		transform: translateX( -50% );
		transform-origin: 0% 100%;
	}

	&.left
	{
		transform-origin: 100% 50%;
		transform: translate( 00% -50% );
	}

	&.right
	{
		transform-origin: 0 50%;
		transform: translate( 0% -50% );
	}

	&:intro
	{
		transform: scale( 0 );
		pointer-events: none;
		opacity: 0;
	}

	&:outro
	{
		opacity: 0;
		transition: all 0.1s ease-in;
		margin-top: 50px;
		pointer-events: none;
	}

	&.below-stretch
	{
		transform-origin: 0% 0%;

		&:intro
		{
			transform: scaleY( 0.1 );
		}
	}

	> .canvas
	{
		flex-direction: column;
	}

	.information
	{
		padding: 16px;
		font-size: 14px;
		opacity: 0.5;
	}

	button
	{
		padding: 2px 8px;
		cursor: pointer;
		font-family: Poppins;
		font-weight: 600;
		flex-shrink: 0;

		&:hover
		{
			background-color: #08f;
			color: #fff;
		}
	}
}

$primary: red !default;
$primary-alt: white !default;

$switch-padding: 6px !default;

.button.popupbutton.dropdown
{
	cursor: pointer;
	transition: all .1s ease-out;
	position: relative;

	> .dropdown_indicator
	{
		position: absolute;
		right: 8px;
	}

	&.open
	{
		border-bottom-left-radius: 1px;
		border-bottom-right-radius: 1px;
		transition: border-radius 0.2s ease-out;
	}
}

select
{
	min-height: 40px;

	> option
	{
		display: none;
	}
}

$primary: red !default;
$primary-alt: white !default;

$switch-padding: 6px !default;

.coloreditor
{
	flex-direction: column;
	min-width: 250px;
	max-width: 250px;

	> .canvas
	{
		border-top: 1px solid rgba( white, 0.5 );
		flex-grow: 1;
		flex-direction: column;
		padding: 10px 15px;


		> textentry.textentry
		{
			flex-grow: 0;
			flex-shrink: 0;
			width: auto;
		}
	}

	.slider
	{
		padding: 6.5px;
	}

	.slider .track
	{
		height: 14px;
		border-radius: 14px;
		left: 0px;
		right: 0px;
		transition: all 0.1s ease-out;

		.inner
		{
			display: none;
		}
	}

	.slider:hover, .slider:active
	{
		padding: 7px;

		.track
		{
			height: 20px;
			border-radius: 6px;
		}

		.thumb
		{
			width: 10px;
			height: 16px;
			margin-left: 2px;
		}
	}

	.slider .thumb
	{
		width: 10px;
		height: 10px;
		box-shadow: 0px 0px 0px transparent;
		background-color: white;
		backdrop-filter: invert( 1 );
		margin-left: 1px;
		pointer-events: none;
	}

	.alpha_slider .track
	{
		background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
	}

	.hue .track
	{
		background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);

		.inner
		{
			display: none;
		}
	}

	.satval
	{
		background: linear-gradient(to right, white, transparent);
		height: 200px;
		position: relative;

		&:active
		{
			z-index: 10;
			cursor: none;
		}

		.value_gradient
		{
			background: linear-gradient(to bottom, black, white);
			position: absolute;
			top: 0;
			left: 0;
			right: 0;
			bottom: 0;
			pointer-events: none;
			mix-blend-mode: multiply;
		}

		.thumb
		{
			z-index: 10;
		}
	}

	.presets
	{
		flex-wrap: wrap;
		align-content: center;
		justify-content: center;
		padding: 15px;
		border-top: 1px solid rgba( white, 0.5 );

		.preset
		{
			background-color: white;
			width: 16px;
			height: 16px;
			margin: 2px;
			border-radius: 20px;
			cursor: pointer;
			
			&:hover
			{
				box-shadow: 0px 0px 1px 2px white;
			}
		}
	}
}


.coloreditorrgba
{
	flex-direction: column;

	.slider
	{
		padding-right: 16px;
	}

	.slider .track
	{
		background-color: transparent;
		height: 14px;
		border-radius: 14px;
		left: 0px;
		right: 0px;

		.inner
		{
			display: none;
		}
	}

	.slider .thumb
	{
		width: 12px;
		height: 12px;
		box-shadow: 0px 0px 0px transparent;
		background-color: white;
		margin-left: 2px;
		backdrop-filter: invert( 1 );
	}

	.red_slider .track
	{
		background-image: linear-gradient( to right, rgba( #e63439, 0.1 ), rgba( #e63439, 0.9 ) );
	}

	.green_slider .track
	{
		background-image: linear-gradient( to right, rgba( #54be35, 0.1 ), rgba( #54be35, 0.9 ) );
	}

	.blue_slider .track
	{
		background-image: linear-gradient( to right, rgba( #3472e6, 0.1 ), rgba( #3472e6, 0.9 ) );
	}

	.alpha_slider .track
	{
		background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
	}
}


.coloreditorhsva
{
	flex-direction: column;

	.slider
	{
		padding-right: 16px;
	}

	.slider .track
	{
		background-color: transparent;
		height: 14px;
		border-radius: 14px;
		left: 0px;
		right: 0px;

		.inner
		{
			display: none;
		}
	}

	.slider .thumb
	{
		width: 12px;
		height: 12px;
		box-shadow: 0px 0px 0px transparent;
		background-color: white;
		margin-left: 2px;
		backdrop-filter: invert( 1 );
	}
		
	.hue_slider .track
	{
		background-image: linear-gradient( to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00 );
	}

	.alpha_slider .track
	{
		background-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );
	}
}

menupanel
{
    position: absolute;
    z-index: 1000;
    pointer-events: all;
    font-size: 12px;
    flex-shrink: 0;

    .background
    {
        position: absolute;
        left: -5000px;
        right: -5000px;
        top: -5000px;
        bottom: -5000px;
    }

    > .inner
    {
        min-width: 200px;
        min-height: 20px;
        flex-direction: column;
        font-family: Poppins;
        font-weight: bold;
        border-radius: 10px;
        box-shadow: 5px 5px 30px #000e;
        background-color: #2a2a2a;
        flex-shrink: 0;

        .spacer
        {
            height: 1px;
            background-color: #0005;
        }

        .option
        {
            color: #fffa;
            padding: 0px 8px;
            cursor: pointer;
            flex-shrink: 0;
            height: 32px;

            &:first-child
            {
                border-top-left-radius: 10px;
                border-top-right-radius: 10px;
            }

            &:last-child
            {
                border-bottom-left-radius: 10px;
                border-bottom-right-radius: 10px;
            }

            .icon
            {
                padding: 8px;
                font-family: Material Icons;
                justify-content: center;
                align-items: center;
                flex-shrink: 0;
            }

            .text
            {
                padding: 8px;
                flex-shrink: 0;
            }

            &:hover
            {
                background-color: #3472e6;
                color: #f5f8fe;
            }
        }
    }
}
ColorAlphaControl
{
	gap: 0.5rem;
	flex-grow: 1;
	pointer-events: all;
	background: linear-gradient( to right, black, white );
	border-radius: 4px;
	padding: 2px;
	height: 12px;
	position: relative;
	cursor: pointer;
	border: 1px solid #333;

	&:hover
	{
		border: 1px solid #08f;
	}

	&:active
	{
		border: 1px solid #fff;
	}

	.handle
	{
		top: -5px;
		bottom: -5px;
		aspect-ratio: 1;
		border-radius: 100px;
		border: 2px solid #444;
		position: absolute;
		background-color: white;
		box-shadow: 2px 2px 16px #000a;
		transform: translateX( -50% );
		pointer-events: none;
	}
}

@import "/styles/_theme.scss";

$primary: $primary-blue !default;
$primary-alt: white !default;
$form-control-height: 24px !default;

.checkbox
{
	cursor: pointer;
	color: rgba( $primary-alt, 0.6 );
	align-items: center;
	gap: 8px;

	label
	{
		pointer-events: none;
	}

	> .checkmark
	{
		padding: 1px;
		font-size: 22px;
		border: 1px solid $primary;
		border-radius: $rounding-small;
		text-align: center;
		justify-content: center;
		align-items: center;
		color: transparent;
		min-height: $form-control-height;
		pointer-events: none;
		flex-shrink: 0;
	}

	&.checked
	{
		> .checkmark
		{
			color: $primary-alt;
			background-color: $primary;
		}
	}

	&:active
	{
		color: $primary-alt;
	}

	&:hover
	{
		color: $primary-alt;
	}
}
#nullable enable

using Sandbox;

public abstract class Item
{
    public Scene? Scene {get;set;} = null;
    public GameObject? GameObject {get;set;} = null;
    public string Name {get;set;} = "empty";
    
    public abstract Item Init();
    public abstract void Act();
    public abstract void Drop();
}
using Sandbox;

public class SCP_173_SFX : Component
{
    [Property] public CharacterController Character_Controller {get;set;}
    [Property] public SoundPointComponent Rattle {get;set;}
    
    protected override void OnUpdate()
    { if (IsProxy) return;
        if (Character_Controller.IsOnGround && Character_Controller.Velocity.LengthSquared > 0
        && !GameObject.Tags.Has("seen")) {Rattle.StartSound(); return;} Rattle.StopSound();
    }
}
namespace Sandbox.UiPro;

public enum HorizontalAlignment
{
	Left,
	Center,
	Right
}

public enum VerticalAlignment
{
	Top,
	Center,
	Bottom
}

[Title( "Text Node - UI Pro" ), Category( "UI Pro" ), Icon( "text_fields" )]
public class TextNode : NodeComponent
{
	[Property, InlineEditor, Group( "Layout Settings" ), Order( -999 )] public override NodeStyle Style { get; set; } = GetDefaultStyle();

	[Property, Group("Text Settings")] public HorizontalAlignment HorizontalAlignment { get; set; } = HorizontalAlignment.Center;
	[Property, Group( "Text Settings" )] public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Center;
	[Property, InlineEditor, Group( "Text Settings" )] public TextRendering.Scope TextScope { get; set; } = TextRendering.Scope.Default;

	private static NodeStyle GetDefaultStyle()
	{
		return new NodeStyle()
		{
			Anchor = NodePoint.CenterMiddle,
			Pivot = NodePoint.CenterMiddle,
			Offset = Vector2.Zero,
			Size = new Vector2( 100, 100 ),
			ChildPadding = Vector2.Zero,
			ClipChildren = false,
			StretchHorizontal = false,
			StretchVertical = false,
			CornerRadius = 0,
			BorderWidth = 0,
			BorderColor = Color.Black,
			Texture = Texture.White,
			Tint = Color.White,
			UvScale = Vector2.One,
			UvOffset = Vector2.Zero
		};
	}

	protected override void UpdateStyle(float scaleFactor)
	{
		Style.BorderWidth = 0; // for debugging
		Style.BorderColor = Color.White;

		TextRendering.Scope scope = TextScope;
		scope.FontSize *= scaleFactor;

		Texture texture = TextRendering.GetOrCreateTexture( scope );
		Style.Texture = texture;

		Vector2 scale = (Layout.Outer.Size * scaleFactor) / texture.Size;
		Style.UvScale = scale;

		float alignX = HorizontalAlignment switch
		{
			HorizontalAlignment.Left => 0f,
			HorizontalAlignment.Center => 0.5f,
			HorizontalAlignment.Right => 1f,
			_ => 0.5f,
		};

		float alignY = VerticalAlignment switch
		{
			VerticalAlignment.Top => 0f,
			VerticalAlignment.Center => 0.5f,
			VerticalAlignment.Bottom => 1f,
			_ => 0.5f,
		};

		float offsetX = alignX * (1f / scale.x - 1f);
		float offsetY = alignY * (1f / scale.y - 1f);

		Style.UvOffset = new Vector2( offsetX, offsetY );
	}
}
using Sandbox.UiPro;

namespace Sandbox;

// Hooks up the Button's OnClick event and responds
// by updating the TextNode
public class ExampleButtonController : Component
{
	[Property] public Button MyButton { get; set; }
	[Property] public TextNode MyText { get; set; }
	[Property, ReadOnly] public int ClickCount { get; set; } = 0;

	protected override void OnStart()
	{
		if ( !MyButton.IsValid() ) return;

		MyButton.OnClick = OnButtonClicked;
	}

	private void OnButtonClicked()
	{
		ClickCount++;

		if ( !MyText.IsValid() ) return;

		TextRendering.Scope scope = MyText.TextScope;
		scope.Text = $"Clicked {ClickCount} Times";
		MyText.TextScope = scope;
	}
}
using System;

namespace Sandbox.UiPro;

public enum NodePoint
{
	TopLeft, TopMiddle, TopRight,
	CenterLeft, CenterMiddle, CenterRight,
	BottomLeft, BottomMiddle, BottomRight,
}

public class NodeStyle
{
	[Property] public NodePoint Anchor { get; set; }
	[Property] public NodePoint Pivot { get; set; }
	[Property] public Vector2 Offset { get; set; }
	[Property] public Vector2 Size { get; set; }
	[Property] public Vector4 ChildPadding { get; set; }
	[Property] public bool ClipChildren { get; set; }
	[Property] public bool StretchHorizontal { get; set; }
	[Property] public bool StretchVertical { get; set; }
	
	[Property, Hide] public float CornerRadius { get; set; }
	[Property, Hide] public float BorderWidth { get; set; }
	[Property, Hide] public Color BorderColor { get; set; }
	[Property, Hide] public Texture Texture { get; set; }
	[Property, Hide] public Color Tint { get; set; }
	[Property, Hide] public Vector2 UvScale { get; set; }
	[Property, Hide] public Vector2 UvOffset { get; set; }
}

public class NodeLayout
{
	public Rect Outer { get; private set; }
	public Rect Inner { get; private set; }
	public Rect ClipRect { get; private set; }
	public float ClipRadius { get; private set; }
	public Rect ChildClipRect { get; private set; }
	public float ChildClipRadius { get; private set; }

	public static NodeLayout GetRootLayout( Vector2 size )
	{
		Rect rootRect = new Rect( Vector2.Zero, size );

		NodeLayout layout = new NodeLayout()
		{
			Outer = rootRect,
			Inner = rootRect,
			ClipRect = rootRect,
			ClipRadius = 0,
			ChildClipRect = rootRect,
			ChildClipRadius = 0
		};

		return layout;
	}

	public void Compute( NodeLayout parentLayout, NodeStyle style )
	{
		Vector2 anchor = AnchorFraction( style.Anchor );
		Vector2 pivot = AnchorFraction( style.Pivot );

		float width = style.StretchHorizontal ? parentLayout.Inner.Size.x : style.Size.x;
		float height = style.StretchVertical ? parentLayout.Inner.Size.y : style.Size.y;

		float x = style.StretchHorizontal
			? parentLayout.Inner.Position.x + style.Offset.x
			: parentLayout.Inner.Position.x + anchor.x * parentLayout.Inner.Size.x - pivot.x * width + style.Offset.x;

		float y = style.StretchVertical
			? parentLayout.Inner.Position.y + style.Offset.y
			: parentLayout.Inner.Position.y + anchor.y * parentLayout.Inner.Size.y - pivot.y * height + style.Offset.y;

		Outer = new Rect( new Vector2( x, y ), new Vector2( width, height ) );

		float innerW = Math.Max( 0f, width - style.ChildPadding.x - style.ChildPadding.z );
		float innerH = Math.Max( 0f, height - style.ChildPadding.y - style.ChildPadding.w );
		Inner = new Rect( new Vector2( x + style.ChildPadding.x, y + style.ChildPadding.y ), new Vector2( innerW, innerH ) );

		ClipRect = parentLayout.ChildClipRect;
		ClipRadius = parentLayout.ChildClipRadius;

		if ( style.ClipChildren )
		{
			float bw = Math.Max( 0f, style.BorderWidth );
			float clipW = Math.Max( 0f, Outer.Size.x - bw * 2f );
			float clipH = Math.Max( 0f, Outer.Size.y - bw * 2f );

			ChildClipRect = new Rect( new Vector2( Outer.Position.x + bw, Outer.Position.y + bw ), new Vector2( clipW, clipH ) );
			ChildClipRadius = Math.Max( 0f, style.CornerRadius - bw );
		}
		else
		{
			ChildClipRect = parentLayout.ChildClipRect;
			ChildClipRadius = parentLayout.ChildClipRadius;
		}
	}

	private static Vector2 AnchorFraction( NodePoint point )
	{
		float fx = point switch
		{
			NodePoint.TopLeft or NodePoint.CenterLeft or NodePoint.BottomLeft => 0f,
			NodePoint.TopMiddle or NodePoint.CenterMiddle or NodePoint.BottomMiddle => 0.5f,
			_ => 1f,
		};
		float fy = point switch
		{
			NodePoint.TopLeft or NodePoint.TopMiddle or NodePoint.TopRight => 0f,
			NodePoint.CenterLeft or NodePoint.CenterMiddle or NodePoint.CenterRight => 0.5f,
			_ => 1f,
		};
		return new Vector2( fx, fy );
	}
}
global using Sandbox;
global using Editor;
global using System.Collections.Generic;
global using System.Linq;
global using Dreams.UltimateLightManager;
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Dreams.UltimateLightManager;

[Library( "UltimateLightManager" )]
[Title( "Ultimate Light Manager" )]
[Description( "Advanced light component with presets, runtime controls, grouping, and an integrated S&box editor workflow." )]
[Category( "Light" )]
[Icon( "tungsten" )]
public class UltimateLightManager : Component, Component.ExecuteInEditor
{
    public enum LightTypeEnum
    {
        Point,
        Spot
    }

    public enum LightPreset
    {
        Custom,
        Candle,
        Torch,
        Neon,
        Alarm,
        BrokenLamp,
        SciFi,
        StreetLight
    }

    public static void SetGroupState( string groupName, bool isEnabled )
    {
        foreach ( var light in GetLightsInGroup( groupName ) )
        {
            light.SetEnabledState( isEnabled );
        }
    }

    public static void SetGroupBrightness( string groupName, float brightness )
    {
        brightness = Math.Max( brightness, 0f );

        foreach ( var light in GetLightsInGroup( groupName ) )
        {
            light.SetBrightnessLevel( brightness );
        }
    }

    public static void SetGroupColor( string groupName, Color color )
    {
        foreach ( var light in GetLightsInGroup( groupName ) )
        {
            light.SetLightColorValue( color );
        }
    }

    public static void ApplyPresetToGroup( string groupName, LightPreset preset )
    {
        foreach ( var light in GetLightsInGroup( groupName ) )
        {
            light.ApplyPreset( preset );
        }
    }

    public static void TriggerGroupFlash( string groupName, float duration = 0.15f, float brightnessMultiplier = 2f )
    {
        foreach ( var light in GetLightsInGroup( groupName ) )
        {
            light.TriggerFlash( duration, brightnessMultiplier );
        }
    }

    public static void TriggerGroupAlarm( string groupName, float duration = 2f )
    {
        foreach ( var light in GetLightsInGroup( groupName ) )
        {
            light.TriggerAlarm( duration );
        }
    }

    public static void SetPowerGridState( string powerGridTag, bool isPowered )
    {
        foreach ( var light in GetLightsInPowerGrid( powerGridTag ) )
        {
            light.SetPowered( isPowered );
        }
    }

    private static IEnumerable<UltimateLightManager> GetLightsInGroup( string groupName )
    {
        return EnumerateAllLights().Where( light => string.Equals( light.LightGroup, groupName, StringComparison.OrdinalIgnoreCase ) );
    }

    private static IEnumerable<UltimateLightManager> GetLightsInPowerGrid( string powerGridTag )
    {
        return EnumerateAllLights().Where( light => string.Equals( light.PowerGridTag, powerGridTag, StringComparison.OrdinalIgnoreCase ) );
    }

    private static IEnumerable<UltimateLightManager> EnumerateAllLights()
    {
        var visitedScenes = new HashSet<Scene>();
        var visitedLights = new HashSet<UltimateLightManager>();

        foreach ( var scene in EnumerateCandidateScenes() )
        {
            if ( scene == null || !visitedScenes.Add( scene ) )
            {
                continue;
            }

            foreach ( var light in scene.GetAllComponents<UltimateLightManager>() )
            {
                if ( light != null && visitedLights.Add( light ) )
                {
                    yield return light;
                }
            }
        }
    }

    private static IEnumerable<Scene> EnumerateCandidateScenes()
    {
        if ( Game.ActiveScene != null )
        {
            yield return Game.ActiveScene;
        }

        foreach ( var scene in Scene.All )
        {
            if ( scene != null )
            {
                yield return scene;
            }
        }
    }

    [Property, Order( -1 ), Group( "Management" )]
    public string LightGroup { get; set; } = "Default";

    [Property, Group( "Management" )]
    public string PowerGridTag { get; set; } = string.Empty;

    [Property, Group( "Management" )]
    public float StartDelay { get; set; } = 0.0f;

    [Property, Group( "Management" )]
    public bool AutoDesync { get; set; } = true;

    [Property, Group( "Management" )]
    public bool ShowDebugGizmos { get; set; } = false;

    [Property, Group( "Management" )]
    public bool ForceNetworkObjectMode { get; set; } = true;

    [Property, Group( "General" ), Sync( SyncFlags.FromHost )]
    public LightTypeEnum TargetLightType { get; set; } = LightTypeEnum.Point;

    [Property, Group( "General" ), Sync( SyncFlags.FromHost )]
    public bool IsEnabled { get; set; } = true;

    [Property, Group( "General" ), Sync( SyncFlags.FromHost )]
    public Color LightColor { get; set; } = Color.White;

    [Property, Group( "General" ), Range( 0, 100 ), Sync( SyncFlags.FromHost | SyncFlags.Interpolate )]
    public float Brightness { get; set; } = 1.0f;

    [Property, Group( "General" ), Range( 0, 10 )]
    public float VolumetricBoost { get; set; } = 1.0f;

    [Property, Group( "General" )]
    public bool CastShadows { get; set; } = true;

    [Property, Group( "Presets" ), Sync( SyncFlags.FromHost )]
    public LightPreset SelectedPreset { get; set; } = LightPreset.Custom;

    [Property, Group( "Presets" ), Sync( SyncFlags.FromHost )]
    public bool AutoApplyPreset { get; set; } = true;

    [Property, Group( "Transitions" )]
    public bool EnableFade { get; set; } = false;

    [Property, Group( "Transitions" )]
    public float FadeInDuration { get; set; } = 0.2f;

    [Property, Group( "Transitions" )]
    public float FadeOutDuration { get; set; } = 0.2f;

    [Property, Group( "Audio" )]
    public SoundEvent AmbientSound { get; set; }

    [Property, Group( "Audio" )]
    public SoundEvent ToggleOnSound { get; set; }

    [Property, Group( "Audio" )]
    public SoundEvent ToggleOffSound { get; set; }

    [Property, Group( "Audio" )]
    public bool ModulateVolumeWithLight { get; set; } = true;

    [Property, Group( "Audio" )]
    public bool ModulatePitchWithLight { get; set; } = false;

    [Property, Group( "Audio" ), Range( 0, 5 )]
    public float BaseVolume { get; set; } = 1.0f;

    [Property, Group( "Audio" ), Range( 0.5f, 2f )]
    public float MinPitch { get; set; } = 0.9f;

    [Property, Group( "Audio" ), Range( 0.5f, 2f )]
    public float MaxPitch { get; set; } = 1.1f;

    [Property, Group( "Optimization" )]
    public float MaxDistance { get; set; } = 2500.0f;

    [Property, Group( "Optimization" )]
    public float ShadowMaxDistance { get; set; } = 800.0f;

    [Property, Group( "Optimization" )]
    public bool EnableCulling { get; set; } = true;

    [Property, Group( "Optimization" )]
    public bool EnableAdaptiveUpdates { get; set; } = false;

    [Property, Group( "Optimization" ), Range( 1, 120 )]
    public float NearUpdateRate { get; set; } = 60.0f;

    [Property, Group( "Optimization" ), Range( 1, 120 )]
    public float FarUpdateRate { get; set; } = 12.0f;

    [Property, Group( "Gameplay" )]
    public float DefaultAlarmDuration { get; set; } = 2.0f;

    [Property, Group( "Gameplay" ), Sync( SyncFlags.FromHost )]
    public Color AlarmColor { get; set; } = new Color( 1.0f, 0.15f, 0.1f );

    [Property, Group( "Gameplay" )]
    public float AlarmStrobeSpeed { get; set; } = 8.0f;

    [Property, Group( "Gameplay" )]
    public float AlarmBrightnessMultiplier { get; set; } = 1.5f;

    [Property, FeatureEnabled( "Fire & Candle" )]
    public bool EnableFire { get; set; } = false;

    [Property, Feature( "Fire & Candle" )]
    public float FireSpeed { get; set; } = 12.0f;

    [Property, Feature( "Fire & Candle" ), Range( 0, 1 )]
    public float FireIntensity { get; set; } = 0.3f;

    [Property, Feature( "Fire & Candle" ), Range( 0, 2 )]
    public float FireChaos { get; set; } = 1.0f;

    [Property, FeatureEnabled( "Horror Mode" )]
    public bool EnableHorror { get; set; } = false;

    [Property, Feature( "Horror Mode" )]
    public float MinFlickerDelay { get; set; } = 0.05f;

    [Property, Feature( "Horror Mode" )]
    public float MaxFlickerDelay { get; set; } = 0.4f;

    [Property, Feature( "Horror Mode" ), Range( 0, 1 )]
    public float DamageSeverity { get; set; } = 0.8f;

    [Property, Feature( "Horror Mode" )]
    public SoundEvent SparkSound { get; set; }

    [Property, FeatureEnabled( "Disco Mode" )]
    public bool EnableDisco { get; set; } = false;

    [Property, Feature( "Disco Mode" )]
    public float DiscoSpeed { get; set; } = 20.0f;

    [Property, Feature( "Disco Mode" ), Range( 0, 1 )]
    public float DiscoSaturation { get; set; } = 1.0f;

    [Property, Feature( "Disco Mode" ), Range( 0, 1 )]
    public float DiscoValue { get; set; } = 1.0f;

    [Property, FeatureEnabled( "Color Transition" )]
    public bool EnableColorTransition { get; set; } = false;

    [Property, Feature( "Color Transition" ), Sync( SyncFlags.FromHost )]
    public Color SecondaryColor { get; set; } = new Color( 0.2f, 0.85f, 1.0f );

    [Property, Feature( "Color Transition" )]
    public float ColorTransitionSpeed { get; set; } = 1.0f;

    [Property, FeatureEnabled( "Proximity Sensor" )]
    public bool EnableSensor { get; set; } = false;

    [Property, Feature( "Proximity Sensor" )]
    public float SensorRange { get; set; } = 300.0f;

    [Property, Feature( "Proximity Sensor" ), Range( 0, 1 )]
    public float SensorMinBrightness { get; set; } = 0.0f;

    [Property, Feature( "Proximity Sensor" ), Range( 0, 1 )]
    public float SensorMaxBrightness { get; set; } = 1.0f;

    [Property, Feature( "Proximity Sensor" ), Range( 1, 20 )]
    public float SensorSmoothness { get; set; } = 5.0f;

    [Property, Feature( "Proximity Sensor" )]
    public bool InvertSensor { get; set; } = false;

    [Property, FeatureEnabled( "Motion Sway" )]
    public bool EnableSway { get; set; } = false;

    [Property, Feature( "Motion Sway" )]
    public float SwaySpeedPitch { get; set; } = 1.0f;

    [Property, Feature( "Motion Sway" )]
    public float SwayAmountPitch { get; set; } = 5.0f;

    [Property, Feature( "Motion Sway" )]
    public float SwaySpeedRoll { get; set; } = 0.7f;

    [Property, Feature( "Motion Sway" )]
    public float SwayAmountRoll { get; set; } = 3.0f;

    [Property, FeatureEnabled( "Flicker Pattern" )]
    public bool EnablePattern { get; set; } = false;

    [Property, Feature( "Flicker Pattern" )]
    public string Pattern { get; set; } = "mmnmmommommnonmmonqnmmo";

    [Property, Feature( "Flicker Pattern" )]
    public float PatternSpeed { get; set; } = 10.0f;

    [Property, FeatureEnabled( "Pulse" )]
    public bool EnablePulse { get; set; } = false;

    [Property, Feature( "Pulse" )]
    public float PulseSpeed { get; set; } = 1.0f;

    [Property, Feature( "Pulse" ), Range( 0, 1 )]
    public float PulseMin { get; set; } = 0.2f;

    [Property, FeatureEnabled( "Strobe" )]
    public bool EnableStrobe { get; set; } = false;

    [Property, Feature( "Strobe" )]
    public float StrobeSpeed { get; set; } = 10.0f;

    [Property, Feature( "Strobe" ), Range( 0.1f, 0.9f )]
    public float StrobeDutyCycle { get; set; } = 0.5f;

    [Property, FeatureEnabled( "Kelvin" )]
    public bool EnableKelvin { get; set; } = false;

    [Property, Feature( "Kelvin" ), Range( 1000, 12000 )]
    public int KelvinTemperature { get; set; } = 4500;

    [Property, FeatureEnabled( "Power Surge" )]
    public bool EnablePowerSurge { get; set; } = false;

    [Property, Feature( "Power Surge" )]
    public float SurgeMinInterval { get; set; } = 4.0f;

    [Property, Feature( "Power Surge" )]
    public float SurgeMaxInterval { get; set; } = 10.0f;

    [Property, Feature( "Power Surge" )]
    public float SurgeDuration { get; set; } = 0.15f;

    [Property, Feature( "Power Surge" )]
    public float SurgeBrightnessMultiplier { get; set; } = 1.8f;

    public bool Powered => PoweredState;
    public float ExternalBrightnessMultiplier => ExternalBrightnessMultiplierState;
    public bool HasColorOverride => HasExternalColorOverrideState;
    public bool AlarmActive => AlarmEndTimeState > RealTime.Now;

    private PointLight _pointLight;
    private SpotLight _spotLight;
    private Light ActiveLight => TargetLightType == LightTypeEnum.Point ? (Light)_pointLight : _spotLight;

    private int _lastKelvin = -1;
    private Color _cachedKelvinColor = Color.White;
    private Rotation _baseRotation;
    private bool _isInitialized;
    private bool _hasAppliedPreset;
    private LightPreset _lastAppliedPreset = LightPreset.Custom;
    private LightPreset _lastObservedPreset = LightPreset.Custom;
    private bool _lastObservedAutoApplyPreset = true;
    private LightTypeEnum _lastSyncedLightType = LightTypeEnum.Point;
    private float _brokenMultiplier = 1.0f;
    private float _nextFlicker;
    private float _sensorWeightTarget = 1.0f;
    private float _sensorWeightCurrent = 1.0f;
    private float _randomTimeOffset;
    private float _creationTime;
    private float _lastUpdateTimestamp;
    private float _enabledBlend = 1.0f;
    private float _nextAdaptiveUpdateTime;
    private float _nextViewerCameraRefreshTime;
    private float _nextSurgeTime;
    private float _surgeEndTime;
    private bool _hasOutputState;
    private bool _lastOutputEnabled;
    private CameraComponent _cachedViewerCamera;
    private SoundHandle _ambientSoundHandle;

    [Sync( SyncFlags.FromHost )]
    private bool PoweredState { get; set; } = true;

    [Sync( SyncFlags.FromHost | SyncFlags.Interpolate )]
    private float ExternalBrightnessMultiplierState { get; set; } = 1.0f;

    [Sync( SyncFlags.FromHost )]
    private bool HasExternalColorOverrideState { get; set; }

    [Sync( SyncFlags.FromHost )]
    private Color ExternalColorOverrideState { get; set; } = Color.White;

    [Sync( SyncFlags.FromHost )]
    private float FlashEndTimeState { get; set; }

    [Sync( SyncFlags.FromHost )]
    private float FlashBrightnessMultiplierState { get; set; } = 1.0f;

    [Sync( SyncFlags.FromHost )]
    private float AlarmEndTimeState { get; set; }

    protected override void OnAwake()
    {
        EnsureNetworkMode();
    }

    protected override void OnStart()
    {
        EnsureNetworkMode();

        _baseRotation = LocalRotation;
        _creationTime = RealTime.Now;
        _lastUpdateTimestamp = _creationTime;
        _enabledBlend = IsEnabled ? 1.0f : 0.0f;

        if ( AutoDesync )
        {
            _randomTimeOffset = Game.Random.Float( 0f, 100f );
        }

        SyncComponentsIfNeeded( force: true );

        if ( AutoApplyPreset )
        {
            ApplyPresetInternal( SelectedPreset );
        }

        ScheduleNextSurge( _creationTime );

        _lastObservedPreset = SelectedPreset;
        _lastObservedAutoApplyPreset = AutoApplyPreset;
        _isInitialized = true;
    }

    protected override void OnUpdate()
    {
        if ( !_isInitialized )
        {
            return;
        }

        SyncComponentsIfNeeded();
        SyncPresetSelectionIfNeeded();

        var light = ActiveLight;
        if ( light == null )
        {
            return;
        }

        bool isPlaying = Game.IsPlaying;
        float absoluteTime = RealTime.Now;
        float effectTime = (isPlaying ? Time.Now : absoluteTime) + _randomTimeOffset;
        float deltaTime = GetFrameDelta( absoluteTime );

        if ( isPlaying && StartDelay > 0f && (absoluteTime - _creationTime) < StartDelay )
        {
            DisableOutput( light );
            return;
        }

        var viewerCam = GetViewerCamera( absoluteTime );
        float distSq = viewerCam != null ? WorldPosition.DistanceSquared( viewerCam.WorldPosition ) : 0f;

        if ( ShouldSkipAdaptiveUpdate( isPlaying, viewerCam, distSq, absoluteTime ) )
        {
            UpdateAmbientSoundPosition();
            return;
        }

        UpdateSensorWeight( viewerCam, distSq, deltaTime );
        UpdateEnabledBlend( deltaTime );
        UpdateSway( effectTime );

        if ( isPlaying && EnableCulling && viewerCam != null && distSq > MaxDistance * MaxDistance )
        {
            DisableOutput( light );
            return;
        }

        float fx = 1.0f;

        fx *= EvaluatePulseAndStrobe( effectTime );
        fx *= EvaluatePattern( effectTime );
        fx *= EvaluateFire( effectTime );
        fx *= EvaluateHorror( effectTime, isPlaying );
        fx *= EvaluatePowerSurge( absoluteTime );
        fx *= EvaluateAlarm( effectTime, absoluteTime );

        if ( absoluteTime < FlashEndTimeState )
        {
            fx *= FlashBrightnessMultiplierState;
        }

        float finalBrightness = Brightness * fx * _sensorWeightCurrent * _enabledBlend * ExternalBrightnessMultiplierState;
        finalBrightness = Math.Max( finalBrightness, 0f );

        bool shouldBeEnabled = finalBrightness > 0.001f;
        light.Enabled = shouldBeEnabled;

        Color resolvedColor = ResolveLightColor( effectTime, absoluteTime );
        light.LightColor = resolvedColor * finalBrightness;
        light.Shadows = CastShadows && ( !isPlaying || distSq < ShadowMaxDistance * ShadowMaxDistance );
        light.FogStrength = VolumetricBoost;

        UpdateOutputState( shouldBeEnabled, true );
        ManageAudio( shouldBeEnabled, finalBrightness / Math.Max( Brightness, 0.01f ) );
    }

    public void TurnOn()
    {
        SetEnabledState( true );
    }

    [Button]
    public void ApplySelectedPreset()
    {
        ApplyPreset( SelectedPreset );
    }

    public void TurnOff()
    {
        SetEnabledState( false );
    }

    public void Toggle()
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        IsEnabled = !IsEnabled;
    }

    public void SetPowered( bool powered )
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        PoweredState = powered;
    }

    public void SetExternalBrightness( float multiplier )
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        ExternalBrightnessMultiplierState = Math.Max( multiplier, 0f );
    }

    public void ResetExternalBrightness()
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        ExternalBrightnessMultiplierState = 1.0f;
    }

    public void SetColorOverride( Color color )
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        HasExternalColorOverrideState = true;
        ExternalColorOverrideState = color;
    }

    public void ClearColorOverride()
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        HasExternalColorOverrideState = false;
        ExternalColorOverrideState = Color.White;
    }

    public void TriggerFlash( float duration = 0.15f, float brightnessMultiplier = 2f )
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        FlashEndTimeState = RealTime.Now + Math.Max( duration, 0.01f );
        FlashBrightnessMultiplierState = Math.Max( brightnessMultiplier, 1.0f );
    }

    public void TriggerAlarm( float duration = -1f )
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        if ( duration <= 0f )
        {
            duration = DefaultAlarmDuration;
        }

        AlarmEndTimeState = Math.Max( AlarmEndTimeState, RealTime.Now + duration );
    }

    public void ClearAlarm()
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        AlarmEndTimeState = 0f;
    }

    [Button]
    public void PreviewFlash()
    {
        TriggerFlash();
    }

    [Button]
    public void PreviewAlarm()
    {
        TriggerAlarm();
    }

    [Button]
    public void ResetRuntimeOverrides()
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        ClearAlarm();
        ClearColorOverride();
        ResetExternalBrightness();
        SetPowered( true );
    }

    public void ApplyPreset( LightPreset preset )
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        ApplyPresetInternal( preset );
    }

    public void SetEnabledState( bool isEnabled )
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        IsEnabled = isEnabled;
    }

    public void SetBrightnessLevel( float brightness )
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        Brightness = Math.Max( brightness, 0f );
    }

    public void SetLightColorValue( Color color )
    {
        if ( ShouldIgnoreNetworkMutation() )
        {
            return;
        }

        LightColor = color;
    }

    private void ApplyPresetInternal( LightPreset preset )
    {
        SelectedPreset = preset;
        ResetPresetControlledFeatures();

        switch ( preset )
        {
            case LightPreset.Candle:
                Brightness = 0.75f;
                LightColor = new Color( 1.0f, 0.76f, 0.5f );
                SecondaryColor = new Color( 1.0f, 0.66f, 0.35f );
                EnableKelvin = true;
                KelvinTemperature = 1800;
                EnableFire = true;
                FireSpeed = 10.0f;
                FireIntensity = 0.18f;
                FireChaos = 0.6f;
                VolumetricBoost = 0.6f;
                break;

            case LightPreset.Torch:
                Brightness = 1.35f;
                LightColor = new Color( 1.0f, 0.72f, 0.38f );
                SecondaryColor = new Color( 1.0f, 0.45f, 0.2f );
                EnableKelvin = true;
                KelvinTemperature = 2200;
                EnableFire = true;
                FireSpeed = 13.0f;
                FireIntensity = 0.28f;
                FireChaos = 1.0f;
                VolumetricBoost = 1.4f;
                break;

            case LightPreset.Neon:
                Brightness = 1.15f;
                LightColor = new Color( 0.2f, 0.95f, 1.0f );
                SecondaryColor = new Color( 1.0f, 0.2f, 0.85f );
                EnableColorTransition = true;
                ColorTransitionSpeed = 0.65f;
                EnablePulse = true;
                PulseSpeed = 1.2f;
                PulseMin = 0.75f;
                CastShadows = false;
                VolumetricBoost = 0.25f;
                break;

            case LightPreset.Alarm:
                Brightness = 2.0f;
                LightColor = new Color( 1.0f, 0.18f, 0.12f );
                AlarmColor = LightColor;
                EnableStrobe = true;
                StrobeSpeed = 7.0f;
                StrobeDutyCycle = 0.45f;
                CastShadows = false;
                VolumetricBoost = 1.1f;
                break;

            case LightPreset.BrokenLamp:
                Brightness = 1.0f;
                LightColor = new Color( 1.0f, 0.93f, 0.82f );
                EnableHorror = true;
                MinFlickerDelay = 0.04f;
                MaxFlickerDelay = 0.25f;
                DamageSeverity = 0.85f;
                EnableKelvin = true;
                KelvinTemperature = 3400;
                break;

            case LightPreset.SciFi:
                Brightness = 1.6f;
                LightColor = new Color( 0.35f, 0.78f, 1.0f );
                SecondaryColor = new Color( 0.1f, 1.0f, 0.8f );
                EnableColorTransition = true;
                ColorTransitionSpeed = 1.15f;
                EnablePulse = true;
                PulseSpeed = 0.85f;
                PulseMin = 0.55f;
                VolumetricBoost = 2.0f;
                break;

            case LightPreset.StreetLight:
                Brightness = 1.4f;
                LightColor = new Color( 1.0f, 0.84f, 0.68f );
                EnableKelvin = true;
                KelvinTemperature = 3500;
                MaxDistance = 4500.0f;
                ShadowMaxDistance = 1200.0f;
                VolumetricBoost = 0.55f;
                break;

            case LightPreset.Custom:
            default:
                break;
        }

        _lastAppliedPreset = preset;
        _hasAppliedPreset = true;
    }

    private void ResetPresetControlledFeatures()
    {
        EnableFire = false;
        EnableHorror = false;
        EnableDisco = false;
        EnableColorTransition = false;
        EnablePulse = false;
        EnableStrobe = false;
        EnableKelvin = false;
    }

    private void SyncPresetSelectionIfNeeded()
    {
        bool autoApplyChanged = AutoApplyPreset != _lastObservedAutoApplyPreset;
        bool presetChanged = SelectedPreset != _lastObservedPreset;

        _lastObservedAutoApplyPreset = AutoApplyPreset;
        _lastObservedPreset = SelectedPreset;

        if ( !AutoApplyPreset )
        {
            return;
        }

        if ( autoApplyChanged || presetChanged || !_hasAppliedPreset || SelectedPreset != _lastAppliedPreset )
        {
            ApplyPresetInternal( SelectedPreset );
        }
    }

    private void SyncComponentsIfNeeded( bool force = false )
    {
        bool missingActiveLight = TargetLightType == LightTypeEnum.Point ? _pointLight == null : _spotLight == null;
        if ( !force && !missingActiveLight && TargetLightType == _lastSyncedLightType )
        {
            return;
        }

        Component createdComponent = null;

        if ( TargetLightType == LightTypeEnum.Point )
        {
            if ( _pointLight == null )
            {
                _pointLight = Components.GetOrCreate<PointLight>();
                createdComponent = _pointLight;
            }

            if ( _spotLight != null && _spotLight.Enabled )
            {
                _spotLight.Enabled = false;
            }
        }
        else
        {
            if ( _spotLight == null )
            {
                _spotLight = Components.GetOrCreate<SpotLight>();
                createdComponent = _spotLight;
            }

            if ( _pointLight != null && _pointLight.Enabled )
            {
                _pointLight.Enabled = false;
            }
        }

        _lastSyncedLightType = TargetLightType;

        if ( createdComponent != null && Game.IsPlaying && GameObject.NetworkMode == NetworkMode.Object )
        {
            GameObject.Network.Refresh( createdComponent );
        }
    }

    private CameraComponent GetViewerCamera( float absoluteTime )
    {
        var sceneCamera = Scene.Camera;
        if ( sceneCamera != null )
        {
            _cachedViewerCamera = sceneCamera;
            _nextViewerCameraRefreshTime = absoluteTime + 0.25f;
            return sceneCamera;
        }

        if ( _cachedViewerCamera != null && absoluteTime < _nextViewerCameraRefreshTime )
        {
            return _cachedViewerCamera;
        }

        _nextViewerCameraRefreshTime = absoluteTime + 0.25f;
        _cachedViewerCamera = Scene.GetAllComponents<CameraComponent>().FirstOrDefault( camera => camera != null && camera.Enabled );
        return _cachedViewerCamera;
    }

    private void EnsureNetworkMode()
    {
        if ( !ForceNetworkObjectMode || !Game.IsPlaying || GameObject.NetworkMode == NetworkMode.Object )
        {
            return;
        }

        GameObject.NetworkMode = NetworkMode.Object;
    }

    private bool ShouldIgnoreNetworkMutation()
    {
        return Game.IsPlaying && IsProxy;
    }

    private float GetFrameDelta( float absoluteTime )
    {
        float delta = Math.Clamp( absoluteTime - _lastUpdateTimestamp, 0.0001f, 0.25f );
        _lastUpdateTimestamp = absoluteTime;
        return delta;
    }

    private bool ShouldSkipAdaptiveUpdate( bool isPlaying, CameraComponent viewerCam, float distSq, float absoluteTime )
    {
        if ( !EnableAdaptiveUpdates || !isPlaying || viewerCam == null )
        {
            return false;
        }

        if ( absoluteTime < _nextAdaptiveUpdateTime )
        {
            return true;
        }

        float maxDistanceSq = Math.Max( MaxDistance * MaxDistance, 1f );
        float distRatio = Math.Clamp( distSq / maxDistanceSq, 0f, 1f );
        float nearInterval = 1f / Math.Max( NearUpdateRate, 1f );
        float farInterval = 1f / Math.Max( FarUpdateRate, 1f );
        _nextAdaptiveUpdateTime = absoluteTime + MathX.Lerp( nearInterval, farInterval, distRatio );
        return false;
    }

    private void UpdateSensorWeight( CameraComponent viewerCam, float distSq, float deltaTime )
    {
        if ( EnableSensor && viewerCam != null && SensorRange > 0.01f )
        {
            float distRatio = Math.Clamp( 1.0f - (MathF.Sqrt( distSq ) / SensorRange), 0f, 1f );
            float rawWeight = InvertSensor ? 1.0f - distRatio : distRatio;
            _sensorWeightTarget = MathX.Lerp( SensorMinBrightness, SensorMaxBrightness, rawWeight );
        }
        else
        {
            _sensorWeightTarget = 1.0f;
        }

        _sensorWeightCurrent = MathX.Lerp( _sensorWeightCurrent, _sensorWeightTarget, Math.Clamp( deltaTime * SensorSmoothness, 0f, 1f ) );
    }

    private void UpdateEnabledBlend( float deltaTime )
    {
        float target = IsEnabled && PoweredState ? 1.0f : 0.0f;

        if ( !EnableFade )
        {
            _enabledBlend = target;
            return;
        }

        float duration = target > _enabledBlend ? Math.Max( FadeInDuration, 0.0001f ) : Math.Max( FadeOutDuration, 0.0001f );
        float lerp = Math.Clamp( deltaTime / duration, 0f, 1f );
        _enabledBlend = MathX.Lerp( _enabledBlend, target, lerp );

        if ( Math.Abs( _enabledBlend - target ) < 0.001f )
        {
            _enabledBlend = target;
        }
    }

    private void UpdateSway( float effectTime )
    {
        if ( EnableSway )
        {
            float pitch = MathF.Sin( effectTime * SwaySpeedPitch ) * SwayAmountPitch;
            float roll = MathF.Cos( effectTime * SwaySpeedRoll ) * SwayAmountRoll;
            LocalRotation = _baseRotation * Rotation.From( pitch, 0f, roll );
            return;
        }

        LocalRotation = _baseRotation;
    }

    private float EvaluatePulseAndStrobe( float effectTime )
    {
        if ( EnableStrobe )
        {
            float cycle = (effectTime * StrobeSpeed) % 1.0f;
            return cycle < StrobeDutyCycle ? 1.0f : 0.0f;
        }

        if ( EnablePulse )
        {
            float sine = (MathF.Sin( effectTime * PulseSpeed * 2.0f ) + 1.0f) * 0.5f;
            return MathX.Lerp( PulseMin, 1.0f, sine );
        }

        return 1.0f;
    }

    private float EvaluatePattern( float effectTime )
    {
        if ( !EnablePattern || string.IsNullOrWhiteSpace( Pattern ) )
        {
            return 1.0f;
        }

        int index = (int)(effectTime * PatternSpeed) % Pattern.Length;
        float value = Math.Max( 0, (char.ToLower( Pattern[index] ) - 'a') / 12.0f );
        return value;
    }

    private float EvaluateFire( float effectTime )
    {
        if ( !EnableFire )
        {
            return 1.0f;
        }

        float noise = MathF.Sin( effectTime * FireSpeed ) + MathF.Sin( effectTime * FireSpeed * 0.5f );

        if ( FireChaos > 0f )
        {
            noise += MathF.Sin( effectTime * FireSpeed * 1.5f ) * FireChaos;
        }

        return 1.0f - (noise * 0.15f * FireIntensity);
    }

    private float EvaluateHorror( float effectTime, bool isPlaying )
    {
        if ( !EnableHorror )
        {
            return 1.0f;
        }

        if ( effectTime > _nextFlicker )
        {
            bool isDamaged = Game.Random.Float( 0f, 1f ) < DamageSeverity;
            _brokenMultiplier = isDamaged ? Game.Random.Float( 0.0f, 0.4f ) : 1.0f;
            _nextFlicker = effectTime + Game.Random.Float( MinFlickerDelay, MaxFlickerDelay );

            if ( isPlaying && isDamaged && SparkSound != null && _brokenMultiplier < 0.2f )
            {
                Sound.Play( SparkSound, WorldPosition );
            }
        }

        return _brokenMultiplier;
    }

    private float EvaluatePowerSurge( float absoluteTime )
    {
        if ( !EnablePowerSurge )
        {
            return 1.0f;
        }

        if ( _nextSurgeTime <= 0f )
        {
            ScheduleNextSurge( absoluteTime );
        }

        if ( absoluteTime >= _nextSurgeTime )
        {
            _surgeEndTime = absoluteTime + Math.Max( SurgeDuration, 0.01f );
            ScheduleNextSurge( _surgeEndTime );
        }

        return absoluteTime < _surgeEndTime ? Math.Max( SurgeBrightnessMultiplier, 1.0f ) : 1.0f;
    }

    private float EvaluateAlarm( float effectTime, float absoluteTime )
    {
        if ( absoluteTime >= AlarmEndTimeState )
        {
            return 1.0f;
        }

        float cycle = (effectTime * AlarmStrobeSpeed) % 1.0f;
        float gate = cycle < 0.5f ? 1.0f : 0.15f;
        return gate * Math.Max( AlarmBrightnessMultiplier, 0f );
    }

    private void ScheduleNextSurge( float absoluteTime )
    {
        float minInterval = Math.Min( SurgeMinInterval, SurgeMaxInterval );
        float maxInterval = Math.Max( SurgeMinInterval, SurgeMaxInterval );
        _nextSurgeTime = absoluteTime + Game.Random.Float( Math.Max( minInterval, 0.01f ), Math.Max( maxInterval, 0.01f ) );
    }

    private Color ResolveLightColor( float effectTime, float absoluteTime )
    {
        Color color = LightColor;

        if ( EnableKelvin )
        {
            if ( KelvinTemperature != _lastKelvin )
            {
                _cachedKelvinColor = KelvinToColor( KelvinTemperature );
                _lastKelvin = KelvinTemperature;
            }

            color = _cachedKelvinColor;
        }

        if ( EnableColorTransition )
        {
            float lerp = (MathF.Sin( effectTime * ColorTransitionSpeed ) + 1.0f) * 0.5f;
            color = Color.Lerp( color, SecondaryColor, lerp, true );
        }

        if ( EnableDisco )
        {
            color = new ColorHsv( (effectTime * DiscoSpeed) % 360f, DiscoSaturation, DiscoValue ).ToColor();
        }

        if ( absoluteTime < AlarmEndTimeState )
        {
            color = Color.Lerp( color, AlarmColor, 0.85f, true );
        }

        if ( HasExternalColorOverrideState )
        {
            color = ExternalColorOverrideState;
        }

        return color;
    }

    private void UpdateOutputState( bool shouldBeEnabled, bool playOneShot )
    {
        if ( !_hasOutputState )
        {
            _hasOutputState = true;
            _lastOutputEnabled = shouldBeEnabled;
            return;
        }

        if ( _lastOutputEnabled == shouldBeEnabled )
        {
            return;
        }

        if ( playOneShot && Game.IsPlaying )
        {
            if ( shouldBeEnabled && ToggleOnSound != null )
            {
                Sound.Play( ToggleOnSound, WorldPosition );
            }
            else if ( !shouldBeEnabled && ToggleOffSound != null )
            {
                Sound.Play( ToggleOffSound, WorldPosition );
            }
        }

        _lastOutputEnabled = shouldBeEnabled;
    }

    private void DisableOutput( Light light )
    {
        light.Enabled = false;
        UpdateOutputState( false, false );
        ManageAudio( false, 0f );
    }

    private void UpdateAmbientSoundPosition()
    {
        if ( _ambientSoundHandle != null && !_ambientSoundHandle.IsStopped )
        {
            _ambientSoundHandle.Position = WorldPosition;
        }
    }

    private void ManageAudio( bool isLightEnabled, float intensityRatio )
    {
        if ( !Game.IsPlaying || AmbientSound == null )
        {
            return;
        }

        if ( isLightEnabled )
        {
            if ( _ambientSoundHandle == null || _ambientSoundHandle.IsStopped )
            {
                _ambientSoundHandle = Sound.Play( AmbientSound, WorldPosition );
            }

            if ( _ambientSoundHandle != null )
            {
                _ambientSoundHandle.Position = WorldPosition;
                _ambientSoundHandle.Volume = BaseVolume * (ModulateVolumeWithLight ? intensityRatio : 1.0f);
                _ambientSoundHandle.Pitch = ModulatePitchWithLight ? MathX.Lerp( MinPitch, MaxPitch, intensityRatio ) : 1.0f;
            }
        }
        else if ( _ambientSoundHandle != null )
        {
            _ambientSoundHandle.Stop();
            _ambientSoundHandle = null;
        }
    }

    private Color KelvinToColor( int kelvin )
    {
        float temperature = kelvin / 100.0f;
        float red;
        float green;
        float blue;

        if ( temperature <= 66f )
        {
            red = 255f;
            green = Math.Clamp( 99.47f * MathF.Log( temperature ) - 161.11f, 0f, 255f );
        }
        else
        {
            red = Math.Clamp( 329.698f * MathF.Pow( temperature - 60f, -0.133f ), 0f, 255f );
            green = Math.Clamp( 288.12f * MathF.Pow( temperature - 60f, -0.075f ), 0f, 255f );
        }

        if ( temperature >= 66f )
        {
            blue = 255f;
        }
        else if ( temperature <= 19f )
        {
            blue = 0f;
        }
        else
        {
            blue = Math.Clamp( 138.51f * MathF.Log( temperature - 10f ) - 305.04f, 0f, 255f );
        }

        return new Color( red / 255f, green / 255f, blue / 255f );
    }

    protected override void DrawGizmos()
    {
        if ( !ShowDebugGizmos )
        {
            return;
        }

        Gizmo.Draw.Text( $"Group: {LightGroup}", new Transform( Vector3.Up * 20f ), size: 12 );

        if ( !string.IsNullOrWhiteSpace( PowerGridTag ) )
        {
            Gizmo.Draw.Text( $"Grid: {PowerGridTag}", new Transform( Vector3.Up * 34f ), size: 12 );
        }

        if ( EnableSensor )
        {
            Gizmo.Draw.Color = Color.Cyan.WithAlpha( 0.2f );
            Gizmo.Draw.SolidSphere( Vector3.Zero, SensorRange );
            Gizmo.Draw.Color = Color.Cyan;
            Gizmo.Draw.LineSphere( Vector3.Zero, SensorRange );
        }

        if ( EnableCulling )
        {
            Gizmo.Draw.Color = Color.Red.WithAlpha( 0.05f );
            Gizmo.Draw.LineSphere( Vector3.Zero, MaxDistance );
            Gizmo.Draw.Text( $"Cull: {MaxDistance}", new Transform( Vector3.Up * (MaxDistance * 0.9f) ), size: 14 );
        }
    }

    protected override void OnDestroy()
    {
        if ( _ambientSoundHandle != null )
        {
            _ambientSoundHandle.Stop();
        }
    }
}