πŸ” s&box Package Code Search

Search C# source code, UI razor templates, shaders, and configs across s&box packages.

Showing code results for query: * (92 total matches found)
playnplay.chain_reaction / .obj/__compiler_extra.cs
Game game
global using static Sandbox.Internal.GlobalGameNamespace;
global using Microsoft.AspNetCore.Components;
global using Microsoft.AspNetCore.Components.Rendering;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "Chain Reaction" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "chain_reaction" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "playnplay" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "playnplay.chain_reaction" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "28" )]
[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-08-19T10:24:41.4407817Z" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.111.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.111.0")]
playnplay.chain_reaction / Game/2D/MusicManager.cs
Game game
using Sandbox;

public sealed class MusicManager : Component
{
    public static MusicManager Instance { get; private set; }

    SoundHandle _menuMusic;
    SoundHandle _windMusic;
    SoundHandle _gameMusic;

    // Tracked explicitly rather than inferred from SoundHandle.IsValid(), because
    // a handle stopped with a fade is not reliably invalid straight away β€” and
    // guessing wrong here means the menu comes back silent.
    bool _menuPlaying;

    protected override void OnStart()
    {
        Instance = this;

        // Subscribe to game events
        if (ChainReactionGame.Instance != null)
        {
            ChainReactionGame.Instance.OnGameStarted  += OnGameStarted;
            ChainReactionGame.Instance.OnGameReturned += OnGameReturned;
        }

        StartMenuLoops(.25f);
    }

    void StartMenuLoops(float volume)
    {
        _menuMusic   = Sound.Play("phonk_menu_music", volume);
        _windMusic   = Sound.Play("wind_blowing_loop", volume);
        _menuPlaying = true;
    }

    void StopMenuLoops(float fade)
    {
        if (_menuMusic.IsValid()) _menuMusic.Stop(fade);
        if (_windMusic.IsValid()) _windMusic.Stop(fade);
        _menuPlaying = false;
    }

    void OnGameStarted()
    {
        // Reactor mode is meant to be left running for a long time β€” the combat
        // track would grate after ten minutes, so it keeps the calmer menu loop.
        if (ChainReactionGame.Instance?.Mode == GameMode.Idle)
        {
            if (_gameMusic.IsValid()) _gameMusic.Stop(.5f);

            if (!_menuPlaying) StartMenuLoops(.18f);
            else if (_menuMusic.IsValid()) _menuMusic.Volume = .18f;

            return;
        }

        StopMenuLoops(.5f);

        // Only start game music if not already playing
        if (!_gameMusic.IsValid())
            _gameMusic = Sound.Play("abydos_game");
    }

    public void StopMenuMusic() => StopMenuLoops(0f);

    public void PlayMenuMusic() => StartMenuLoops(.25f);

    void OnGameReturned()
    {
        // Reactor mode never starts the combat track, so this handle can still be
        // default-constructed here β€” calling Stop() on that throws.
        if (_gameMusic.IsValid()) _gameMusic.Stop(.5f);

        // Coming back from Reactor mode the menu loop never stopped β€” restore
        // its volume instead of stacking a second copy on top of it.
        if (_menuPlaying)
        {
            if (_menuMusic.IsValid()) _menuMusic.Volume = .3f;
            return;
        }

        StartMenuLoops(.3f);
    }
}
playnplay.chain_reaction / Game/Idle/UI/IdleHud.razor
Game game
@using Sandbox;
@using Sandbox.UI;
@using System;
@using System.Linq;
@using System.Collections.Generic;
@inherits PanelComponent
@namespace Sandbox

@*
	IMPORTANT β€” s&box text rendering quirk.

	Razor compiles a literal containing non-ASCII characters (emoji, Γ—, Β·, β€”)
	into AddMarkupContent, and plain-ASCII literals into AddContent. The panel
	renderer hoists every AddMarkupContent node ahead of every AddContent node,
	so "@A Γ— @B" comes out as "Γ—AB".

	The rule this file follows: an element holds EITHER pure literal text OR a
	single @expression. Anything composed goes through a helper below.
*@

<root>
@if ( Visible )
{
	<div class="idle-root">

		@* ══════════════════════ AMBIENT BACKDROP ══════════════════════ *@
		<div class="idle-bg"></div>
		<div class="idle-bg-glow @GlowClass()"></div>

		@for ( int i = 0; i < 12; i++ )
		{
			float pLeft = (i * 8.3f) % 100f;
			float pDur  = 10f + (i * 0.9f) % 8f;
			float pDel  = (i * 1.4f) % 12f;
			int   pSz   = (i % 4 == 0) ? 4 : 2;
			string pCol = (i % 3 == 0) ? "rgba(94,234,212,0.45)" : "rgba(96,165,250,0.30)";
			<div class="idle-particle"
				 style="left:@pLeft.ToString("F1")%;width:@(pSz)px;height:@(pSz)px;background-color:@pCol;animation-duration:@pDur.ToString("F1")s;animation-delay:@pDel.ToString("F1")s;"></div>
		}

		@* ══════════════════════════ TOP BAR ══════════════════════════ *@
		<div class="idle-top">
			<button class="idle-back" onclick=@OnBack>βŒ‚</button>

			<div class="idle-brand">
				<div class="idle-brand-title">THE REACTOR</div>
				<div class="idle-brand-sub">chain reactions, forever</div>
			</div>

			<div class="idle-currencies">
				<div class="idle-cur cores">
					<span class="idle-cur-icon">βš›οΈ</span>
					<span class="idle-cur-val">@CoresLabel()</span>
					<span class="idle-cur-lbl">Cores</span>
				</div>
				<div class="idle-cur rate">
					<span class="idle-cur-icon">πŸ“ˆ</span>
					<span class="idle-cur-val">@RateLabel()</span>
					<span class="idle-cur-lbl">per sec</span>
				</div>
				<div class="idle-cur flux">
					<span class="idle-cur-icon">πŸ”·</span>
					<span class="idle-cur-val">@FluxLabel()</span>
					<span class="idle-cur-lbl">Flux</span>
				</div>
				<div class="idle-cur frag">
					<span class="idle-cur-icon">πŸ’ </span>
					<span class="idle-cur-val">@FragLabel()</span>
					<span class="idle-cur-lbl">Frags</span>
				</div>
			</div>
		</div>

		@* ═══════════════════════════ BODY ════════════════════════════ *@
		@* Hidden behind a modal β€” the panel renderer draws emoji glyphs above
		   overlay panels, so the board has to actually leave the tree. *@
		@if ( !ModalOpen )
		{
		<div class="idle-body">

			@* ─────────────────────── LEFT COLUMN ─────────────────────── *@
			<div class="idle-col idle-left">

				<div class="idle-card">
					<div class="idle-card-hd">OUTPUT</div>
					<div class="idle-stat-row">
						<span class="idle-stat-lbl">Cores / sec</span>
						<span class="idle-stat-val hot">@RateLabel()</span>
					</div>
					<div class="idle-stat-row">
						<span class="idle-stat-lbl">Global multiplier</span>
						<span class="idle-stat-val">@GlobalMultLabel()</span>
					</div>
					<div class="idle-stat-row">
						<span class="idle-stat-lbl">Chain exponent</span>
						<span class="idle-stat-val">@ExponentLabel()</span>
					</div>
					<div class="idle-stat-row">
						<span class="idle-stat-lbl">Last chain</span>
						<span class="idle-stat-val">@LastChainLabel()</span>
					</div>
					<div class="idle-stat-row">
						<span class="idle-stat-lbl">Best chain</span>
						<span class="idle-stat-val">@BestChainLabel()</span>
					</div>
					<div class="idle-stat-row">
						<span class="idle-stat-lbl">Board</span>
						<span class="idle-stat-val">@BoardLabel()</span>
					</div>
					<div class="idle-stat-row">
						<span class="idle-stat-lbl">Feed line</span>
						<span class="idle-stat-val @FeedClass()">@FeedLabel()</span>
					</div>
				</div>

				@if ( ShowBoosts )
				{
					<div class="idle-card">
						<div class="idle-card-hd">ACTIVE</div>
						@if ( R.SurgeActive )
						{
							<div class="idle-boost surge">
								<span class="idle-boost-icon">⚑</span>
								<span class="idle-boost-lbl">@SurgeBoostLabel()</span>
								<span class="idle-boost-time">@SecondsLabel( R.SurgeTimeLeft )</span>
							</div>
						}
						@foreach ( var boost in R.Boosts )
						{
							var b = boost;
							<div class="idle-boost">
								<span class="idle-boost-icon">@b.Icon</span>
								<span class="idle-boost-lbl">@BoostLabel( b )</span>
								<span class="idle-boost-time">@SecondsLabel( b.SecondsLeft )</span>
							</div>
						}
					</div>
				}

				<div class="idle-card">
					<div class="idle-card-hd">CONDENSER</div>
					<div class="idle-bar-track">
						<div class="idle-bar-fill frag" style="width:@Pct( R.FragmentProgressFraction )%;"></div>
					</div>
					<div class="idle-card-note">@CondenserLabel()</div>
					<div class="idle-stat-row">
						<span class="idle-stat-lbl">Today</span>
						<span class="idle-stat-val @CapClass()">@FragTodayLabel()</span>
					</div>
				</div>

				<div class="idle-card">
					<div class="idle-card-hd">MANIFEST</div>
					@foreach ( var t in IdleBalance.Tiers )
					{
						var tier = t;
						bool got = S.CycleCores >= tier.Requires;
						<div class="idle-tier @(got ? "on" : "")">
							<span class="idle-tier-icon">@tier.Icon</span>
							<div class="idle-tier-body">
								<div class="idle-tier-name">@tier.Name</div>
								<div class="idle-tier-blurb">@TierBlurb( tier, got )</div>
							</div>
						</div>
					}
				</div>

			</div>

			@* ────────────────────── CENTER COLUMN ────────────────────── *@
			<div class="idle-col idle-center">

				<div class="idle-board-wrap">
					<div class="idle-board @SurgeClass() @BoardShake()">
						@for ( int r = 0; r < R.Size; r++ )
						{
							<div class="idle-row">
								@for ( int c = 0; c < R.Size; c++ )
								{
									int row = r;
									int col = c;
									var cell = R.Board[row, col];
									bool golden = R.GoldenActive && R.GoldenRow == row && R.GoldenCol == col;

									<div class="idle-cell @CellClass( cell, golden )"
										 style="width:@(CellPx)px;height:@(CellPx)px;"
										 onclick=@(() => OnCellClick( row, col ))>

										@if ( golden )
										{
											<div class="idle-golden">🌟</div>
											<div class="idle-golden-timer">@GoldenTimerLabel()</div>
										}
										@if ( !golden && cell.HasChest )
										{
											<div class="idle-chest" style="@ChestStyle( cell )">@ChestGlyph( cell )</div>
										}
										@if ( !golden && cell.HasChest && cell.MaxHits > 1 )
										{
											<div class="idle-hits">@cell.HitsLeft</div>
										}
										@if ( cell.HasDrone )
										{
											<div class="idle-drone" style="@DroneStyle( cell )">πŸ’£</div>
										}
									</div>
								}
							</div>
						}
					</div>

					@foreach ( var floater in _floaters )
					{
						var f = floater;
						<div class="idle-float @f.Cls"
							 style="left:@f.X.ToString("F1")%;top:@f.Y.ToString("F1")%;opacity:@f.Alpha.ToString("F2");transform:translateY(@f.Rise.ToString("F0")px) scale(@f.Scale.ToString("F2"));">
							@f.Text
						</div>
					}

					@if ( _calloutLife > 0f )
					{
						<div class="idle-callout @_calloutCls" style="opacity:@_calloutLife.ToString("F2");">@_calloutText</div>
					}
				</div>

				<div class="idle-controls">

					<button class="idle-overload @OverloadClass()" onclick=@OnOverload>
						<div class="idle-overload-fill" style="width:@OverloadFillPct()%;"></div>
						<span class="idle-overload-label">@OverloadLabel()</span>
						<span class="idle-overload-sub">@OverloadSubLabel()</span>
					</button>

					<div class="idle-surge-wrap">
						<div class="idle-surge-track @SurgeReadyClass()">
							<div class="idle-surge-fill" style="width:@Pct( R.Charge )%;"></div>
							<span class="idle-surge-lbl">@SurgeLabel()</span>
						</div>
						@if ( R.SurgeReady )
						{
							<button class="idle-surge-btn" onclick=@OnSurge>@UnleashLabel()</button>
						}
					</div>
				</div>

				<div class="idle-meltdown-strip @MeltdownStripClass()">
					<span class="idle-melt-icon">☒️</span>
					<span class="idle-melt-text">@MeltdownLabel()</span>
					@if ( R.CanMeltdown )
					{
						<button class="idle-melt-btn" onclick=@OnOpenMeltdown>REVIEW</button>
					}
					@if ( !R.CanMeltdown )
					{
						<div class="idle-melt-bar">
							<div class="idle-melt-fill" style="width:@Pct( R.MeltdownProgress )%;"></div>
						</div>
					}
				</div>
			</div>

			@* ─────────────────────── RIGHT COLUMN ────────────────────── *@
			<div class="idle-col idle-right">

				<div class="idle-tabs">
					<button class="idle-tab @TabClass("reactor")" onclick=@(() => SetTab("reactor"))>βš™οΈ</button>
					<button class="idle-tab @TabClass("chains")"  onclick=@(() => SetTab("chains"))>⛓️</button>
					<button class="idle-tab @TabClass("systems")" onclick=@(() => SetTab("systems"))>πŸ› οΈ</button>
					<button class="idle-tab fluxtab @TabClass("flux")" onclick=@(() => SetTab("flux"))>πŸ”·</button>
					<button class="idle-tab @TabClass("log")" onclick=@(() => SetTab("log"))>πŸ“œ</button>
				</div>

				@if ( IsUpgradeTab )
				{
					<div class="idle-buyrow">
						<span class="idle-buyrow-lbl">BUY</span>
						@foreach ( var amount in BuyAmounts )
						{
							int amt = amount;
							<button class="idle-buy-amt @BuyAmtClass( amt )" onclick=@(() => SetBuyAmount( amt ))>@BuyAmountLabel( amt )</button>
						}
					</div>
				}

				<div class="idle-panel">

					@if ( IsUpgradeTab )
					{
						@foreach ( var upgrade in IdleUpgradeCatalog.InCategory( CurrentCategory ) )
						{
							var up = upgrade;
							bool revealed = R.IsRevealed( up );
							int  lvl      = S.Level( up.Id );
							bool maxed    = lvl >= up.MaxLevel;
							int  count    = revealed ? BuyCount( up.Id ) : 0;
							double cost   = (!revealed || maxed) ? 0 : R.CostFor( up.Id, count );
							bool afford   = revealed && !maxed && count > 0 && Cores >= cost;

							<div class="idle-up @UpgradeClass( revealed, maxed, afford )" onclick=@(() => OnBuy( up.Id ))>
								<div class="idle-up-icon">@UpgradeIcon( up, revealed )</div>
								<div class="idle-up-body">
									<div class="idle-up-top">
										<span class="idle-up-name">@UpgradeName( up, revealed )</span>
										<span class="idle-up-lvl">@LevelLabel( up, lvl, revealed )</span>
									</div>
									<div class="idle-up-desc">@UpgradeDesc( up, revealed )</div>
									@if ( revealed )
									{
										<div class="idle-up-bottom">
											<span class="idle-up-effect">@up.Effect( lvl )</span>
											<span class="idle-up-cost @MaxedClass( maxed )">@CostLabel( maxed, cost, count )</span>
										</div>
									}
								</div>
							</div>
						}
					}

					@if ( _tab == "flux" )
					{
						<div class="idle-flux-head">
							<div class="idle-flux-amount">@FluxHeadLabel()</div>
							<div class="idle-flux-sub">@FluxSubLabel()</div>
						</div>

						@foreach ( var perk in IdleUpgradeCatalog.Perks )
						{
							var p      = perk;
							int lvl    = S.PerkLevel( p.Id );
							bool maxed = lvl >= p.MaxLevel;
							int cost   = p.CostAt( lvl );
							bool afford = !maxed && S.Flux >= cost;

							<div class="idle-up perk @UpgradeClass( true, maxed, afford )" onclick=@(() => OnBuyPerk( p.Id ))>
								<div class="idle-up-icon">@p.Icon</div>
								<div class="idle-up-body">
									<div class="idle-up-top">
										<span class="idle-up-name">@p.Name</span>
										<span class="idle-up-lvl">@PerkLevelLabel( p, lvl )</span>
									</div>
									<div class="idle-up-desc">@p.Desc</div>
									<div class="idle-up-bottom">
										<span class="idle-up-effect">@p.Effect( lvl )</span>
										<span class="idle-up-cost flux @MaxedClass( maxed )">@PerkCostLabel( maxed, cost )</span>
									</div>
								</div>
							</div>
						}
					}

					@if ( _tab == "log" )
					{
						<div class="idle-flux-head">
							<div class="idle-flux-amount">@LogHeadLabel()</div>
							<div class="idle-flux-sub">Reactor Log β€” every entry pays out once.</div>
						</div>

						@foreach ( var achievement in IdleAchievements.All )
						{
							var a = achievement;
							bool done = S.HasAchievement( a.Id );

							<div class="idle-up ach @(done ? "done" : "")">
								<div class="idle-up-icon">@AchIcon( a, done )</div>
								<div class="idle-up-body">
									<div class="idle-up-top">
										<span class="idle-up-name">@a.Name</span>
										<span class="idle-ach-reward">@RewardLabel( a )</span>
									</div>
									<div class="idle-up-desc">@a.Desc</div>
									@if ( !done )
									{
										<div class="idle-ach-bar">
											<div class="idle-ach-fill" style="width:@Pct( a.Fraction( S ) )%;"></div>
										</div>
										<div class="idle-ach-prog">@ProgressLabel( a )</div>
									}
								</div>
							</div>
						}
					}
				</div>
			</div>
		</div>
		}

		@* ═════════════════════════ TOASTS ═════════════════════════ *@
		<div class="idle-toasts">
			@foreach ( var toast in _toasts )
			{
				var t = toast;
				<div class="idle-toast @t.Cls" style="opacity:@t.Alpha.ToString("F2");">
					<span class="idle-toast-icon">@t.Icon</span>
					<span class="idle-toast-text">@t.Text</span>
				</div>
			}
		</div>

		@* ═══════════════════ WELCOME BACK OVERLAY ═══════════════════ *@
		@if ( R.OfflinePending )
		{
			<div class="idle-modal-bg"></div>
			<div class="idle-modal offline">
				<div class="idle-modal-eyebrow">REACTOR REPORT</div>
				<div class="idle-modal-title">Welcome back</div>
				<div class="idle-modal-sub">@OfflineSubLabel()</div>

				<div class="idle-offline-haul">
					<div class="idle-offline-amount">@OfflineAmountLabel()</div>
					<div class="idle-offline-lbl">CORES</div>
				</div>

				@if ( R.PendingOfflineFrags > 0 )
				{
					<div class="idle-offline-extra">@OfflineFragLabel()</div>
				}

				@if ( OfflineTankFull )
				{
					<div class="idle-offline-warn">@OfflineWarnLabel()</div>
				}

				<button class="idle-modal-btn" onclick=@OnClaimOffline>COLLECT</button>
			</div>
		}

		@* ═══════════════════ DAILY STREAK OVERLAY ═══════════════════ *@
		@if ( !R.OfflinePending && !_showIntro && R.DailyPending )
		{
			<div class="idle-modal-bg"></div>
			<div class="idle-modal daily">
				<div class="idle-modal-eyebrow">DAILY CYCLE</div>
				<div class="idle-modal-title">@DailyTitleLabel()</div>
				<div class="idle-modal-sub">Come back tomorrow to keep the streak climbing. Day 7 pays eight times.</div>

				<div class="idle-streak-row">
					@for ( int d = 1; d <= 7; d++ )
					{
						int day = d;
						<div class="idle-streak-pip @StreakPipClass( day )">@day</div>
					}
				</div>

				<div class="idle-daily-reward">
					<div class="idle-daily-mult">@DailyMultLabel()</div>
					<div class="idle-daily-lbl">@DailyRewardLabel()</div>
				</div>

				<button class="idle-modal-btn" onclick=@OnClaimDaily>CLAIM</button>
			</div>
		}

		@* ══════════════════════ MELTDOWN MODAL ══════════════════════ *@
		@if ( !R.OfflinePending && !_showIntro && !R.DailyPending && _showMeltdown )
		{
			<div class="idle-modal-bg"></div>
			<div class="idle-modal meltdown">
				<div class="idle-modal-eyebrow">PRESTIGE</div>
				<div class="idle-modal-title">MELTDOWN</div>
				<div class="idle-modal-sub">Collapse this cycle into permanent Flux. Every Flux you have ever earned adds income forever, and Flux buys perks that survive every reset.</div>

				<div class="idle-melt-payout">
					<div class="idle-melt-payout-val">@MeltPayoutLabel()</div>
					<div class="idle-melt-payout-lbl">FLUX</div>
				</div>

				<div class="idle-melt-cols">
					<div class="idle-melt-col keep">
						<div class="idle-melt-col-hd">KEPT</div>
						<div class="idle-melt-line">Flux and every perk</div>
						<div class="idle-melt-line">Reactor Log entries</div>
						<div class="idle-melt-line">Records and Fragments</div>
						<div class="idle-melt-line">Kickstart head start</div>
					</div>
					<div class="idle-melt-col lose">
						<div class="idle-melt-col-hd">BURNED</div>
						<div class="idle-melt-line">@BurnedCoresLabel()</div>
						<div class="idle-melt-line">All Core upgrades</div>
						<div class="idle-melt-line">Reactor Frame size</div>
						<div class="idle-melt-line">Unlocked chest tiers</div>
					</div>
				</div>

				<div class="idle-modal-btns">
					<button class="idle-modal-btn ghost" onclick=@OnCloseMeltdown>Not yet</button>
					<button class="idle-modal-btn danger" onclick=@OnMeltdown>MELT DOWN</button>
				</div>
			</div>
		}

		@* ════════════════════════ INTRO MODAL ═══════════════════════ *@
		@if ( !R.OfflinePending && _showIntro )
		{
			<div class="idle-modal-bg"></div>
			<div class="idle-modal intro">
				<div class="idle-modal-eyebrow">HOW IT WORKS</div>
				<div class="idle-modal-title">THE REACTOR</div>
				<div class="idle-modal-sub">A board that plays itself. Chests drop, drones bomb them, chain reactions pay Cores. You never lose here β€” you only grow.</div>

				<div class="idle-intro-steps">
					<div class="idle-intro-step">
						<span class="idle-intro-num">1</span>
						<span class="idle-intro-txt">Spend Cores on the right. Longer chains are worth far more than richer chests.</span>
					</div>
					<div class="idle-intro-step">
						<span class="idle-intro-num">2</span>
						<span class="idle-intro-txt">Hit OVERLOAD, or press SPACE, for a burst. It charges the Surge meter β€” ten times output when you unleash it.</span>
					</div>
					<div class="idle-intro-step">
						<span class="idle-intro-num">3</span>
						<span class="idle-intro-txt">It keeps running while you are on the menu or another screen. Close the game and it stops.</span>
					</div>
					<div class="idle-intro-step">
						<span class="idle-intro-num">4</span>
						<span class="idle-intro-txt">Cores condense into Fragments, which feed your companion for a permanent output bonus. Meltdown turns a whole cycle into Flux.</span>
					</div>
				</div>

				<button class="idle-modal-btn" onclick=@OnCloseIntro>START THE REACTOR</button>
			</div>
		}
	</div>
}
</root>

@code {
	public static IdleHud Instance { get; private set; }

	IdleReactor       R    => IdleReactor.Instance;
	ChainReactionGame Game => ChainReactionGame.Instance;
	IdleSave          S    => ChainReactionGame.Save?.Idle;

	bool Visible => Game?.GameStarted == true
				 && Game?.Mode == GameMode.Idle
				 && R != null && S != null;

	double Cores => S?.Cores ?? 0;

	bool ModalOpen  => R.OfflinePending || R.DailyPending || _showMeltdown || _showIntro;
	bool ShowBoosts => R.Boosts.Count > 0 || R.SurgeActive;
	bool OfflineTankFull => R.PendingOfflineSeconds > R.OfflineCapHours * 3600f;

	// ── UI state ──────────────────────────────────────────────────
	string _tab       = "reactor";
	int    _buyAmount = 1;                      // -1 == MAX
	bool   _showMeltdown;
	bool   _showIntro;
	bool   _subscribed;
	bool   _wasVisible;

	static readonly int[] BuyAmounts = { 1, 10, 25, -1 };

	bool IsUpgradeTab => _tab == "reactor" || _tab == "chains" || _tab == "systems";

	IdleUpgradeCategory CurrentCategory => _tab switch
	{
		"reactor" => IdleUpgradeCategory.Reactor,
		"chains"  => IdleUpgradeCategory.Chains,
		_         => IdleUpgradeCategory.Systems,
	};

	// ── Floating numbers ──────────────────────────────────────────
	class Floater
	{
		public string Text;
		public string Cls;
		public float X, Y;
		public float Life, MaxLife;
		public float Alpha => MathF.Min( 1f, Life / 0.35f );
		public float Rise  => -(MaxLife - Life) * 42f;
		public float Scale => 0.85f + MathF.Min( 0.35f, (MaxLife - Life) * 1.2f );
	}
	readonly List<Floater> _floaters = new();

	class ToastMsg
	{
		public string Icon;
		public string Text;
		public string Cls;
		public float Life;
		public float Alpha => MathF.Min( 1f, Life / 0.4f );
	}
	readonly List<ToastMsg> _toasts = new();

	string _calloutText = "";
	string _calloutCls  = "";
	float  _calloutLife;

	// Board shake is kept local rather than going through ScreenShaker so the
	// class string stays stable between rebuilds β€” a changing class would
	// restart the animation on every repaint and read as a vibration.
	string _shakeCls = "";
	float  _shakeUntil;

	int CellPx => R == null ? 92 : R.Size switch
	{
		5 => 96,
		6 => 84,
		7 => 74,
		8 => 66,
		_ => 59,
	};

	// ══════════════════════════════════════════════════════════════
	// Lifecycle
	// ══════════════════════════════════════════════════════════════

	protected override void OnStart()
	{
		Instance = this;
	}

	protected override void OnUpdate()
	{
		if ( !_subscribed && R != null ) Subscribe();

		bool vis = Visible;
		if ( vis != _wasVisible )
		{
			_wasVisible = vis;
			if ( vis )
			{
				_floaters.Clear();
				_toasts.Clear();
				_calloutLife  = 0f;
				_shakeUntil   = 0f;
				_showMeltdown = false;
				_showIntro    = S != null && !S.IntroShown;
				_tab          = "reactor";
			}
			StateHasChanged();
		}

		if ( !vis ) return;

		// SPACE is the detonate key everywhere else in the game, so it fires the
		// reactor's manual burst here too.
		if ( Input.Pressed( "Jump" ) && R.OverloadReady && !ModalOpen )
			R.Overload();

		float dt = Time.Delta;

		for ( int i = _floaters.Count - 1; i >= 0; i-- )
		{
			_floaters[i].Life -= dt;
			if ( _floaters[i].Life <= 0f ) _floaters.RemoveAt( i );
		}

		for ( int i = _toasts.Count - 1; i >= 0; i-- )
		{
			_toasts[i].Life -= dt;
			if ( _toasts[i].Life <= 0f ) _toasts.RemoveAt( i );
		}

		if ( _calloutLife > 0f ) _calloutLife -= dt * 1.4f;
	}

	void Subscribe()
	{
		var r = R;
		if ( r == null || _subscribed ) return;

		r.OnPop        += OnPop;
		r.OnToast      += OnToastEvent;
		r.OnDetonation += OnDetonation;

		_subscribed = true;
	}

	/// <summary>
	/// Rebuilds only when something on screen would actually differ.
	///
	/// The first version forced a repaint 15 times a second, which meant
	/// rebuilding the board, both side columns and every upgrade row constantly
	/// β€” that was the source of the stutter. Between detonations almost nothing
	/// changes, so hashing real state instead of a clock cuts the repaint rate
	/// by roughly an order of magnitude.
	/// </summary>
	protected override int BuildHash()
	{
		if ( !Visible ) return 0;

		var hc = new HashCode();
		hc.Add( _tab );
		hc.Add( _buyAmount );
		hc.Add( _showMeltdown );
		hc.Add( _showIntro );
		hc.Add( ModalOpen );

		// Currencies β€” quantised to what the labels actually show
		hc.Add( (long)Cores );
		hc.Add( (long)R.Cps );
		hc.Add( S.Flux );
		hc.Add( ChainReactionGame.Save?.TotalFragments ?? 0 );
		hc.Add( S.TodayReactorFragments );
		hc.Add( (int)(R.FragmentProgressFraction * 60f) );

		// Live controls
		hc.Add( (int)(R.OverloadCooldownLeft * 10f) );
		hc.Add( (int)(R.Charge * 100f) );
		hc.Add( R.SurgeActive );
		hc.Add( (int)R.SurgeTimeLeft );
		hc.Add( (int)R.GoldenTimeLeft );
		hc.Add( R.Boosts.Count );
		hc.Add( R.Boosts.Count > 0 ? (int)R.Boosts[0].SecondsLeft : 0 );
		hc.Add( R.FeedStarved );

		// Transient effects
		hc.Add( _floaters.Count );
		hc.Add( (int)(_calloutLife * 20f) );
		hc.Add( Time.Now < _shakeUntil ? _shakeCls : "" );
		hc.Add( _toasts.Count );

		// Board β€” chest layout, damage, drones and any live flash
		hc.Add( R.Size );
		float now = Time.Now;
		bool animating = _floaters.Count > 0 || _calloutLife > 0f || now < _shakeUntil;

		for ( int r = 0; r < R.Size; r++ )
			for ( int c = 0; c < R.Size; c++ )
			{
				var cell = R.Board[r, c];
				hc.Add( (int)cell.Chest );
				hc.Add( cell.HitsLeft );
				hc.Add( cell.HasDrone );
				hc.Add( cell.FlashUntil > now ? cell.FlashKind : 0 );

				if ( cell.FlashUntil > now ) animating = true;
				if ( cell.HasChest && now - cell.SpawnAt < 0.25f ) animating = true;
				if ( cell.HasDrone && now - cell.DroneAt < 0.18f ) animating = true;
			}

		hc.Add( R.GoldenRow );
		hc.Add( R.GoldenCol );
		hc.Add( R.LastChain );

		// Anything mid-flight is drawn from inline styles rather than keyframes,
		// so it needs a clock β€” but only while it is actually moving. The rest of
		// the time this hash is stable and the panel is never rebuilt at all.
		if ( animating ) hc.Add( (int)(now * 20f) );

		return hc.ToHashCode();
	}

	// ══════════════════════════════════════════════════════════════
	// Reactor events
	// ══════════════════════════════════════════════════════════════

	void OnPop( int row, int col, double value, bool crit )
	{
		if ( R == null || _floaters.Count > 10 ) return;

		float step = 100f / R.Size;
		_floaters.Add( new Floater {
			Text = "+" + IdleFormat.Short( value ),
			Cls  = crit ? "crit" : "",
			X = step * col + step * 0.5f,
			Y = step * row + step * 0.5f,
			Life = 0.9f,
			MaxLife = 0.9f,
		} );
	}

	void OnToastEvent( string icon, string text, string cls )
	{
		_toasts.Insert( 0, new ToastMsg { Icon = icon, Text = text, Cls = cls, Life = 3.2f } );
		while ( _toasts.Count > 4 ) _toasts.RemoveAt( _toasts.Count - 1 );
	}

	void OnDetonation( int chain, double payout, bool crit )
	{
		string shake = crit || chain >= 25 ? "idle-shake-3"
					 : chain >= 12 ? "idle-shake-2"
					 : chain >= 4  ? "idle-shake-1"
					 : "";

		if ( shake != "" )
		{
			_shakeCls   = shake;
			_shakeUntil = Time.Now + (shake == "idle-shake-3" ? 0.45f : 0.28f);
		}

		if ( chain < 15 && !crit ) return;

		double critPower = IdleBalance.CritPower( S?.Level( IdleUpgradeId.CritPower ) ?? 0 );

		_calloutText = crit
			? $"CRIT Γ—{critPower:F0}  Β·  +{IdleFormat.Short( payout )}"
			: $"{chain} CHAIN  Β·  +{IdleFormat.Short( payout )}";

		_calloutCls  = crit ? "crit" : chain >= 35 ? "mega" : "big";
		_calloutLife = 1f;
	}

	// ══════════════════════════════════════════════════════════════
	// Input
	// ══════════════════════════════════════════════════════════════

	void SetTab( string tab )       { _tab = tab; Sound.Play( "UI_Button" ); }
	void SetBuyAmount( int amount ) { _buyAmount = amount; Sound.Play( "UI_Button" ); }

	int BuyCount( IdleUpgradeId id )
		=> _buyAmount < 0 ? Math.Max( 1, R.MaxAffordable( id ) ) : _buyAmount;

	void OnBuy( IdleUpgradeId id )
	{
		if ( R == null ) return;
		if ( !R.TryBuy( id, BuyCount( id ) ) ) Sound.Play( "Pop_Button_1" );
	}

	void OnBuyPerk( IdlePerkId id )
	{
		if ( R == null ) return;
		if ( !R.TryBuyPerk( id ) ) Sound.Play( "Pop_Button_1" );
	}

	void OnOverload()  => R?.Overload();
	void OnSurge()     => R?.ActivateSurge();
	void OnCellClick( int r, int c ) => R?.ClickCell( r, c );

	void OnClaimOffline() => R?.ClaimOffline();
	void OnClaimDaily()   => R?.ClaimDaily();

	void OnOpenMeltdown()  { _showMeltdown = true;  Sound.Play( "UI_Button" ); }
	void OnCloseMeltdown() { _showMeltdown = false; Sound.Play( "UI_Button" ); }

	void OnMeltdown()
	{
		_showMeltdown = false;
		R?.DoMeltdown();
	}

	void OnCloseIntro()
	{
		_showIntro = false;
		if ( S != null )
		{
			S.IntroShown = true;
			ChainReactionGame.Save?.Save();
		}
		Sound.Play( "PlayButton" );
	}

	void OnBack()
	{
		Sound.Play( "UI_Button" );
		Game?.ReturnToMenu();
	}

	// ══════════════════════════════════════════════════════════════
	// Class helpers
	// ══════════════════════════════════════════════════════════════

	string Pct( float f ) => (Math.Clamp( f, 0f, 1f ) * 100f).ToString( "F1" );

	string GlowClass()        => R.SurgeActive ? "surging" : "";
	string SurgeClass()       => R.SurgeActive ? "surging" : "";
	string SurgeReadyClass()  => R.SurgeReady ? "ready" : "";
	string OverloadClass()    => R.OverloadReady ? "ready" : "cooling";
	string BoardShake()       => Time.Now < _shakeUntil ? _shakeCls : "";
	string TabClass( string t )     => _tab == t ? "on" : "";
	string BuyAmtClass( int amt )   => _buyAmount == amt ? "on" : "";
	string MaxedClass( bool maxed ) => maxed ? "maxed" : "";
	string CapClass()  => R.FragmentsToday >= R.DailyFragmentCap ? "capped" : "";
	string FeedClass() => R.FeedStarved ? "starved" : "";
	string MeltdownStripClass() => R.CanMeltdown ? "ready" : "";

	string StreakPipClass( int day )
	{
		int streak = S?.DailyStreak ?? 1;
		if ( day == streak ) return "on today";
		return day < streak ? "on" : "";
	}

	// ══════════════════════════════════════════════════════════════
	// Text helpers β€” one composed string per element (see note at top)
	// ══════════════════════════════════════════════════════════════

	string CoresLabel() => IdleFormat.Short( Cores );
	string RateLabel()  => IdleFormat.Short( R.Cps );
	string FluxLabel()  => (S?.Flux ?? 0).ToString();
	string FragLabel()  => (ChainReactionGame.Save?.TotalFragments ?? 0).ToString();

	string GlobalMultLabel() => "Γ—" + IdleFormat.Short( R.GlobalMultiplier );
	string ExponentLabel()   => R.ChainExponent.ToString( "F2" );
	string LastChainLabel()  => $"{R.LastChain} chests";
	string BestChainLabel()  => $"{S.BestChain} chests";
	string BoardLabel()      => $"{R.Size} Γ— {R.Size}";

	string FeedLabel() => R.FeedStarved ? "starving" : "keeping up";

	string SurgeBoostLabel() => $"SURGE Γ—{IdleBalance.SurgeMultiplier:F0}";
	string BoostLabel( IdleReactor.Boost b ) => $"{b.Label} Γ—{b.Mult:F1}";
	string SecondsLabel( float s ) => $"{s:F0}s";

	string CondenserLabel()
		=> $"next at {IdleFormat.Short( R.FragmentCost )} Cores Β· price climbs with each one";

	string FragTodayLabel() => $"{R.FragmentsToday} / {R.DailyFragmentCap}";

	string TierBlurb( IdleBalance.ChestTier tier, bool unlocked )
		=> unlocked ? tier.Blurb : $"unlocks at {IdleFormat.Short( tier.Requires )} cycle Cores";

	string GoldenTimerLabel() => R.GoldenTimeLeft.ToString( "F0" );

	string OverloadLabel()
		=> R.OverloadReady ? "⚑ OVERLOAD" : $"{R.OverloadCooldownLeft:F1}s";

	string OverloadSubLabel() => $"Γ—{IdleBalance.OverloadMultiplier:F1} burst Β· SPACE";
	string OverloadFillPct()  => ((1f - R.OverloadCooldownFraction) * 100f).ToString( "F0" );
	string UnleashLabel()     => $"UNLEASH Γ—{IdleBalance.SurgeMultiplier:F0}";

	string SurgeLabel()
	{
		if ( R.SurgeActive ) return $"SURGE {R.SurgeTimeLeft:F0}s";
		if ( R.SurgeReady )  return "SURGE READY";
		return $"CHARGE {R.Charge * 100f:F0}%";
	}

	string MeltdownLabel()
		=> R.CanMeltdown
			? $"Meltdown available β€” +{R.PendingFlux} Flux"
			: $"Meltdown unlocks at {IdleFormat.Short( IdleBalance.MeltdownRequirement )} cycle Cores Β· {IdleFormat.Short( S.CycleCores )} banked";

	string BuyAmountLabel( int amount ) => amount < 0 ? "MAX" : $"Γ—{amount}";

	string UpgradeClass( bool revealed, bool maxed, bool afford )
	{
		if ( !revealed ) return "locked";
		if ( maxed ) return "maxed";
		return afford ? "afford" : "poor";
	}

	string UpgradeIcon( IdleUpgrade up, bool revealed ) => revealed ? up.Icon : "πŸ”’";
	string UpgradeName( IdleUpgrade up, bool revealed ) => revealed ? up.Name : "Locked";

	string UpgradeDesc( IdleUpgrade up, bool revealed )
		=> revealed ? up.Desc : $"Reach {IdleFormat.Short( up.RevealAt )} lifetime Cores to reveal.";

	string LevelLabel( IdleUpgrade up, int lvl, bool revealed )
	{
		if ( !revealed ) return "β€”";
		return up.MaxLevel < 100 ? $"Lv {lvl} / {up.MaxLevel}" : $"Lv {lvl}";
	}

	string CostLabel( bool maxed, double cost, int count )
	{
		if ( maxed ) return "MAXED";
		return count > 1
			? $"{IdleFormat.Short( cost )}  Γ—{count}"
			: IdleFormat.Short( cost );
	}

	string PerkLevelLabel( IdlePerk p, int lvl ) => $"Lv {lvl} / {p.MaxLevel}";
	string PerkCostLabel( bool maxed, int cost ) => maxed ? "MAXED" : $"πŸ”· {cost}";

	string FluxHeadLabel() => $"πŸ”· {S.Flux}";

	string FluxSubLabel()
		=> $"{S.FluxEarned} earned Β· Γ—{IdleBalance.FluxMultiplier( S.FluxEarned ):F2} permanent income";

	string LogHeadLabel() => $"{IdleAchievements.CompletedCount( S )} / {IdleAchievements.All.Count}";

	string AchIcon( IdleAchievement a, bool done ) => done ? a.Icon : "πŸ”˜";

	string RewardLabel( IdleAchievement a )
	{
		if ( a.RewardFlux > 0 && a.RewardFrags > 0 ) return $"+{a.RewardFlux}πŸ”·  +{a.RewardFrags}πŸ’ ";
		if ( a.RewardFlux > 0 ) return $"+{a.RewardFlux}πŸ”·";
		return $"+{a.RewardFrags}πŸ’ ";
	}

	string ProgressLabel( IdleAchievement a )
	{
		double cur = a.Progress( S );
		return a.BigNumbers
			? $"{IdleFormat.Short( cur )} / {IdleFormat.Short( a.Target )}"
			: $"{(long)cur} / {(long)a.Target}";
	}

	// ── Modal labels ──────────────────────────────────────────────
	string OfflineSubLabel()
		=> $"The reactor ran for {IdleFormat.Duration( R.PendingOfflineSeconds )} at {IdleFormat.Pct( R.OfflineEfficiency )} efficiency.";

	string OfflineAmountLabel() => "+" + IdleFormat.Short( R.PendingOfflineCores );

	string OfflineFragLabel() => $"condensing {R.PendingOfflineFrags} Fragments";

	string OfflineWarnLabel()
		=> $"Storage filled up after {R.OfflineCapHours:F0}h β€” upgrade Core Storage to bank longer trips.";

	string DailyTitleLabel() => $"Day {S?.DailyStreak ?? 1}";
	string DailyMultLabel()  => $"Γ—{IdleBalance.DailyReward( S?.DailyStreak ?? 1 ).mult:F1}";

	string DailyRewardLabel()
	{
		var reward = IdleBalance.DailyReward( S?.DailyStreak ?? 1 );
		return $"for {IdleFormat.Duration( reward.seconds )}  Β·  +{reward.frags} Fragments";
	}

	string MeltPayoutLabel()  => "+" + R.PendingFlux;
	string BurnedCoresLabel() => $"{IdleFormat.Short( Cores )} Cores";

	// ══════════════════════════════════════════════════════════════
	// Cell rendering
	// ══════════════════════════════════════════════════════════════

	string CellClass( IdleReactor.Cell cell, bool golden )
	{
		if ( golden ) return "golden";

		string cls = cell.HasChest ? "filled" : "empty";

		if ( cell.FlashUntil > Time.Now )
		{
			cls += cell.FlashKind switch
			{
				3 => " fx-drone",
				2 => " fx-kill",
				_ => " fx-blast",
			};
		}

		if ( cell.HasDrone ) cls += " armed";

		if ( cell.HasChest )
		{
			cls += cell.Chest switch
			{
				GridManager.ChestType.Gem       => " c-gem",
				GridManager.ChestType.BombChest => " c-bomb",
				GridManager.ChestType.Locked    => " c-locked",
				GridManager.ChestType.Cursed    => " c-cursed",
				GridManager.ChestType.Void      => " c-void",
				GridManager.ChestType.Boss      => " c-boss",
				_                               => " c-normal",
			};
		}

		return cls;
	}

	/// <summary>
	/// Spawn pop, computed here rather than as a keyframe: the panel can rebuild
	/// at any moment and a class-driven animation would restart mid-flight.
	/// </summary>
	string ChestStyle( IdleReactor.Cell cell )
	{
		float age = Time.Now - cell.SpawnAt;
		if ( age >= 0.25f || age < 0f ) return "";

		float scale = 0.4f + 0.6f * (age / 0.25f);
		return $"transform:scale({scale.ToString( "F2" )});";
	}

	/// <summary>Drone drop β€” falls onto the cell as the volley arms.</summary>
	string DroneStyle( IdleReactor.Cell cell )
	{
		float age = Time.Now - cell.DroneAt;
		if ( age >= 0.18f || age < 0f ) return "";

		float t = age / 0.18f;
		float scale = 2.0f - 1.0f * t;
		return $"transform:scale({scale.ToString( "F2" )});opacity:{t.ToString( "F2" )};";
	}

	string ChestGlyph( IdleReactor.Cell cell ) => cell.Chest switch
	{
		GridManager.ChestType.BombChest => "πŸ’£",
		GridManager.ChestType.Locked    => "πŸ”’",
		GridManager.ChestType.Cursed    => "πŸ’€",
		GridManager.ChestType.Void      => "πŸŒ‘",
		GridManager.ChestType.Boss      => "πŸ‘‘",
		GridManager.ChestType.Gem       => "πŸ’Ž",
		_                               => "🎁",
	};
}
playnplay.chain_reaction / styles/form/_switch.scss
Game game
$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%;
	}
}
playnplay.chain_reaction / ui/controls/vectorcontrol.cs.scss
Game game
VectorControl
{
	gap: 2px;
	flex-grow: 1;
}

VectorControl NumberEntry
{
	flex-basis: 50%;
}
playnplay.chain_reaction / ui/controls/enumcontrol.cs.scss
Game game
EnumControl
{
	gap: 2px;
	flex-grow: 1;
}

EnumControl DropDown,
EnumControl ButtonGroup
{
	border-radius: 8px;
	background-color: #000a;
	flex-grow: 1;
}

EnumControl DropDown
{
	flex-grow: 1;
	min-height: 32px;
}

EnumControl ButtonGroup
{
	border-radius: 12px;
	overflow: hidden;
	min-height: 32px;

	Button
	{
		flex-grow: 1;
		justify-content: center;
		align-items: center;
		gap: 4px;
		color: #aaa;
		font-size: 1rem;
		cursor: pointer;

		.icon
		{
			color: #08f;
		}

		&:hover
		{
			color: #ddd;

			.icon
			{
				color: #3af;
			}
		}

		&:active
		{
			background-color: #04a;
			color: white;
			transform: translateX( 1px ) translateY( 1px );

			.icon
			{
				color: #fff;
			}
		}

		&.active
		{
			background-color: #08f;
			color: white;
			pointer-events: none;

			.icon
			{
				color: #fff;
			}
		}
	}
}
playnplay.chain_reaction / ui/controls/color/colorpickercontrol.cs.scss
Game game
ColorPickerControl
{
	flex-direction: column;
	flex-shrink: 0;
	gap: 0.5rem;
	margin: 1rem;
}
playnplay.chain_reaction / game/idle/ui/idlehud.razor.scss
Game game
/* ══════════════════════════════════════════════════════════════════════════
   THE REACTOR β€” Idle mode HUD
   Palette leans teal/amber against the same deep purple-black the rest of
   10XPLOSION uses, so it reads as "the same game, different room".
   ══════════════════════════════════════════════════════════════════════════ */

root {
    /* Declared so no panel in this tree ever claims the OS pointer -- a hovered
       panel outranks Mouse.CursorType, and one that declares nothing still claims
       the default arrow. GameCursor draws the cursor instead. */
    cursor: none;
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    pointer-events: none;
    overflow: hidden;
    /* Sits above the wave HUD's layers β€” those hide themselves in this mode,
       but ScreenShaker and PopupLayer share the same ScreenPanel. */
    z-index: 200;
}

.idle-root {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    display: flex;
    flex-direction: column;
    pointer-events: none;
}

/* ── Backdrop ─────────────────────────────────────────────────────────── */

.idle-bg {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    background-color: #04070d;
}

.idle-bg-glow {
    position: absolute;
    top: -20%; left: 15%;
    width: 70%;
    height: 120%;
    border-radius: 50%;
    background-color: rgba(20, 130, 130, 0.10);
    filter: blur(120px);
    animation: idle-breathe 9s ease-in-out infinite alternate;
    transition: background-color 0.5s;
}

.idle-bg-glow.surging {
    background-color: rgba(251, 146, 60, 0.22);
}

@keyframes idle-breathe {
    0%   { transform: scale(1.00); opacity: 0.75; }
    100% { transform: scale(1.18); opacity: 1.00; }
}

.idle-particle {
    position: absolute;
    bottom: -12px;
    border-radius: 50%;
    animation: idle-rise linear infinite;
}

@keyframes idle-rise {
    0%   { transform: translateY(0px);      opacity: 0; }
    12%  {                                  opacity: 1; }
    100% { transform: translateY(-1180px);  opacity: 0; }
}

/* ── Top bar ──────────────────────────────────────────────────────────── */

.idle-top {
    position: relative;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 16px;
    padding: 12px 22px;
    flex-shrink: 0;
    background-color: rgba(6, 12, 20, 0.82);
    border-bottom: 2px solid rgba(45, 212, 191, 0.18);
    pointer-events: all;
    overflow: hidden;
}

.idle-back {
    width: 46px;
    height: 46px;
    flex-shrink: 0;
    border-radius: 10px;
    border: 2px solid rgba(148, 163, 184, 0.28);
    background-color: rgba(15, 23, 35, 0.9);
    color: rgba(226, 232, 240, 0.85);
    font-size: 22px;
    font-weight: 900;
    align-items: center;
    justify-content: center;
    cursor: none;
    pointer-events: all;
    transition: background-color 0.1s, border-color 0.1s;
}
.idle-back:hover {
    background-color: rgba(45, 212, 191, 0.18);
    border-color: rgba(45, 212, 191, 0.6);
}

/* Without an explicit no-shrink the currency row squeezes this to nothing and
   the title wraps one letter per line. */
.idle-brand {
    display: flex;
    flex-direction: column;
    flex-shrink: 0;
    flex-grow: 0;
}

.idle-brand-title {
    white-space: nowrap;
    font-size: 22px;
    font-weight: 900;
    letter-spacing: 0.24em;
    color: #5eead4;
    text-shadow: 0px 0px 18px rgba(45, 212, 191, 0.45);
}

.idle-brand-sub {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: rgba(148, 163, 184, 0.55);
}

.idle-currencies {
    display: flex;
    flex-direction: row;
    gap: 8px;
    margin-left: auto;
    flex-shrink: 0;
    flex-grow: 0;
}

.idle-cur {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 7px;
    padding: 7px 13px;
    border-radius: 10px;
    background-color: rgba(12, 20, 30, 0.92);
    border: 2px solid rgba(100, 116, 139, 0.22);
    width: 172px;
    flex-shrink: 0;
    flex-grow: 0;
    overflow: hidden;
}

.idle-cur-icon { font-size: 17px; flex-shrink: 0; }

.idle-cur-val {
    font-size: 19px;
    font-weight: 900;
    color: #e2e8f0;
    white-space: nowrap;
    flex-shrink: 0;
}

.idle-cur-lbl {
    font-size: 9px;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: rgba(148, 163, 184, 0.55);
    margin-left: auto;
    white-space: nowrap;
    flex-shrink: 0;
}

.idle-cur.cores { border-color: rgba(45, 212, 191, 0.45); }
.idle-cur.cores .idle-cur-val { color: #5eead4; }
.idle-cur.rate  .idle-cur-val { color: #a7f3d0; font-size: 17px; }
.idle-cur.flux  { border-color: rgba(96, 165, 250, 0.42); width: 132px; }
.idle-cur.flux  .idle-cur-val { color: #93c5fd; }
.idle-cur.frag  { border-color: rgba(167, 139, 250, 0.42); width: 152px; }
.idle-cur.frag  .idle-cur-val { color: #c4b5fd; }

/* ── Body layout ──────────────────────────────────────────────────────── */

.idle-body {
    position: relative;
    display: flex;
    flex-direction: row;
    align-items: stretch;
    gap: 14px;
    padding: 14px 20px 16px 20px;
    flex-grow: 1;
    flex-shrink: 1;
    height: 0;          /* lets flex-grow decide the height, so children can scroll */
    overflow: hidden;
}

.idle-col { display: flex; flex-direction: column; gap: 12px; }

.idle-left {
    width: 300px;
    flex-shrink: 0;
    overflow-y: scroll;
    overflow-x: hidden;
    pointer-events: all;
}

.idle-right {
    width: 400px;
    flex-shrink: 0;
    overflow: hidden;
    pointer-events: all;
}

.idle-center {
    flex-grow: 1;
    align-items: center;
    justify-content: center;
    gap: 16px;
    pointer-events: all;
}

/* ── Info cards ───────────────────────────────────────────────────────── */

.idle-card {
    background-color: rgba(10, 17, 26, 0.88);
    border: 2px solid rgba(100, 116, 139, 0.18);
    border-radius: 12px;
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    flex-shrink: 0;
}

.idle-card-hd {
    font-size: 11px;
    font-weight: 900;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: rgba(94, 234, 212, 0.75);
    margin-bottom: 4px;
}

.idle-card-note {
    font-size: 11px;
    font-weight: 600;
    color: rgba(148, 163, 184, 0.65);
}
.idle-card-note.dim { color: rgba(251, 191, 36, 0.75); }

.idle-stat-row {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.idle-stat-lbl {
    font-size: 12px;
    font-weight: 600;
    color: rgba(148, 163, 184, 0.75);
}

.idle-stat-val {
    font-size: 14px;
    font-weight: 900;
    color: #e2e8f0;
}
.idle-stat-val.hot    { color: #5eead4; }
.idle-stat-val.capped  { color: #fbbf24; }
.idle-stat-val.starved { color: #f87171; }

.idle-bar-track {
    width: 100%;
    height: 10px;
    border-radius: 5px;
    background-color: rgba(30, 41, 59, 0.9);
    overflow: hidden;
}

.idle-bar-fill {
    height: 100%;
    border-radius: 5px;
    background-color: #5eead4;
    transition: width 0.2s;
}
.idle-bar-fill.frag { background-color: #a78bfa; }

/* Boosts */
.idle-boost {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    border-radius: 8px;
    background-color: rgba(30, 41, 59, 0.7);
    border-left: 3px solid rgba(94, 234, 212, 0.7);
}
.idle-boost.surge {
    border-left-color: #fb923c;
    background-color: rgba(120, 53, 15, 0.45);
}
.idle-boost-icon { font-size: 15px; }
.idle-boost-lbl  { font-size: 12px; font-weight: 800; color: #e2e8f0; }
.idle-boost-time {
    margin-left: auto;
    font-size: 11px;
    font-weight: 900;
    color: rgba(251, 191, 36, 0.9);
}

/* Chest tiers */
.idle-tier {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 9px;
    padding: 5px 0px;
    opacity: 0.42;
}
.idle-tier.on { opacity: 1; }

.idle-tier-icon { font-size: 20px; width: 26px; text-align: center; }
.idle-tier-body { display: flex; flex-direction: column; flex-grow: 1; }
.idle-tier-name { font-size: 12px; font-weight: 900; color: #e2e8f0; }
.idle-tier-blurb {
    font-size: 10px;
    font-weight: 600;
    color: rgba(148, 163, 184, 0.7);
}
.idle-tier.on .idle-tier-name { color: #5eead4; }

/* ── The board ────────────────────────────────────────────────────────── */

.idle-board-wrap {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.idle-board {
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 14px;
    border-radius: 16px;
    background-color: rgba(6, 14, 22, 0.9);
    border: 2px solid rgba(45, 212, 191, 0.22);
    box-shadow: 0px 0px 40px rgba(13, 148, 136, 0.14);
    transition: border-color 0.3s, box-shadow 0.3s;
}

.idle-board.surging {
    border-color: rgba(251, 146, 60, 0.65);
    box-shadow: 0px 0px 60px rgba(251, 146, 60, 0.35);
}

.idle-row { display: flex; flex-direction: row; gap: 5px; }

.idle-cell {
    position: relative;
    border-radius: 9px;
    background-color: rgba(17, 27, 40, 0.85);
    border: 2px solid rgba(71, 85, 105, 0.28);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    cursor: none;
    pointer-events: all;
    overflow: hidden;
}

.idle-cell.empty  { background-color: rgba(13, 21, 32, 0.7); }
.idle-cell.filled { border-color: rgba(148, 163, 184, 0.35); }

.idle-chest {
    font-size: 30px;
    text-shadow: 0px 2px 6px rgba(0, 0, 0, 0.6);
}

/* chest type tinting */
.idle-cell.c-normal { background-color: rgba(59, 44, 20, 0.55);  border-color: rgba(217, 160, 60, 0.45); }
.idle-cell.c-gem    { background-color: rgba(14, 60, 78, 0.6);   border-color: rgba(56, 189, 248, 0.55); }
.idle-cell.c-bomb   { background-color: rgba(70, 24, 24, 0.62);  border-color: rgba(248, 113, 113, 0.6); }
.idle-cell.c-locked { background-color: rgba(45, 39, 74, 0.65);  border-color: rgba(167, 139, 250, 0.6); }
.idle-cell.c-cursed { background-color: rgba(56, 20, 52, 0.7);   border-color: rgba(232, 121, 249, 0.65); }
.idle-cell.c-void   { background-color: rgba(10, 10, 20, 0.95);  border-color: rgba(129, 140, 248, 0.7); }
.idle-cell.c-boss   { background-color: rgba(80, 55, 8, 0.75);   border-color: rgba(251, 191, 36, 0.85); }

/* detonation feedback */
.idle-cell.fx-blast {
    background-color: rgba(94, 234, 212, 0.30);
    border-color: rgba(153, 246, 228, 0.85);
}
.idle-cell.fx-kill {
    background-color: rgba(251, 191, 36, 0.55);
    border-color: rgba(254, 240, 138, 0.95);
}
.idle-cell.fx-drone {
    background-color: rgba(248, 113, 113, 0.6);
    border-color: rgba(254, 202, 202, 0.95);
}

.idle-hits {
    position: absolute;
    bottom: 2px;
    right: 4px;
    font-size: 11px;
    font-weight: 900;
    color: rgba(255, 255, 255, 0.85);
    text-shadow: 0px 1px 3px rgba(0, 0, 0, 0.9);
}

/* A drone sits on its target for a beat before the volley goes off, so the
   explosion has something to come from. */
.idle-drone {
    position: absolute;
    font-size: 26px;
    text-shadow: 0px 0px 10px rgba(248, 113, 113, 0.9);
}

.idle-cell.armed {
    border-color: rgba(248, 113, 113, 0.95);
    background-color: rgba(80, 20, 20, 0.55);
}

/* golden chest */
.idle-cell.golden {
    background-color: rgba(120, 82, 6, 0.85);
    border-color: #fde047;
    box-shadow: 0px 0px 26px rgba(253, 224, 71, 0.7);
    animation: idle-golden-pulse 0.55s ease-in-out infinite alternate;
}

@keyframes idle-golden-pulse {
    0%   { transform: scale(1.00); }
    100% { transform: scale(1.10); }
}

.idle-golden { font-size: 32px; }

.idle-golden-timer {
    position: absolute;
    bottom: 1px;
    font-size: 11px;
    font-weight: 900;
    color: #fef9c3;
}

/* floating values */
.idle-float {
    position: absolute;
    font-size: 17px;
    font-weight: 900;
    color: #a7f3d0;
    text-shadow: 0px 2px 6px rgba(0, 0, 0, 0.9);
    pointer-events: none;
}
.idle-float.crit {
    font-size: 22px;
    color: #fde047;
}

.idle-callout {
    position: absolute;
    top: 40%;
    font-size: 34px;
    font-weight: 900;
    letter-spacing: 0.06em;
    color: #5eead4;
    text-shadow: 0px 0px 22px rgba(45, 212, 191, 0.8), 0px 3px 8px rgba(0, 0, 0, 0.9);
    pointer-events: none;
}
.idle-callout.big  { color: #a7f3d0; }
.idle-callout.mega { color: #fbbf24; font-size: 40px; }
.idle-callout.crit { color: #fb923c; font-size: 44px; }

/* ── Controls ─────────────────────────────────────────────────────────── */

.idle-controls {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 16px;
    flex-shrink: 0;
}

.idle-overload {
    position: relative;
    overflow: hidden;
    width: 290px;
    padding-top: 14px;
    padding-bottom: 14px;
    border-radius: 14px;
    border: 3px solid rgba(248, 113, 113, 0.55);
    background-color: rgba(60, 12, 12, 0.9);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1px;
    cursor: none;
    pointer-events: all;
    transition: transform 0.06s, border-color 0.12s;
}

.idle-overload.ready {
    border-color: #f87171;
    animation: idle-overload-pulse 1.4s ease-in-out infinite alternate;
}
.idle-overload.cooling { opacity: 0.62; cursor: none; }
.idle-overload:active { transform: translateY(2px); }

@keyframes idle-overload-pulse {
    0%   { box-shadow: 0px 0px 12px rgba(248, 113, 113, 0.25); }
    100% { box-shadow: 0px 0px 34px rgba(248, 113, 113, 0.65); }
}

.idle-overload-fill {
    position: absolute;
    left: 0; top: 0; bottom: 0;
    background-color: rgba(248, 113, 113, 0.18);
    pointer-events: none;
}

.idle-overload-label {
    font-size: 20px;
    font-weight: 900;
    letter-spacing: 0.14em;
    color: #fecaca;
}

.idle-overload-sub {
    font-size: 10px;
    font-weight: 800;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: rgba(254, 202, 202, 0.6);
}

.idle-surge-wrap {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 300px;
}

.idle-surge-track {
    position: relative;
    height: 34px;
    border-radius: 9px;
    background-color: rgba(15, 23, 35, 0.92);
    border: 2px solid rgba(148, 163, 184, 0.24);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
}
.idle-surge-track.ready {
    border-color: #fb923c;
    animation: idle-overload-pulse 0.9s ease-in-out infinite alternate;
}

.idle-surge-fill {
    position: absolute;
    left: 0; top: 0; bottom: 0;
    background-color: rgba(251, 146, 60, 0.42);
    transition: width 0.2s;
}

.idle-surge-lbl {
    position: relative;
    font-size: 12px;
    font-weight: 900;
    letter-spacing: 0.16em;
    color: #fed7aa;
}

.idle-surge-btn {
    padding-top: 10px;
    padding-bottom: 10px;
    border-radius: 9px;
    border: 2px solid #fb923c;
    background-color: rgba(154, 52, 18, 0.85);
    color: #ffedd5;
    font-size: 14px;
    font-weight: 900;
    letter-spacing: 0.12em;
    align-items: center;
    justify-content: center;
    cursor: none;
    pointer-events: all;
}
.idle-surge-btn:hover { background-color: rgba(194, 65, 12, 0.95); }

/* ── Meltdown strip ───────────────────────────────────────────────────── */

.idle-meltdown-strip {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 14px;
    width: 620px;
    padding: 10px 16px;
    border-radius: 12px;
    background-color: rgba(10, 17, 26, 0.85);
    border: 2px solid rgba(100, 116, 139, 0.18);
    flex-shrink: 0;
}
.idle-meltdown-strip.ready {
    border-color: rgba(251, 191, 36, 0.7);
    background-color: rgba(69, 26, 3, 0.6);
}

.idle-melt-icon { font-size: 20px; flex-shrink: 0; }
.idle-melt-text {
    font-size: 12px;
    font-weight: 700;
    color: rgba(203, 213, 225, 0.85);
    flex-grow: 1;
}
.idle-meltdown-strip.ready .idle-melt-text { color: #fde68a; font-weight: 900; }

.idle-melt-bar {
    margin-left: auto;
    width: 180px;
    height: 8px;
    border-radius: 4px;
    background-color: rgba(30, 41, 59, 0.9);
    overflow: hidden;
}
.idle-melt-fill {
    height: 100%;
    background-color: #fbbf24;
    border-radius: 4px;
}

.idle-melt-btn {
    margin-left: auto;
    padding: 8px 20px;
    border-radius: 9px;
    border: 2px solid #fbbf24;
    background-color: rgba(120, 53, 15, 0.9);
    color: #fef3c7;
    font-size: 12px;
    font-weight: 900;
    letter-spacing: 0.14em;
    cursor: none;
    pointer-events: all;
}
.idle-melt-btn:hover { background-color: rgba(180, 83, 9, 0.95); }

/* ── Right panel ──────────────────────────────────────────────────────── */

.idle-tabs { display: flex; flex-direction: row; gap: 6px; flex-shrink: 0; }

.idle-tab {
    flex-grow: 1;
    padding-top: 10px;
    padding-bottom: 10px;
    border-radius: 9px;
    border: 2px solid rgba(100, 116, 139, 0.22);
    background-color: rgba(12, 20, 30, 0.85);
    font-size: 18px;
    align-items: center;
    justify-content: center;
    cursor: none;
    pointer-events: all;
    opacity: 0.55;
    transition: opacity 0.1s, border-color 0.1s;
}
.idle-tab:hover { opacity: 0.85; }
.idle-tab.on {
    opacity: 1;
    border-color: rgba(45, 212, 191, 0.7);
    background-color: rgba(13, 40, 44, 0.9);
}
.idle-tab.fluxtab.on { border-color: rgba(96, 165, 250, 0.8); background-color: rgba(15, 32, 60, 0.9); }

.idle-buyrow {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}

.idle-buyrow-lbl {
    font-size: 10px;
    font-weight: 900;
    letter-spacing: 0.16em;
    color: rgba(148, 163, 184, 0.6);
    margin-right: 4px;
}

.idle-buy-amt {
    padding: 5px 14px;
    border-radius: 7px;
    border: 2px solid rgba(100, 116, 139, 0.22);
    background-color: rgba(12, 20, 30, 0.85);
    color: rgba(203, 213, 225, 0.7);
    font-size: 11px;
    font-weight: 900;
    cursor: none;
    pointer-events: all;
}
.idle-buy-amt.on {
    border-color: rgba(45, 212, 191, 0.75);
    color: #5eead4;
    background-color: rgba(13, 40, 44, 0.9);
}

/* height:0 + flex-grow is what makes this actually scroll instead of pushing
   its content off the bottom of the screen. */
.idle-panel {
    flex-grow: 1;
    flex-shrink: 1;
    height: 0;
    overflow-y: scroll;
    overflow-x: hidden;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding-right: 8px;
}

/* ── Upgrade rows ─────────────────────────────────────────────────────── */

.idle-up {
    display: flex;
    flex-direction: row;
    gap: 12px;
    padding: 10px 12px;
    border-radius: 11px;
    background-color: rgba(10, 17, 26, 0.9);
    border: 2px solid rgba(100, 116, 139, 0.16);
    cursor: none;
    pointer-events: all;
    flex-shrink: 0;
    transition: border-color 0.1s, background-color 0.1s, transform 0.06s;
}
.idle-up:hover  { background-color: rgba(18, 28, 42, 0.95); }
.idle-up:active { transform: translateY(1px); }

.idle-up.afford { border-color: rgba(45, 212, 191, 0.55); }
.idle-up.afford:hover { border-color: rgba(94, 234, 212, 0.95); }
.idle-up.poor   { opacity: 0.62; }
.idle-up.maxed  { border-color: rgba(251, 191, 36, 0.45); opacity: 0.9; }
.idle-up.locked { opacity: 0.35; cursor: none; }
.idle-up.perk.afford { border-color: rgba(96, 165, 250, 0.7); }
.idle-up.ach    { cursor: none; }
.idle-up.ach.done { border-color: rgba(52, 211, 153, 0.5); }

.idle-up-icon {
    font-size: 26px;
    width: 34px;
    flex-shrink: 0;
    text-align: center;
    align-items: center;
    justify-content: center;
}

.idle-up-body {
    display: flex;
    flex-direction: column;
    gap: 3px;
    flex-grow: 1;
}

.idle-up-top {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.idle-up-name {
    font-size: 14px;
    font-weight: 900;
    color: #e2e8f0;
}

.idle-up-lvl {
    font-size: 11px;
    font-weight: 800;
    color: rgba(148, 163, 184, 0.7);
}

.idle-up-desc {
    font-size: 11px;
    font-weight: 600;
    color: rgba(148, 163, 184, 0.72);
}

.idle-up-bottom {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: 3px;
}

.idle-up-effect {
    font-size: 11px;
    font-weight: 800;
    color: rgba(94, 234, 212, 0.85);
}

.idle-up-cost {
    font-size: 13px;
    font-weight: 900;
    color: #cbd5e1;
}
.idle-up.afford .idle-up-cost { color: #5eead4; }
.idle-up-cost.flux  { color: #93c5fd; }
.idle-up-cost.maxed { color: #fbbf24; }

/* ── Flux / log headers ───────────────────────────────────────────────── */

.idle-flux-head {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 12px 14px;
    border-radius: 11px;
    background-color: rgba(13, 25, 46, 0.9);
    border: 2px solid rgba(96, 165, 250, 0.3);
    flex-shrink: 0;
}

.idle-flux-amount {
    font-size: 22px;
    font-weight: 900;
    color: #93c5fd;
}

.idle-flux-sub {
    font-size: 11px;
    font-weight: 700;
    color: rgba(148, 163, 184, 0.75);
}

.idle-ach-reward {
    font-size: 11px;
    font-weight: 900;
    color: rgba(251, 191, 36, 0.9);
}

.idle-ach-bar {
    width: 100%;
    height: 6px;
    border-radius: 3px;
    background-color: rgba(30, 41, 59, 0.9);
    overflow: hidden;
    margin-top: 4px;
}

.idle-ach-fill {
    height: 100%;
    border-radius: 3px;
    background-color: rgba(94, 234, 212, 0.8);
}

.idle-ach-prog {
    font-size: 10px;
    font-weight: 800;
    color: rgba(148, 163, 184, 0.6);
    margin-top: 2px;
}

/* ── Toasts ───────────────────────────────────────────────────────────── */

.idle-toasts {
    position: absolute;
    top: 96px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    pointer-events: none;
}

.idle-toast {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 9px;
    padding: 8px 18px;
    border-radius: 10px;
    background-color: rgba(8, 14, 22, 0.94);
    border: 2px solid rgba(100, 116, 139, 0.35);
}

.idle-toast-icon { font-size: 17px; }
.idle-toast-text { font-size: 13px; font-weight: 800; color: #e2e8f0; }

.idle-toast.idle-toast-gold        { border-color: rgba(253, 224, 71, 0.8); }
.idle-toast.idle-toast-chain       { border-color: rgba(94, 234, 212, 0.75); }
.idle-toast.idle-toast-crit        { border-color: rgba(251, 146, 60, 0.85); }
.idle-toast.idle-toast-frag        { border-color: rgba(167, 139, 250, 0.8); }
.idle-toast.idle-toast-flux        { border-color: rgba(96, 165, 250, 0.85); }
.idle-toast.idle-toast-surge       { border-color: rgba(251, 146, 60, 0.9); }
.idle-toast.idle-toast-big         { border-color: rgba(52, 211, 153, 0.85); }
.idle-toast.idle-toast-achievement { border-color: rgba(251, 191, 36, 0.9); }
.idle-toast.idle-toast-dim         { opacity: 0.6; }

/* ── Modals ───────────────────────────────────────────────────────────── */

.idle-modal-bg {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    background-color: rgba(2, 4, 8, 0.86);
    pointer-events: all;
}

.idle-modal {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 640px;
    max-height: 88%;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: 34px 40px;
    border-radius: 18px;
    background-color: rgba(9, 15, 24, 0.98);
    border: 3px solid rgba(45, 212, 191, 0.45);
    box-shadow: 0px 0px 70px rgba(13, 148, 136, 0.28);
    pointer-events: all;
}

.idle-modal.meltdown { border-color: rgba(251, 191, 36, 0.6); box-shadow: 0px 0px 70px rgba(180, 83, 9, 0.35); }
.idle-modal.daily    { border-color: rgba(167, 139, 250, 0.6); }
.idle-modal.intro    { width: 720px; }

.idle-modal-eyebrow {
    font-size: 11px;
    font-weight: 900;
    letter-spacing: 0.30em;
    text-transform: uppercase;
    color: rgba(148, 163, 184, 0.55);
}

.idle-modal-title {
    font-size: 34px;
    font-weight: 900;
    letter-spacing: 0.06em;
    color: #5eead4;
    text-shadow: 0px 0px 24px rgba(45, 212, 191, 0.4);
}
.idle-modal.meltdown .idle-modal-title { color: #fbbf24; }
.idle-modal.daily    .idle-modal-title { color: #c4b5fd; }

.idle-modal-sub {
    font-size: 13px;
    font-weight: 600;
    color: rgba(203, 213, 225, 0.75);
    text-align: center;
    max-width: 520px;
    margin-bottom: 8px;
}

.idle-modal-btn {
    margin-top: 14px;
    padding: 14px 40px;
    border-radius: 11px;
    border: 2px solid #2dd4bf;
    background-color: rgba(13, 78, 74, 0.9);
    color: #ccfbf1;
    font-size: 16px;
    font-weight: 900;
    letter-spacing: 0.16em;
    align-items: center;
    justify-content: center;
    cursor: none;
    pointer-events: all;
}
.idle-modal-btn:hover { background-color: rgba(15, 118, 110, 0.95); }

.idle-modal-btn.ghost {
    border-color: rgba(148, 163, 184, 0.4);
    background-color: rgba(30, 41, 59, 0.7);
    color: rgba(203, 213, 225, 0.85);
}
.idle-modal-btn.danger {
    border-color: #fbbf24;
    background-color: rgba(146, 64, 14, 0.9);
    color: #fef3c7;
}
.idle-modal-btn.danger:hover { background-color: rgba(180, 83, 9, 0.95); }

.idle-modal-btns { display: flex; flex-direction: row; gap: 14px; }

/* offline report */
.idle-offline-haul {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 18px 44px;
    border-radius: 14px;
    background-color: rgba(13, 40, 44, 0.7);
    border: 2px solid rgba(45, 212, 191, 0.35);
    margin-top: 6px;
}

.idle-offline-amount {
    font-size: 46px;
    font-weight: 900;
    color: #5eead4;
    text-shadow: 0px 0px 26px rgba(45, 212, 191, 0.5);
}

.idle-offline-lbl {
    font-size: 12px;
    font-weight: 900;
    letter-spacing: 0.20em;
    color: rgba(148, 163, 184, 0.7);
}

.idle-offline-extra {
    font-size: 13px;
    font-weight: 800;
    color: #c4b5fd;
    margin-top: 6px;
}

.idle-offline-warn {
    font-size: 12px;
    font-weight: 700;
    color: rgba(251, 191, 36, 0.9);
    text-align: center;
    max-width: 480px;
    margin-top: 8px;
}

/* daily */
.idle-streak-row { display: flex; flex-direction: row; gap: 8px; margin-top: 6px; }

.idle-streak-pip {
    width: 44px;
    height: 44px;
    border-radius: 10px;
    background-color: rgba(30, 41, 59, 0.8);
    border: 2px solid rgba(100, 116, 139, 0.25);
    align-items: center;
    justify-content: center;
    font-size: 15px;
    font-weight: 900;
    color: rgba(148, 163, 184, 0.6);
}
.idle-streak-pip.on {
    background-color: rgba(76, 29, 149, 0.6);
    border-color: rgba(167, 139, 250, 0.7);
    color: #ddd6fe;
}
.idle-streak-pip.today {
    border-color: #fbbf24;
    color: #fde68a;
    animation: idle-overload-pulse 1.1s ease-in-out infinite alternate;
}

.idle-daily-reward {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-top: 14px;
}
.idle-daily-mult {
    font-size: 44px;
    font-weight: 900;
    color: #fbbf24;
}
.idle-daily-lbl {
    font-size: 13px;
    font-weight: 800;
    color: rgba(203, 213, 225, 0.8);
}

/* meltdown */
.idle-melt-payout {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 10px;
    padding: 14px 38px;
    border-radius: 14px;
    background-color: rgba(30, 41, 90, 0.6);
    border: 2px solid rgba(96, 165, 250, 0.45);
}
.idle-melt-payout-val { font-size: 44px; font-weight: 900; color: #93c5fd; }
.idle-melt-payout-lbl { font-size: 15px; font-weight: 900; color: rgba(148, 163, 184, 0.85); }

.idle-melt-cols {
    display: flex;
    flex-direction: row;
    gap: 16px;
    margin-top: 14px;
    width: 100%;
}

.idle-melt-col {
    flex-grow: 1;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 12px 14px;
    border-radius: 11px;
    background-color: rgba(15, 23, 35, 0.85);
    border: 2px solid rgba(100, 116, 139, 0.2);
}
.idle-melt-col.keep { border-color: rgba(52, 211, 153, 0.4); }
.idle-melt-col.lose { border-color: rgba(248, 113, 113, 0.4); }

.idle-melt-col-hd {
    font-size: 11px;
    font-weight: 900;
    letter-spacing: 0.16em;
    color: rgba(203, 213, 225, 0.8);
    margin-bottom: 4px;
}

.idle-melt-line {
    font-size: 12px;
    font-weight: 700;
    color: rgba(148, 163, 184, 0.85);
}

/* intro */
.idle-intro-steps {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 10px;
    width: 100%;
}

.idle-intro-step {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 14px;
    padding: 10px 14px;
    border-radius: 11px;
    background-color: rgba(15, 23, 35, 0.8);
    border-left: 3px solid rgba(45, 212, 191, 0.6);
}

.idle-intro-num {
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    border-radius: 50%;
    background-color: rgba(13, 78, 74, 0.9);
    border: 2px solid rgba(45, 212, 191, 0.6);
    align-items: center;
    justify-content: center;
    font-size: 13px;
    font-weight: 900;
    color: #99f6e4;
}

.idle-intro-txt {
    font-size: 13px;
    font-weight: 600;
    color: rgba(203, 213, 225, 0.9);
}


/* ── Board shake ──────────────────────────────────────────────────────── */
/* Own keyframes rather than reusing Gamehud's, so this stylesheet stands
   on its own if that file ever changes. */

.idle-board.idle-shake-1 { animation: idle-shake-1 0.28s ease-out; }
.idle-board.idle-shake-2 { animation: idle-shake-2 0.28s ease-out; }
.idle-board.idle-shake-3 { animation: idle-shake-3 0.45s ease-out; }

@keyframes idle-shake-1 {
    0%   { transform: translate(0px, 0px); }
    30%  { transform: translate(-2px, 1px); }
    60%  { transform: translate(2px, -1px); }
    100% { transform: translate(0px, 0px); }
}

@keyframes idle-shake-2 {
    0%   { transform: translate(0px, 0px) rotate(0deg); }
    20%  { transform: translate(-4px, 2px) rotate(-0.3deg); }
    45%  { transform: translate(4px, -3px) rotate(0.3deg); }
    70%  { transform: translate(-3px, -1px) rotate(-0.2deg); }
    100% { transform: translate(0px, 0px) rotate(0deg); }
}

@keyframes idle-shake-3 {
    0%   { transform: translate(0px, 0px) rotate(0deg) scale(1.00); }
    15%  { transform: translate(-7px, 4px) rotate(-0.6deg) scale(1.01); }
    35%  { transform: translate(7px, -5px) rotate(0.6deg) scale(1.02); }
    55%  { transform: translate(-5px, -3px) rotate(-0.4deg) scale(1.01); }
    78%  { transform: translate(4px, 2px) rotate(0.3deg) scale(1.00); }
    100% { transform: translate(0px, 0px) rotate(0deg) scale(1.00); }
}
playnplay.chain_reaction / Game/2D/UI/MainMenuTutorial.razor
Game game
@using System;
@using Sandbox;
@using Sandbox.Game.Menu
@using Sandbox.UI;
@inherits PanelComponent
@namespace Sandbox

<root>
    @if(_active)
    {
		@* ── Debug card ── *@
		@if(_debugMode)
		{
			<div class="tut-debug-card">
				<div class="tut-debug-title">MM TUT β€” Step @_step</div>
				<div class="tut-debug-row">
					<span>BL: @_debugBL.ToString("F1")%</span>
					<button class="tut-debug-btn" onclick=@(() => { _debugBL -= 1f; StateHasChanged(); })>β—€</button>
					<button class="tut-debug-btn" onclick=@(() => { _debugBL += 1f; StateHasChanged(); })>β–Ά</button>
				</div>
				<div class="tut-debug-row">
					<span>BY: @_debugBY.ToString("F0")px</span>
					<button class="tut-debug-btn" onclick=@(() => { _debugBY -= 10f; StateHasChanged(); })>β–²</button>
					<button class="tut-debug-btn" onclick=@(() => { _debugBY += 10f; StateHasChanged(); })>β–Ό</button>
				</div>
				<div class="tut-debug-row">
					<span>BR: @_debugBR.ToString("F1")%</span>
					<button class="tut-debug-btn" onclick=@(() => { _debugBR -= 1f; StateHasChanged(); })>β—€</button>
					<button class="tut-debug-btn" onclick=@(() => { _debugBR += 1f; StateHasChanged(); })>β–Ά</button>
				</div>
				<div class="tut-debug-row">
					<span>HX: @_debugHX.ToString("F1")%</span>
					<button class="tut-debug-btn" onclick=@(() => { _debugHX -= 1f; StateHasChanged(); })>β—€</button>
					<button class="tut-debug-btn" onclick=@(() => { _debugHX += 1f; StateHasChanged(); })>β–Ά</button>
				</div>
				<div class="tut-debug-row">
					<span>HY: @_debugHY.ToString("F0")px</span>
					<button class="tut-debug-btn" onclick=@(() => { _debugHY -= 10f; StateHasChanged(); })>β–²</button>
					<button class="tut-debug-btn" onclick=@(() => { _debugHY += 10f; StateHasChanged(); })>β–Ό</button>
				</div>
				<div class="tut-debug-row">
					<span>HW: @_debugHW.ToString("F0")px</span>
					<button class="tut-debug-btn" onclick=@(() => { _debugHW -= 10f; StateHasChanged(); })>β—€</button>
					<button class="tut-debug-btn" onclick=@(() => { _debugHW += 10f; StateHasChanged(); })>β–Ά</button>
				</div>
				<div class="tut-debug-row">
					<span>HH: @_debugHH.ToString("F0")px</span>
					<button class="tut-debug-btn" onclick=@(() => { _debugHH -= 10f; StateHasChanged(); })>β–²</button>
					<button class="tut-debug-btn" onclick=@(() => { _debugHH += 10f; StateHasChanged(); })>β–Ό</button>
				</div>
				<button class="tut-debug-log" onclick=@LogCurrentValues>πŸ“‹ Log</button>
			</div>
		}

		@* ── Dim layer ── *@
		<div class="tut-dim"></div>

		@* ── Highlight ── *@
		@if(_step < _steps.Length && _steps[_step].HW > 0)
		{
			var s = _steps[_step];
			float hx = _debugHX != 0f ? _debugHX : s.HX;
			float hy = _debugHY != 0f ? _debugHY : s.HY;
			float hw = _debugHW != 0f ? _debugHW : s.HW;
			float hh = _debugHH != 0f ? _debugHH : s.HH;
			<div class="tut-highlight"
					style="left:@hx.ToString("F1")%;top:@hy.ToString("F0")px;width:@hw.ToString("F0")px;height:@hh.ToString("F0")px;">
			</div>
		}

		@* ── Instruction box ── *@
		@if(_step < _steps.Length)
		{
			var s = _steps[_step];
			float bl = _debugBL != 0f ? _debugBL : s.BL;
			float br = _debugBR != 0f ? _debugBR : s.BR;
			float by = _debugBY != 0f ? _debugBY : s.BY;

			float boxWidthPct = s.BW > 0f ? (s.BW / 1920f * 100f) : (380f / 1920f * 100f);

			string posStyle = br != 0f
				? $"left:{(100f - br - boxWidthPct).ToString("F1")}%;"
				: $"left:{bl.ToString("F1")}%;";
			string boxStyle = $"{posStyle}top:{by.ToString("F0")}px;{(s.BW > 0f ? $"width:{s.BW.ToString("F0")}px;" : "")}";

			<div class="tut-box" style="@boxStyle">
				<div class="tut-step-count">@(_step + 1) / @_steps.Length</div>
				<div class="tut-title">@s.Title</div>
				<div class="tut-text">@s.Text</div>

				@* Action hint β€” shown when player must act *@
				@if(s.Trigger == MMTutTrigger.HeartPurchased)
				{
					<div class="tut-action-hint">πŸ‘† @s.ActionHint</div>
				}

				@* Next button β€” shown on Any steps *@
				@if(s.Trigger == MMTutTrigger.Any)
				{
					<button class="tut-btn-next" onclick=@NextStep>Got it β†’</button>
				}

				@* Final step β€” Play button *@
				@if(s.Trigger == MMTutTrigger.Done)
				{
					<button class="tut-btn-next" onclick=@OnPlayAndComplete>β–Ά Let's play!</button>
				}
			</div>
		}
    }
</root>

@code {
    public static MainMenuTutorial Instance { get; private set; }
    public bool IsActive => _active;

    bool _active = false;
	public int Step => _step;
    int  _step   = 0;

    // ── Debug mode ──────────────────────────────────────────────────── //
    const bool _debugMode = false; // set false before release
    float _debugBL = 0f;
    float _debugBR = 0f;
    float _debugBY = 0f;
    float _debugHX = 0f;
    float _debugHY = 0f;
    float _debugHW = 0f;
    float _debugHH = 0f;

    void LogCurrentValues()
    {
        Log.Info($"// MM Step {_step} β€” {_steps[_step].Title}");
        Log.Info($"BL={_debugBL:F1}f, BY={_debugBY:F0}f, BR={_debugBR:F1}f,");
        Log.Info($"HX={_debugHX:F1}f, HY={_debugHY:F0}f, HW={_debugHW:F0}f, HH={_debugHH:F0}f");
    }

    // ── Trigger types ────────────────────────────────────────────────── //
    public enum MMTutTrigger
    {
        Any,           // advances via Got it button
        HeartPurchased, // advances when player buys Heart consumable
        Done,          // final step β€” shows Play button
    }

    // ── Step data ────────────────────────────────────────────────────── //
    public struct MMTutStep
    {
        public string Title;
        public string Text;
        public string ActionHint;
        public MMTutTrigger Trigger;
        public float HX, HY, HW, HH; // highlight
        public float BL, BR, BY, BW;  // box position
    }

    // ── Steps ────────────────────────────────────────────────────────── //
    // Positions are placeholder β€” tune with debug card in-game
    static readonly MMTutStep[] _steps = new MMTutStep[]
    {
        // Step 1 β€” Welcome
        new MMTutStep {
            Title   = "Welcome to 10XPLOSION!",
            Text    = "Here's a quick tour of how the game works. It'll take 30 seconds.",
            Trigger = MMTutTrigger.Any,
            HW = 0,
            BL = 47f, BY = 370f
        },

        // Step 2 β€” Play button
        new MMTutStep {
            Title   = "Play runs to earn πŸ’  Fragments",
            Text    = "Fragments are the game's currency. The further you go each run, the more you earn.",
            Trigger = MMTutTrigger.Any,
            HW = 0,
            BL = 47f, BY = 370f
        },

        // Step 3 β€” Shop β†’ Modifiers
        new MMTutStep {
            Title   = "The Shop β€” Modifiers 🎲",
            Text    = "Spend fragments to unlock Modifiers. These permanently strengthen every run you play. Unlock once, keep forever.",
            Trigger = MMTutTrigger.Any,
            HX = 40f, HY = 200f, HW = 110f, HH = 40f,
            BR = 61f, BY = 120f
        },

        // Step 4 β€” Consumables explanation
        new MMTutStep {
            Title   = "Consumables 🎁",
            Text    = "Consumables are single-use items you bring into a run. Buy them once, use them at any time during a run. A bar will appear in-game when you own one.",
            Trigger = MMTutTrigger.Any,
            HX = 46f, HY = 200f, HW = 130f, HH = 40f,
            BR = 61f, BY = 110
        },

        // Step 5 β€” Buy Heart (action step)
        new MMTutStep {
            Title   = "Get yourself a Heart ❀️",
            Text    = "We've added 100 πŸ’  to your balance. Buy the Heart consumable β€” it gives you an extra life mid-run when you need it most.",
            ActionHint = "Click the buy button on the Heart card",
            Trigger = MMTutTrigger.HeartPurchased,
            HX = 65f, HY = 860f, HW = 80f, HH = 70f,
            BR = 7f, BY = 800f
        },

        // Step 6 β€” Pet tab
        new MMTutStep {
            Title = "Your secret package πŸ“¦",
            Text = "Play 3 runs to unlock what's inside. This section is for cosmetic skins β€” coming soon.",
    		Trigger = MMTutTrigger.Any,
            HX = 53f, HY = 360f, HW = 70f, HH = 40f,
            BR = 50f, BY = 340f
        },

        // Step 7 β€” Bombs
        new MMTutStep {
            Title   = "Unlock new Bombs πŸ’£",
            Text    = "Different bomb types open up new chain reaction strategies. Mix and match to find your playstyle.",
            Trigger = MMTutTrigger.Any,
            HX = 57f, HY = 220f, HW = 90f, HH = 40f,
            BR = 5f, BY = 130f
        },

        // Step 8 β€” Leaderboard
        new MMTutStep {
            Title   = "Compete Globally πŸ…",
            Text    = "The leaderboard tracks your best score, chain reactions, and pet level. How far can you go?",
            Trigger = MMTutTrigger.Any,
            HX = 75f, HY = 400f, HW = 10f, HH = 10f,
            BR = 5f, BY = 160f
        },

        // Step 9 β€” Done
        new MMTutStep {
            Title   = "You're ready. πŸ’₯",
            Text    = "Play runs β†’ earn fragments β†’ unlock things β†’ become stronger. Good luck!",
            Trigger = MMTutTrigger.Done,
			BR = 20f, BY = 370f
        },
    };

    // ── Lifecycle ────────────────────────────────────────────────────── //
    protected override void OnStart()
    {
        Instance = this;
        _active  = false;
    }

    public void Activate(PlayerSave save)
    {
        //var save = ChainReactionGame.Save ?? PlayerSave.Load();
        if (save?.MenuTutorialCompleted == true) return;

        _step   = save?.MenuTutorialStep ?? 0;
        _active = true;
        ResetDebug();

		// If resuming at step 4 (Buy Heart) but heart already purchased, skip it
		if (_step == 4 && (save?.GetConsumableCount(ConsumableId.Heart) ?? 0) > 0)
			_step = 5;

        // Step 3 requires shop/mods to be visible β€” navigate there
        ApplySideEffect(_step);
        StateHasChanged();
    }

    // ── Called from MainMenu when Heart is bought ────────────────────── //
    public void OnHeartPurchased()
    {
        if (!_active || _step >= _steps.Length) return;
        if (_steps[_step].Trigger != MMTutTrigger.HeartPurchased) return;
        NextStep();
    }

    // ── Step advancement ─────────────────────────────────────────────── //
  	void NextStep() 
	{
		_step++;
		ResetDebug();

		if (_step >= _steps.Length)
		{
			Complete();
			return;
		}

		// Persist current step so we can resume after crash - save tuto step
		var save = PlayerSave.Load();
		if (save != null) 
		{ 
			save.MenuTutorialStep = _step; 
			save.Save(); 
		}

		ApplySideEffect(_step);
		Sound.Play("UI_Button");
		StateHasChanged();
}

    // ── Side effects β€” navigate tabs automatically ───────────────────── //
    void ApplySideEffect(int step)
    {
        var mm = MainMenu.Instance;
        if (mm == null) return;

        switch(step)
        {
            case 2: // Show shop modifiers
                mm.SetTabPublic("shop");
                mm.SetShopTabPublic("mods");
                // Grant frags on step before heart purchase
                break;

            case 3: // Show consumables
                mm.SetTabPublic("shop");
                mm.SetShopTabPublic("consumables");
                break;

		    case 4: // Buy Heart β€” shop consumables + grant frags if not already granted
            var save = ChainReactionGame.Save ?? PlayerSave.Load();
            if (save != null && !save.TutorialHeartFragsGranted)
            {
                save.TotalFragments += 100;
                save.TutorialHeartFragsGranted = true;
                save.Save();
            }
            mm.SetTabPublic("shop");
            mm.SetShopTabPublic("consumables");
            mm.ReloadSave();
            break;
		
            case 5: // Pet tab
			    mm.SetTabPublic("shop");
                mm.SetShopTabPublic("pets");
                break;

            case 6: // Bombs
                mm.SetTabPublic("shop");
                mm.SetShopTabPublic("bombs");
                break;

            case 7: // Leaderboard
                mm.SetTabPublic("lb");
                break;
			case 8: // Done β€” no navigation needed
            break;
        }
    }

    void Skip() => Complete();

    void OnPlayAndComplete()
    {
        Complete();
        MainMenu.Instance?.StartGamePublic();
    }

    void Complete()
    {
        _active = false;

		// Get save from MainMenu directly
		var mm = MainMenu.Instance;

		// just use ChainReactionGame.Save or load fresh
		var save = PlayerSave.Load();

		if (save != null)
		{
			save.MenuTutorialCompleted = true;
			save.MenuTutorialStep = 0;
			save.Save();
		}
		StateHasChanged();
    }

    void ResetDebug()
    {
        _debugBL = 0f; _debugBR = 0f; _debugBY = 0f;
        _debugHX = 0f; _debugHY = 0f; _debugHW = 0f; _debugHH = 0f;
    }

	public void ForceReset()
	{
		_active = false;
		_step   = 0;
		ResetDebug();
		StateHasChanged();
	}

    protected override int BuildHash() =>
        System.HashCode.Combine(_active, _step, _debugBL, _debugBY, _debugHX, _debugHY, _debugHW, _debugHH);
}
playnplay.chain_reaction / Game/2D/GameMode.cs
Game game
using Sandbox;

public enum GameMode
{
    Wave,
    Survival,
    Idle,       // Reactor mode β€” the self-running chain reaction economy
}
playnplay.chain_reaction / Game/Idle/IdlePet.cs
Game game
using System;

/// <summary>
/// The pet is the Reactor's Fragment sink.
///
/// Cores buy upgrades that reset on Meltdown, and Flux buys perks that never do.
/// The pet sits outside both: Fragments condense slowly, on a daily ceiling, and
/// go into a permanent output multiplier that takes weeks rather than hours to
/// finish. It is the long-horizon goal that survives every reset.
/// </summary>
public static class IdlePet
{
	public const int MaxLevel = 6;

	/// <summary>Fragments needed to go from <paramref name="level"/> to the next one.</summary>
	public static int CostToUpgrade( int level ) => level switch
	{
		1 => 120,
		2 => 300,
		3 => 700,
		4 => 1_500,
		5 => 3_200,
		_ => 0,
	};

	/// <summary>Total Fragments to take a fresh pet all the way to level 6.</summary>
	public static int TotalCost
	{
		get
		{
			int total = 0;
			for ( int lvl = 1; lvl < MaxLevel; lvl++ ) total += CostToUpgrade( lvl );
			return total;
		}
	}

	public static bool IsMaxed( int level ) => level >= MaxLevel;

	/// <summary>Reactor output multiplier granted by the pet, before Pet Link.</summary>
	public static double OutputBonus( int level ) => 0.15 * Math.Max( 0, level - 1 );

	public static string Name( int level ) => level switch
	{
		1 => "Mysterious Egg",
		2 => "Cat",
		3 => "Wolf",
		4 => "Tiger",
		5 => "Baby Drake",
		_ => "Dragon",
	};

	public static string Emoji( int level ) => level switch
	{
		1 => "πŸ₯š",
		2 => "🐱",
		3 => "🐺",
		4 => "🐯",
		5 => "🐣",
		_ => "πŸ‰",
	};

	public static string Blurb( int level ) => level switch
	{
		1 => "Something is moving in there. Feed it Fragments and find out what.",
		2 => "It watches the reactor and purrs at every chain.",
		3 => "Faster, hungrier, and it has started organising the drones.",
		4 => "Big enough now to shove chests into the blast radius itself.",
		5 => "Wings, and the beginnings of a temper.",
		_ => "Fully grown. The reactor runs hot around it.",
	};
}
playnplay.chain_reaction / Game/Idle/IdleSave.cs
Game game
using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Persistent state for Reactor (Idle) mode.
/// Serialized as a nested object inside PlayerSave β€” see PlayerSave.Idle.
/// Everything here survives a Meltdown except what ResetForMeltdown() clears.
/// </summary>
public class IdleSave
{
	// ── Unlock / onboarding ───────────────────────────────────────
	public bool Unlocked      { get; set; } = false;  // granted after the first finished run
	public bool IntroShown    { get; set; } = false;  // first-entry explainer
	public bool MeltdownSeen  { get; set; } = false;  // has the prestige panel ever been opened

	// ── Currencies ────────────────────────────────────────────────
	public double Cores         { get; set; } = 0;   // spendable, wiped by Meltdown
	public double CycleCores    { get; set; } = 0;   // earned since last Meltdown β€” drives Flux payout
	public double AllTimeCores  { get; set; } = 0;   // never resets
	public int    Flux          { get; set; } = 0;   // unspent prestige currency
	public int    FluxEarned    { get; set; } = 0;   // lifetime, for milestones
	public int    Meltdowns     { get; set; } = 0;

	// ── Upgrade levels ────────────────────────────────────────────
	// Keys are IdleUpgradeId.ToString(); missing key == level 0.
	public Dictionary<string, int> Upgrades { get; set; } = new();
	public Dictionary<string, int> Perks    { get; set; } = new();

	// ── Offline bookkeeping ───────────────────────────────────────
	public long   LastSeenUnix   { get; set; } = 0;
	public double MeasuredCps    { get; set; } = 0;   // smoothed cores/sec, used for offline payout
	public double BestCps        { get; set; } = 0;

	// Pending offline haul, staged so the welcome-back panel can show it
	public double PendingOfflineCores   { get; set; } = 0;
	public double PendingOfflineSeconds { get; set; } = 0;
	public int    PendingOfflineFrags   { get; set; } = 0;

	// ── Daily loop ────────────────────────────────────────────────
	public string LastDailyDate { get; set; } = "";
	public int    DailyStreak   { get; set; } = 0;
	public bool   DailyClaimed  { get; set; } = false;

	// ── Fragment drip (own bucket β€” does not touch the pet farm cap) ──
	public int    TodayReactorFragments { get; set; } = 0;
	public string LastFragDate          { get; set; } = "";
	public double FragProgress          { get; set; } = 0; // cores banked toward the next fragment

	// ── Records / milestones ──────────────────────────────────────
	public int    TotalDetonations { get; set; } = 0;
	public long   TotalChestsBlown { get; set; } = 0;
	public int    BestChain        { get; set; } = 0;
	public double BestSingleBlast  { get; set; } = 0;
	public int    GoldenGrabbed    { get; set; } = 0;
	public double SecondsRun       { get; set; } = 0;
	public List<string> Achievements { get; set; } = new();

	// ── Upgrade helpers ───────────────────────────────────────────
	public int Level( IdleUpgradeId id )
		=> Upgrades.TryGetValue( id.ToString(), out int v ) ? v : 0;

	public void SetLevel( IdleUpgradeId id, int level )
		=> Upgrades[id.ToString()] = Math.Max( 0, level );

	public int PerkLevel( IdlePerkId id )
		=> Perks.TryGetValue( id.ToString(), out int v ) ? v : 0;

	public void SetPerkLevel( IdlePerkId id, int level )
		=> Perks[id.ToString()] = Math.Max( 0, level );

	public bool HasAchievement( string id ) => Achievements.Contains( id );

	// ── Meltdown (prestige) ───────────────────────────────────────
	/// <summary>Wipes the run-scoped economy. Flux, perks, records and achievements stay.</summary>
	public void ResetForMeltdown()
	{
		Cores      = 0;
		CycleCores = 0;
		Upgrades.Clear();
		Meltdowns++;

		// Kickstart perk hands back a head start so the early grind shrinks each time
		int kick = PerkLevel( IdlePerkId.Kickstart );
		if ( kick > 0 )
		{
			SetLevel( IdleUpgradeId.ChestValue, kick * 6 );
			SetLevel( IdleUpgradeId.SpawnRate,  kick * 4 );
			SetLevel( IdleUpgradeId.CycleSpeed, kick * 4 );
			SetLevel( IdleUpgradeId.DroneCount, Math.Min( kick, 3 ) );
		}
	}

	// ── Day rollover for the fragment bucket ──────────────────────
	public void EnsureFragDay()
	{
		string today = DateTime.UtcNow.ToString( "yyyy-MM-dd" );
		if ( LastFragDate == today ) return;
		LastFragDate = today;
		TodayReactorFragments = 0;
	}

	public int FragmentsToday
	{
		get { EnsureFragDay(); return TodayReactorFragments; }
	}
}
playnplay.chain_reaction / styles/form/_dropdown.scss
Game game
$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;
	}
}
playnplay.chain_reaction / ui/components/packagecard.razor.scss
Game game
@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;
		}
	}
}


.package-card.list packageflairbar
{
	display: none;
}

.package-card.list .package-users
{
	display: none;
}
playnplay.chain_reaction / game/2d/ui/bombwheel.razor.scss
Game game
root {
    /* Declared so no panel in this tree ever claims the OS pointer -- a hovered
       panel outranks Mouse.CursorType, and one that declares nothing still claims
       the default arrow. GameCursor draws the cursor instead. */
    cursor: none;
    flex-grow:1; width:100%; height:100%;
    pointer-events:none; position:relative;
    overflow:visible;
}

.mini-hand {
    position:absolute;
    bottom:15px;
    left:0; right:0px;           /* right edge = sidebar width */
    display:flex; flex-direction:row; gap:14px; align-items:flex-end;
    justify-content:center;        /* centers within the grid area, not full viewport */
    pointer-events:all;
    z-index:30;
}

.mini-card {
    width:80px;
    height: 80px;
    background-color:rgba(31,27,83,0.85);   /* more opaque β€” was barely visible */
    border:2px solid rgba(255,255,255,0.18);
    border-radius:16px; padding:16px 10px 12px;
    display:flex; flex-direction:column; align-items:center; gap:10px;
    cursor: none; position:relative;
    transition:background-color 0.1s, border-color 0.1s, transform 0.1s;
}
.mini-card:hover  { background-color:rgba(117,110,194,0.75); transform:translateY(-5px); }
.mini-card.active {
    background-color:rgba(83,74,183,0.90);
    border-color:#a78bfa;
    border-width:3px;
    transform:translateY(-16px);
}
.mini-card.used {
    background-color:rgba(29,158,117,0.28);
    border-color:rgba(29,158,117,0.60);
    opacity:0.75;
    cursor: none;
}

.mini-icon { font-size:30px; position: relative; bottom: 4px;}     /* was 40px β€” easier to see */
.mini-name { font-size:13px; font-weight:800; color:rgba(255,255,255,0.80); }
.mini-card.active .mini-name { color:#fff; font-size:15px; }

.mini-badge {
    position:absolute; top:5px; right:7px;
    font-size:12px; font-weight:700;
    color:rgba(80,220,140,0.95);
    background-color:rgba(29,158,117,0.40);
    border-radius:4px; padding:0 5px;
}

.kb-hint {
    position:absolute; top:5px; left:7px;
    font-size:12px; font-weight:800;
    color:rgba(255,255,255,0.45);
    background-color:rgba(255,255,255,0.10);
    border-radius:4px; padding:0 6px;
}
.mini-card.active .kb-hint {
    color:rgba(255,255,255,0.80);
    background-color:rgba(255,255,255,0.18);
}

.mini-det-wrap { align-self:flex-end; }
.mini-det {
    width:90px;                    /* was 90px */
    background:linear-gradient(135deg,#7c3aed,#a855f7);
    border:2px solid rgba(167,139,250,0.40);
    border-radius:14px; 
    padding:12px 10px 5px;
    display:flex; flex-direction:column; align-items:center; gap:5px;
    cursor: none;
    transition:opacity 0.15s;
}
.mini-det-wrap.ready .mini-det { border-color:#a78bfa; }
.mini-det-wrap.off   .mini-det { opacity:0.25; pointer-events:none; }
.mini-det-icon  { font-size:28px; }
.mini-det-label { font-size:13px; font-weight:800; color:#fff; letter-spacing:0.08em; }

/* SCREEN SHAKE */
.mini-hand.shake-small  { animation: shake-small  0.25s ease-out; }
.mini-hand.shake-medium { animation: shake-medium 0.35s ease-out; }
.mini-hand.shake-strong { animation: shake-strong 0.45s ease-out; }
.mini-hand.shake-insane { animation: shake-insane 0.55s ease-out; }

@keyframes shake-small {
    0%,100% { transform: translate(0,0); }
    25%     { transform: translate(-2px, 1px); }
    50%     { transform: translate(2px, -1px); }
    75%     { transform: translate(-1px, 2px); }
}
@keyframes shake-medium {
    0%,100% { transform: translate(0,0); }
    20%     { transform: translate(-4px, 2px) rotate(-0.3deg); }
    40%     { transform: translate(4px,-3px) rotate(0.3deg); }
    60%     { transform: translate(-3px, 3px) rotate(-0.2deg); }
    80%     { transform: translate(3px,-2px) rotate(0.2deg); }
}
@keyframes shake-strong {
    0%,100% { transform: translate(0,0); }
    15%     { transform: translate(-6px, 3px) rotate(-0.5deg); }
    30%     { transform: translate(6px,-4px) rotate(0.5deg); }
    45%     { transform: translate(-5px, 5px) rotate(-0.4deg); }
    60%     { transform: translate(5px,-4px) rotate(0.4deg); }
    75%     { transform: translate(-3px, 2px); }
    90%     { transform: translate(2px,-2px); }
}
@keyframes shake-insane {
    0%,100% { transform: translate(0,0); }
    10%     { transform: translate(-9px, 5px) rotate(-0.7deg); }
    20%     { transform: translate(9px,-6px) rotate(0.7deg); }
    30%     { transform: translate(-8px, 7px) rotate(-0.6deg); }
    40%     { transform: translate(8px,-6px) rotate(0.6deg); }
    50%     { transform: translate(-6px, 5px) rotate(-0.4deg); }
    60%     { transform: translate(6px,-5px) rotate(0.4deg); }
    70%     { transform: translate(-4px, 3px); }
    80%     { transform: translate(3px,-3px); }
    90%     { transform: translate(-1px, 1px); }
}

/* Lock icon, if random event Bomb jam hit */
.mini-jam-overlay {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    background-color: rgba(239,68,68,0.25);
    border-radius: 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 28px;
    pointer-events: none;
}
playnplay.chain_reaction / game/2d/ui/gamehud.razor.scss
Game game
root {
    /* Declared so no panel in this tree ever claims the OS pointer -- a hovered
       panel outranks Mouse.CursorType, and one that declares nothing still claims
       the default arrow. GameCursor draws the cursor instead. */
    cursor: none;
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    pointer-events: none;
    overflow: hidden;
}

/* TOP HUD */
.topbar {
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 90px;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-evenly;
    border-bottom: 1px solid rgba(167,139,250,0.15);
    z-index: 10;
    background-color: rgba(18,8,42,0.97);
    animation: topbar-shift 20s ease-in-out infinite alternate;
    overflow: hidden;
    z-index: 11;
}

@keyframes topbar-shift {
    0%   { background-color: rgba(18,8,42,0.97); }
    25%  { background-color: rgba(8,24,48,0.97); }
    50%  { background-color: rgba(24,8,40,0.97); }
    75%  { background-color: rgba(8,18,50,0.97); }
    100% { background-color: rgba(20,6,38,0.97); }
}
.hud-block {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
}
.hud-lbl {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: 0.12em;
    color: rgba(255,255,255,0.30);
    text-transform: uppercase;
}
.hud-val {
    font-size: 32px;
    font-weight: 900;
    color: white;
}
.hud-block.wave  .hud-val { color: #a78bfa; }
.hud-block.score .hud-val { color: #34d399; }
.hud-block.best  .hud-val { color: #fbbf24; }
.hud-sep {
    width: 1px;
    height: 120px;
    background-color: rgba(128,95,128,0.07);
}

/* TIMER */
.timer-track {
    position: absolute;
    top: 90px; left: 0; right: 0;
    height: 14px;
    background-color: rgba(255,255,255,0.06);
    z-index: 10;
}
.timer-fill {
    position: absolute;
    top: 0; left: 0; bottom: 0;
    background-color: #34d399;
    transition: width 0.25s linear, background-color 0.8s ease;
    min-width: 4px;
}

.timer-fill.mid {
    background-color: #a78bfa;           /* half time: purple */
    transition: width 0.25s linear, background-color 0.6s ease;
}
.timer-fill.hurry {
    background-color: #f59e0b;           /* 30%: amber */
    transition: width 0.25s linear, background-color 0.5s ease;
}
.timer-fill.panic {
    background-color: #ef4444;           /* 15%: red */
    transition: width 0.25s linear, background-color 0.3s ease;
    animation: pulse-bar 0.55s ease-in-out infinite alternate;
}
@keyframes pulse-bar {
    from { opacity: 1.0; }
    to   { opacity: 0.5; }
}
/* 
   Three-stage color shift:
   full β†’ hurry  : purple  β†’ amber   (feels warm, attention-grabbing)
   hurry β†’ panic : amber   β†’ red     (urgent)
   panic         : red + pulse animation
*/

/* Challenge sentence β€” between timer and grid */

.challenge-bar {
    position: absolute;
    top: 104px; 
    left: 0;
    right: 0px;
    height: 36px;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 10px;
    background-color: rgba(13,8,32,0.80);
    border-bottom: 1px solid rgba(167,139,250,0.12);
    pointer-events: none;
    z-index: 9;
    transition: background-color 0.4s ease;
}
.challenge-bar.done {
    background-color: rgba(16,42,16,0.85);
    border-bottom-color: rgba(52,211,153,0.25);
}

.ch-icon {
    font-size: 16px;
    color: rgba(167,139,250,0.70);
}
.challenge-bar.done .ch-icon {
    color: #34d399;
}

.ch-label {
    font-size: 20px;
    font-weight: 700;
    color: rgba(255,255,255,0.55);
    letter-spacing: 0.04em;
}
.challenge-bar.done .ch-label {
    color: rgba(52,211,153,0.85);
}

.ch-bonus {
    font-size: 13px;
    font-weight: 800;
    color: rgba(251,191,36,0.60);
    background-color: rgba(251,191,36,0.08);
    border-radius: 5px;
    padding: 2px 7px;
}
.challenge-bar.done .ch-bonus {
    color: rgba(52,211,153,0.80);
    background-color: rgba(52,211,153,0.10);
}

/* CHAIN TRACK β€” above grid, pointer-events none */
.chain-track {
    position: absolute;
    top: 146px;
    left: 0;
    right: 0px;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 8px;
    pointer-events: none;
    z-index: 15;
}
.chain-track-inner {
    background-color: rgba(13,8,32,0.75);
    border-radius: 14px;
    padding: 10px 20px;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 8px;
}
.step {
    background-color: rgba(255,255,255,0.05);
    color: rgba(255,255,255,0.25);
    border-radius: 10px;
    padding: 6px 17px;
    font-size: 15px;
    border: 1.5px solid rgba(255,255,255,0.06);
    transition: all 0.15s;
}
.step.lit {
    background-color: rgba(167,139,250,0.2);
    color: #c4b5fd;
    border-color: #7c3aed;
    transform: scale(1.15);
}
.arr { font-size: 15px; color: rgba(255,255,255,0.15); }

/* SIDEBAR */
.sidebar {
    position: absolute;
    top: 140px; right: 0; bottom: 0;
    width: 260px;
    background-color: rgba(18,8,42,0.97);
    border-left: 2px solid rgba(167,139,250,0.18);
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 16px;
    pointer-events: all;
    z-index: 10;
}
.divider {
    height: 1px;
    background-color: rgba(167,139,250,0.10);
}
.sbl {
    font-size: 12px; font-weight: 800;
    letter-spacing: 0.2em; text-transform: uppercase;
    color: rgba(166,154,219,0.5);
}

/* Legend */
.legend { display: flex; flex-direction: column; gap: 8px; }
.leg    { display: flex; flex-direction: row; align-items: center; gap: 6px; font-size: 14px; color: rgba(255,255,255,0.38); font-weight: 600; }
.dot    { width: 10px; height: 10px; border-radius: 2px; }
.dot.p0    { background-color: #60a5fa; }
.dot.p1    { background-color: #34d399; }
.dot.p2    { background-color: #fb923c; }
.dot.red   { background-color: #f87171; }
.dot.chain { background-color: #c4b5fd; }

/* Controls */
.controls-hint {
    display: flex; flex-direction: column; gap: 6px;
    font-size: 13px; color: rgba(255,255,255,0.30); font-weight: 500;
}

.spacer { flex-grow: 1; }

/* Message bottom bar β€” pointer-events none, no text-align */
.msg {
    position: absolute;
    bottom: 8px;
    left: 12px;
    background-color: rgba(4,1,12,1.0);
    border: 1px solid rgba(167,139,250,0.15);
    border-radius: 8px;
    padding: 6px 14px;
    font-size: 18px;
    color: rgba(255,255,255,0.55);
    pointer-events: none;
    font-weight: 600;
    z-index: 5;
}
/* GAME OVER */
/* ── Three column layout ── */
.ov-bg {
    position: absolute;
    top: 0; left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(4,1,12,1.0);
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 24px;
    pointer-events: all;
    z-index: 50;
    overflow: hidden;
}

.ov-side-panel {
    display: flex;
    flex-direction: column;
    gap: 16px;
    width: 220px;
    pointer-events: none;
    z-index: 3;
    max-height: 92vh;
    overflow-y: auto;
}

.ov-side-card {
    position: relative; /* needed for ::before */
    background-color: rgba(8,3,22,0.90);
    border: 1px solid rgba(139,92,246,0.18);
    border-radius: 16px;
    padding: 16px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    overflow: hidden;
}

.ov-side-card.journey {
    gap: 4px;
}
/* Streak card β€” orange glow line */
.ov-side-card.streak::before {
    background-color: rgba(251,146,60,0.55);
}

.ov-side-card::before {
    content: "";
    position: absolute;
    top: -1px; left: 20%; right: 20%;
    height: 1px;
    background-color: rgba(139,92,246,0.40);
    border-radius: 50%;
}

.ov-side-card.streak {
    border-color: rgba(251,146,60,0.30);
    background-color: rgba(251,146,60,0.05);
}

.ov-side-card-title {
    font-size: 14px;
    font-weight: 900;
    color: rgb(255, 214, 161);
    letter-spacing: 0.22em;
    text-transform: uppercase;
    width: 100%;
    justify-content: center;
       margin-bottom: 8px;
       gap: 3px;
}

/* ── Personal best bar ── */
.ov-pb-bar-wrap { width: 100%; }

.ov-pb-bar-track {
    width: 100%;
    height: 6px;
    background-color: rgba(255,255,255,0.06);
    border-radius: 3px;
    position: relative;
}

.ov-pb-bar-fill {
    position: absolute;
    top: 0; left: 0; bottom: 0;
    background-color: #a78bfa;
    border-radius: 3px;
    min-width: 4px;
}

.ov-pb-marker {
    position: absolute;
    top: -3px;
    width: 2px;
    height: 12px;
    background-color: rgba(251,191,36,0.80);
    border-radius: 1px;
}

.ov-pb-scores {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    width: 100%;
}

.ov-pb-score-current {
    font-size: 22px;
    font-weight: 900;
    color: #a78bfa;
}

.ov-pb-score-best {
    font-size: 11px;
    font-weight: 600;
    color: rgba(255,255,255,0.30);
}

.ov-pb-newbest {
    font-size: 12px;
    font-weight: 900;
    color: #fbbf24;
    background-color: rgba(251,191,36,0.10);
    border: 1px solid rgba(251,191,36,0.25);
    border-radius: 8px;
    padding: 3px 10px;
}

/* Game over background Squares */
@keyframes sq-float-1 {
    0%   { margin-top: 0px;    margin-left: 0px; }
    25%  { margin-top: -14px;  margin-left: 8px; }
    50%  { margin-top: -6px;   margin-left: -10px; }
    75%  { margin-top: 10px;   margin-left: 4px; }
    100% { margin-top: 0px;    margin-left: 0px; }
}

@keyframes sq-float-2 {
    0%   { margin-top: 0px;   margin-left: 0px; }
    30%  { margin-top: 12px;  margin-left: -8px; }
    60%  { margin-top: -8px;  margin-left: 12px; }
    100% { margin-top: 0px;   margin-left: 0px; }
}

@keyframes sq-pulse {
    0%,100% { opacity: 0.4; }
    50%     { opacity: 1.0; }
}

.ov-bg-squares {
    position: absolute;
    top: 0; left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 1;
}

.ov-bg-sq {
    position: absolute;
    border: 1.5px solid;
}

@keyframes sq-drift-a {
    0%   { margin-top: 0px;   margin-left: 0px;   opacity: 0.35; }
    20%  { margin-top: -12px; margin-left: 8px;   opacity: 0.80; }
    40%  { margin-top: -20px; margin-left: -6px;  opacity: 0.50; }
    60%  { margin-top: -8px;  margin-left: -14px; opacity: 0.90; }
    80%  { margin-top: 6px;   margin-left: 4px;   opacity: 0.45; }
    100% { margin-top: 0px;   margin-left: 0px;   opacity: 0.35; }
}

@keyframes sq-drift-b {
    0%   { margin-top: 0px;  margin-left: 0px;  opacity: 0.25; }
    25%  { margin-top: 14px; margin-left: -10px; opacity: 0.70; }
    50%  { margin-top: 6px;  margin-left: 16px;  opacity: 0.90; }
    75%  { margin-top: -10px; margin-left: 8px;  opacity: 0.40; }
    100% { margin-top: 0px;  margin-left: 0px;   opacity: 0.25; }
}

.ov-bg-sq.sq-a {
    animation-name: sq-drift-a;
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
}

.ov-bg-sq.sq-b {
    animation-name: sq-drift-b;
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
}

/* Alternate float pattern on even squares */
.ov-bg-sq:nth-child(even) {
    animation-name: sq-float-2, sq-pulse;
}

/* ── Journey ── */
.ov-journey-wave {
    font-size: 32px;
    font-weight: 900;
    color: white;
    gap: 3px;
}

.ov-journey-sub {
    font-size: 12px;
    font-weight: 600;
    color: rgba(167, 255, 179, 0.719);
    word-spacing: 1px;
    width: 100%;
    display: flex;
    flex-direction: row;
    justify-content: space-between;  /* ← pushes label left, value right */
}

.ov-journey-tag {
    font-size: 10px;
    font-weight: 800;
    color: rgba(255, 255, 255, 0.562);
    background-color: rgba(255,255,255,0.04);
    border-radius: 6px;
    padding: 3px 10px;
}

.ov-journey-tag.good {
    color: #34d399;
    background-color: rgba(52,211,153,0.08);
}

/* ── Streak ── */
.ov-streak-count {
    font-size: 48px;
    font-weight: 900;
    color: #fb923c;
}

.ov-streak-label {
    font-size: 11px;
    font-weight: 700;
    color: rgba(255, 199, 125, 0.404);
}

/* ── Next unlock ── */
.ov-unlock-icon { font-size: 32px; }

.ov-unlock-name {
    font-size: 13px;
    font-weight: 800;
    color: rgba(255,255,255,0.75);
    width: 100%;
    text-align: center;
    justify-content: center;
    margin-bottom: 2px;
}

.ov-unlock-cost {
    font-size: 12px;
    flex-direction: row;
    font-weight: 700;
    color: rgba(129,140,248,0.70);
    width: 100%;
    justify-content: center;
    text-align: center;
    gap: 3px; /* adds space b/w elements */
}

.ov-unlock-ready {
    font-size: 12px;
    font-weight: 900;
    color: #34d399;
}

.ov-unlock-bar-track {
    width: 100%;
    height: 4px;
    background-color: rgba(255,255,255,0.06);
    border-radius: 2px;
    position: relative;
    margin-top: 4px;
}

.ov-unlock-bar-fill {
    position: absolute;
    top: 0; left: 0; bottom: 0;
    background-color: #818cf8;
    border-radius: 2px;
    min-width: 4px;
}

.ov-particles {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    pointer-events: none;
    overflow: hidden;
    z-index: 2;
}
.ov-particle {
    position: absolute;
    bottom: -20px;
    opacity: 0;
    animation-name: particle-rise;
    animation-timing-function: ease-in;
    animation-iteration-count: infinite;
}
@keyframes particle-rise {
    0%   { opacity: 0;   transform: translateY(0px); }
    15%  { opacity: 1; }
    80%  { opacity: 0.7; transform: translateY(-65vh); }
    100% { opacity: 0;   transform: translateY(-105vh); }
}

.ov-box {
    position: relative;
    background-color: rgba(8,3,22,0.98);
    border: 1px solid rgba(139,92,246,0.25);
    border-radius: 24px;
    padding: 2rem 2.2rem 2.2rem 2.2rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    width: 600px;
    max-height: 92vh;
    overflow-y: auto;
    z-index: 3;
    box-shadow: 0px 0px 0px 1px rgba(139,92,246,0.08),
                0px 2px 0px rgba(139,92,246,0.15),
                0px 24px 60px rgba(0s,0,0,0.80),
                inset 0px 1px 0px rgba(255,255,255,0.04);
}

/* Subtle top glow on the box */
.ov-box::before {
    content: "";
    position: absolute;
    top: -1px; left: 15%; right: 15%;
    height: 1px;
    background-color: rgba(167,139,250,0.50);
    border-radius: 50%;
}

.ov-wave-big {
    font-size: 64px;
    font-weight: 900;
    color: #a78bfa;
    letter-spacing: 0.06em;
    animation: ov-section-in 0.6s cubic-bezier(0.22,1,0.36,1) 0.3s both;
}

.ov-stats {
    display: flex;
    flex-direction: row;
    gap: 8px;
    flex-shrink: 0;
    width: 100%;
    justify-content: center;
}

.ov-stat {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    background-color: rgba(255,255,255,0.02);
    border-radius: 14px;
    padding: 16px 8px 12px 8px;
    position: relative;
    overflow: hidden;
}

.ov-stat::before {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 2px;
}

.ov-stat-icon { font-size: 18px; opacity: 0.70; }

.ov-stat-val {
    font-size: 36px;
    font-weight: 900;
    letter-spacing: -0.03em;
    line-height: 1;
}

.ov-stat-lbl {
    font-size: 11px;
    font-weight: 700;
    color: rgba(255, 255, 255, 0.575);
    text-transform: uppercase;
    letter-spacing: 0.14em;
}

.ov-stat:nth-child(1) {
    border: 1px solid rgba(52,211,153,0.18);
    background-color: rgba(52,211,153,0.06);
}
.ov-stat:nth-child(2) {
    border: 1px solid rgba(167,139,250,0.18);
    background-color: rgba(167,139,250,0.06);
}
.ov-stat:nth-child(3) {
    border: 1px solid rgba(251,191,36,0.18);
    background-color: rgba(251,191,36,0.06);
}

/* Color accent per stat β€” target each by position */
.ov-stat:nth-child(1)::before { background-color: #34d399; }
.ov-stat:nth-child(2)::before { background-color: #a78bfa; }
.ov-stat:nth-child(3)::before { background-color: #fbbf24; }

.ov-stat:nth-child(1) { border: 1px solid rgba(52,211,153,0.12); }
.ov-stat:nth-child(2) { border: 1px solid rgba(167,139,250,0.12); }
.ov-stat:nth-child(3) { border: 1px solid rgba(251,191,36,0.12); }

/* ── Stat label colors ── */
.ov-stat:nth-child(1) .ov-stat-val { color: #6ee7b7; }
.ov-stat:nth-child(2) .ov-stat-val { color: #d8b4fe; }
.ov-stat:nth-child(3) .ov-stat-val { color: #fde68a; }


/* Background orbs still game over (ov)*/
@keyframes orb-drift-1 {
    0%   { margin-left: 0px;   margin-top: 0px; }
    33%  { margin-left: 18px;  margin-top: -12px; }
    66%  { margin-left: -10px; margin-top: 20px; }
    100% { margin-left: 0px;   margin-top: 0px; }
}

@keyframes orb-drift-2 {
    0%   { margin-left: 0px;   margin-top: 0px; }
    40%  { margin-left: -20px; margin-top: 14px; }
    70%  { margin-left: 12px;  margin-top: -8px; }
    100% { margin-left: 0px;   margin-top: 0px; }
}

@keyframes orb-drift-3 {
    0%   { margin-left: 0px;  margin-top: 0px; }
    30%  { margin-left: 14px; margin-top: 18px; }
    60%  { margin-left: -8px; margin-top: -14px; }
    100% { margin-left: 0px;  margin-top: 0px; }
}

.ov-orb {
    position: absolute;
    border-radius: 50%;
    pointer-events: none;
    z-index: 0;
}

.ov-orb-1 {
    width: 280px;
    height: 280px;
    top: -60px;
    left: -80px;
    background-color: rgba(109,40,217,0.06);
    animation: orb-drift-1 12s ease-in-out infinite;
}

.ov-orb-2 {
    width: 200px;
    height: 200px;
    bottom: 40px;
    right: -60px;
    background-color: rgba(99,102,241,0.05);
    animation: orb-drift-2 16s ease-in-out infinite;
}

.ov-orb-3 {
    width: 150px;
    height: 150px;
    top: 40%;
    left: 30%;
    background-color: rgba(244,114,182,0.03);
    animation: orb-drift-3 20s ease-in-out infinite;
}

/* Game over - secret */
.ov-secret-title {
    font-size: 13px;
    font-weight: 800;
    color: rgba(244,114,182,0.70);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    animation: ov-in 0.5s ease-out 1.6s both;
}
.ov-secrets {
    display: flex;
    flex-direction: row;
    gap: 10px;
    flex-wrap: wrap;
    justify-content: center;
}
.ov-secret {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    background-color: rgba(244,114,182,0.08);
    border: 1px solid rgba(244,114,182,0.25);
    border-radius: 14px;
    padding: 14px 18px;
    min-width: 80px;
    overflow: hidden;
}
.ov-secret.locked {
    background-color: rgba(255,255,255,0.03);
    border-color: rgba(255,255,255,0.08);
    animation: chest-idle 2s ease-in-out infinite;
}
.ov-secret.rare {
    background-color: rgba(251,191,36,0.10);
    border-color: rgba(251,191,36,0.45);
}
.ov-secret.pop      { animation: chest-pop      0.7s cubic-bezier(0.22,1,0.36,1) forwards; }
.ov-secret.rare.pop { animation: chest-pop-rare 1.0s cubic-bezier(0.22,1,0.36,1) forwards; }
.ov-secret-icon { font-size: 28px; color: white; }
.ov-secret-lbl  { font-size: 13px; font-weight: 800; color: rgba(255,255,255,0.80); }
.ov-secret.rare .ov-secret-lbl { color: #fbbf24; }
.ov-secret-shine {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    background: linear-gradient(105deg, transparent 30%, rgba(255,255,255,0.35) 50%, transparent 70%);
    animation: shine-sweep 0.6s ease-out 0.1s forwards;
    pointer-events: none;
}

.ov-section-enter {
    animation: ov-section-in 0.45s cubic-bezier(0.22,1,0.36,1) forwards;
}

@keyframes ov-section-in {
    0%   { margin-top: 24px; }
    100% { margin-top: 0px; }
}

/* Clickable secret chests during Game Over */
.ov-secret.clickable {
    cursor: none;
    border-color: rgba(167,139,250,0.55);
    background-color: rgba(167,139,250,0.10);
    animation: chest-idle 1.0s ease-in-out infinite;
    pointer-events: all;
}

.ov-secret.clickable:hover {
    background-color: rgba(167,139,250,0.20);
    border-color: rgba(167,139,250,0.85);
}

@keyframes chest-idle {
    0%,100% { transform: rotate(0deg); }
    25%     { transform: rotate(-2deg) scale(1.02); }
    75%     { transform: rotate(2deg)  scale(1.02); }
}
@keyframes chest-pop {
    0%   { transform: scale(1.0); }
    20%  { transform: scale(0.88); }
    55%  { transform: scale(1.22); }
    75%  { transform: scale(0.96); }
    100% { transform: scale(1.0); }
}
@keyframes chest-pop-rare {
    0%   { transform: scale(1.0)  rotate(0deg); }
    15%  { transform: scale(0.82) rotate(-4deg); }
    45%  { transform: scale(1.35) rotate(3deg); }
    65%  { transform: scale(0.93) rotate(-1deg); }
    82%  { transform: scale(1.06) rotate(0deg); }
    100% { transform: scale(1.0)  rotate(0deg); }
}
@keyframes shine-sweep {
    0%   { transform: translateX(-200%); }
    100% { transform: translateX(300%); }
}

/* Secret section - when clickicking */
.ov-secret-section {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
}

.ov-secret-eyebrow {
    font-size: 11px;
    font-weight: 800;
    color: rgba(244,114,182,0.70);
    letter-spacing: 0.22em;
    text-transform: uppercase;
}

.ov-secret-hint {
    font-size: 13px;
    font-weight: 600;
    color: rgba(255,255,255,0.35);
}

/* ── Chest cards ── */

.ov-chest-cards {
    display: flex;
    flex-direction: row;
    gap: 16px;
    justify-content: center;
}

.ov-chest-card {
    width: 130px;
    min-height: 150px; /* was height: 150px */
    border-radius: 16px;
    border: 1.5px solid rgba(167,139,250,0.15);
    background-color: rgba(13,6,32,0.90);
    position: relative;
    overflow: visible;
    cursor: none;
    box-shadow: 0px 4px 0px rgba(0,0,0,0.40);
}

.ov-chest-card.ready {
    border-color: rgba(167,139,250,0.55);
    background-color: rgba(60,20,140,0.25);
    cursor: none;
    pointer-events: all;
    animation: chest-card-ready 1.4s ease-in-out infinite alternate;
    box-shadow: 0px 4px 0px rgba(0,0,0,0.40),
                0px 0px 20px rgba(167,139,250,0.15);
}

.ov-chest-card.ready:hover {
    border-color: rgba(167,139,250,0.90);
    background-color: rgba(109,40,217,0.30);
}

.ov-chest-card.waiting {
    border-color: rgba(255,255,255,0.06);
    background-color: rgba(255,255,255,0.02);
    box-shadow: none;
}

.ov-chest-card.revealed {
    border-color: rgba(129,140,248,0.35);
    background-color: rgba(99,102,241,0.08);
    box-shadow: 0px 4px 0px rgba(0,0,0,0.30),
                0px 0px 16px rgba(129,140,248,0.10);
}

.ov-chest-card.revealed.rare {
    border-color: rgba(251,191,36,0.50);
    background-color: rgba(251,191,36,0.08);
    box-shadow: 0px 4px 0px rgba(0,0,0,0.30),
                0px 0px 24px rgba(251,191,36,0.12);
}

.ov-chest-card.pop-in {
    animation: chest-card-pop 0.7s cubic-bezier(0.22,1,0.36,1) forwards;
}

.ov-chest-card-inner {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    pointer-events: none;
}

.ov-chest-emoji {
    font-size: 48px;
    animation: chest-card-idle 2.2s ease-in-out infinite alternate;
}

.ov-chest-card.waiting .ov-chest-emoji {
    opacity: 0.25;
    animation: none;
}

.ov-chest-tap {
    font-size: 10px;
    font-weight: 900;
    color: white;
    background-color: rgba(167,139,250,0.35);
    border: 1px solid rgba(167,139,250,0.50);
    border-radius: 6px;
    padding: 3px 10px;
    letter-spacing: 0.12em;
}

.ov-chest-tap.locked {
    background-color: rgba(255,255,255,0.05);
    border-color: rgba(255,255,255,0.08);
    color: rgba(255,255,255,0.20);
}

.ov-chest-glow {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    border-radius: 14px;
    animation: chest-glow-pulse 1.2s ease-in-out infinite alternate;
    pointer-events: none;
}

.ov-chest-card.ready {
    border-color: rgba(167,139,250,0.60);
    background-color: rgba(109,40,217,0.18);
    cursor: none;
    pointer-events: all;
    animation: chest-card-ready 1.2s ease-in-out infinite alternate;
}

.ov-chest-reward-icon {
    font-size: 40px;
}

.ov-chest-reward-lbl {
    font-size: 11px;
    font-weight: 800;
    color: rgba(255,255,255,0.75);
    padding: 0px 8px;
}

/* rare badge */
.ov-chest-revealed-icon {
    font-size: 40px;
}

.ov-chest-revealed-info {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
}

.ov-chest-revealed-lbl {
    font-size: 13px;
    font-weight: 900;
    color: white;
    padding: 0px 4px;
}

.ov-chest-rare-badge {
    font-size: 9px;
    font-weight: 900;
    color: #fbbf24;
    background-color: rgba(251,191,36,0.10);
    border: 1px solid rgba(251,191,36,0.30);
    border-radius: 20px;
    padding: 2px 8px;
    letter-spacing: 0.10em;
}

.ov-confetti-bit {
    position: absolute;
    top: -20px;
    width: 8px;
    height: 8px;
    border-radius: 2px;
    animation-name: confetti-fly;
    animation-timing-function: ease-out;
    animation-fill-mode: forwards;
    pointer-events: none;
    z-index: 10;
}

@keyframes confetti-fly {
    0%   { opacity: 1; transform: translateY(0px) rotate(0deg) scale(1.2); }
    30%  { opacity: 1; }
    100% { opacity: 0; transform: translateY(-140px) rotate(520deg) scale(0.4); }
}

@keyframes chest-card-ready {
    0%   { transform: translateY(0px) scale(1.0); }
    100% { transform: translateY(-5px) scale(1.02); }
}

@keyframes chest-card-idle {
    0%   { transform: translateY(0px) rotate(-1.5deg); }
    100% { transform: translateY(-5px) rotate(1.5deg); }
}

@keyframes chest-card-pop {
    0%   { transform: scale(1.0); }
    20%  { transform: scale(0.85); }
    55%  { transform: scale(1.25); }
    75%  { transform: scale(0.96); }
    100% { transform: scale(1.0); }
}

@keyframes chest-glow-pulse {
    0%   { opacity: 0.4; }
    100% { opacity: 1.0; }
}

@keyframes chest-shine-sweep {
    0%   { transform: translateX(-100%); }
    40%  { transform: translateX(250%); }
    100% { transform: translateX(250%); }
}

@keyframes confetti-fly {
    0%   { opacity: 1; transform: translateY(0px) rotate(0deg) scale(1.0); }
    60%  { opacity: 1; }
    100% { opacity: 0; transform: translateY(-90px) rotate(380deg) scale(0.6); }
}

/* Frags section */
.ov-frag-section {
    width: 100%;
    display: flex;
     flex-shrink: 0;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    background-color: rgba(99,102,241,0.05);
    border: 1px solid rgba(99,102,241,0.18);
    border-radius: 18px;
    padding: 20px 22px;
    position: relative;
    overflow: hidden;
}

/* Subtle shimmer across the top */
.ov-frag-section::before {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 1px;
    background-color: rgba(129,140,248,0.35);
}

.ov-frag-breakdown {
    display: flex;
    flex-direction: column;
    gap: 5px;
    width: 100%;
}

.ov-frag-line {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    padding: 5px 10px;
    background-color: rgba(255,255,255,0.025);
    border-radius: 8px;
    border-left: 2px solid rgba(129,140,248,0.20);
}

.ov-frag-line-lbl {
    font-size: 12px;
    font-weight: 600;
    color: rgba(255,255,255,0.35);
}

.ov-frag-line-val {
    font-size: 13px;
    font-weight: 900;
    color: #818cf8;
}

.ov-frag-label {
    font-size: 10px;
    font-weight: 900;
    color: rgba(129,140,248,0.55);
    letter-spacing: 0.22em;
    text-transform: uppercase;
}

.ov-frag-val {
    font-size: 52px;
    font-weight: 900;
    color: #818cf8;
    letter-spacing: -0.02em;
    line-height: 1;
}

.ov-frag-total {
    font-size: 12px;
    font-weight: 700;
    color: rgba(129,140,248,0.50);
    border-top: 1px solid rgba(129,140,248,0.10);
    padding-top: 8px;
    width: 100%;
    text-align: center;
}

.ov-frag-hint {
    font-size: 11px;
    color: rgba(255,255,255,0.22);
    font-weight: 600;
    letter-spacing: 0.04em;
}

/**/

.ov-score-section {
    display: flex;
    flex-shrink: 0;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    animation: ov-score-appear 0.6s ease-out forwards;
}

@keyframes ov-score-appear {
    0%   { opacity: 0; }
    100% { opacity: 1; }
}

.ov-score-label { font-size: 12px; font-weight: 700; color: rgba(255,255,255,0.35); letter-spacing: 0.18em; text-transform: uppercase; }
.ov-score       { font-size: 62px; font-weight: 900; color: #a78bfa; }
.ov-score.gold  { color: #fbbf24; }
.ov-newbest-badge {
    font-size: 15px;
    font-weight: 900;
    color: #fbbf24;
    background-color: rgba(251,191,36,0.12);
    border: 1.5px solid rgba(251,191,36,0.40);
    border-radius: 8px;
    padding: 4px 14px;
    letter-spacing: 0.12em;
    animation: badge-slam 0.5s cubic-bezier(0.22,1,0.36,1) 0.6s both;
}
@keyframes badge-slam {
    0%   { opacity: 0; transform: scale(0.3); }
    60%  { transform: scale(1.15); }
    100% { opacity: 1; transform: scale(1.0); }
}
.ov-btns {
    display: flex;
    flex-direction: row;
    gap: 12px;
    flex-shrink: 0; /* never let the buttons get squeezed */
}

/* ── Play Again button sweep ── */
.ov-btn-wrap {
    position: relative;
    overflow: hidden;
    background-color: #3aed3a;
    border: 1.5px solid #8bfa9a;
    border-radius: 12px;
    color: white;
    font-size: 30px;
    font-weight: 800;
    padding: 15px 40px;
    cursor: none;
    pointer-events: all;
    animation: btn-breathe 2.2s ease-in-out infinite alternate;
    display: flex;
    align-items: center;
    justify-content: center;
}

@keyframes btn-breathe {
    0%   { transform: scale(1.0); }
    100% { transform: scale(1.05); }
}

.ov-btn-shine {
    position: absolute;
    top: 0; bottom: 0;
    left: -80%;
    width: 40%;
    background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.25) 50%, transparent 100%);
    animation: btn-shine-sweep 3.0s ease-in-out infinite;
    pointer-events: none;
}

@keyframes btn-shine-sweep {
    0%   { left: -80%; }
    45%  { left: 140%; }
    100% { left: 140%; }
}

@keyframes ov-in {
    0%   { opacity: 0; transform: translateY(12px) scale(0.97); }
    100% { opacity: 1; transform: translateY(0) scale(1.0); }
}

.ov-reveal {
    animation: ov-in 0.4s ease-out both;
}

/* Main Menu Button */
.ov-btn-menu {
    background-color: rgba(255,255,255,0.04);
    border: 1px solid rgba(255,255,255,0.10);
    border-radius: 12px;
    color: rgba(255,255,255,0.40);
    font-size: 14px;
    font-weight: 700;
    padding: 15px 20px;
    cursor: none;
    display: flex;
    align-items: center;
    justify-content: center;
}
.ov-btn-menu:hover {
    background-color: rgba(255,255,255,0.08);
    color: rgba(255,255,255,0.65);
}

/* -------------------- Modifier strip -----------------------
* This shows the equipped modifiers as a vertical strip in the sidebar
* Each item has its icon, name, and a tooltip on hover showing the full description. 
* The green tint matches the "active" color language used in the shop, 
* creating visual consistency between menu and game.

* Players can now glance at the sidebar mid-run and see exactly what's buffing them
* Combined with the boss multiplier suppression warning I added, 
* the player always knows what's affecting their score
*/
.mod-strip {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-bottom: 12px;
}

.mod-strip-item {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    background-color: rgba(52,211,153,0.06);
    border: 1px solid rgba(52,211,153,0.20);
    border-radius: 8px;
    transition: background-color 0.2s ease;
}

.mod-strip-item:hover {
    background-color: rgba(52,211,153,0.12);
}

.mod-strip-icon {
    font-size: 18px;
    flex-shrink: 0;
}

.mod-strip-name {
    font-size: 11px;
    font-weight: 700;
    color: rgba(255,255,255,0.75);
    letter-spacing: 0.04em;
}

/* ── Game over reason ── */
.ov-reason {
    animation: ov-section-in 0.5s cubic-bezier(0.22,1,0.36,1) 0.5s both;
    font-size: 13px;
    font-weight: 700;
    color: rgba(255,255,255,0.55);
    padding: 8px 16px;
    background-color: rgba(255,255,255,0.04);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 10px;
    width: 100%;
    justify-content: center;
}

/* ---------------- SCREEN SHAKE ---------------- */
.hud-overlay {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    pointer-events: none;
    z-index: 5;
}

.hud-overlay.shake-small  { animation: shake-small  0.25s ease-out; }
.hud-overlay.shake-medium { animation: shake-medium 0.35s ease-out; }
.hud-overlay.shake-strong { animation: shake-strong 0.45s ease-out; }
.hud-overlay.shake-insane { animation: shake-insane 0.55s ease-out; }

.mini-hand.shake-small  { animation: shake-small  0.25s ease-out; }
.mini-hand.shake-medium { animation: shake-medium 0.35s ease-out; }
.mini-hand.shake-strong { animation: shake-strong 0.45s ease-out; }
.mini-hand.shake-insane { animation: shake-insane 0.55s ease-out; }

@keyframes shake-small {
    0%,100% { transform: translate(0,0); }
    25%     { transform: translate(-2px, 1px); }
    50%     { transform: translate(2px, -1px); }
    75%     { transform: translate(-1px, 2px); }
}
@keyframes shake-medium {
    0%,100% { transform: translate(0,0); }
    20%     { transform: translate(-4px, 2px) rotate(-0.3deg); }
    40%     { transform: translate(4px,-3px) rotate(0.3deg); }
    60%     { transform: translate(-3px, 3px) rotate(-0.2deg); }
    80%     { transform: translate(3px,-2px) rotate(0.2deg); }
}
@keyframes shake-strong {
    0%,100% { transform: translate(0,0); }
    15%     { transform: translate(-6px, 3px) rotate(-0.5deg); }
    30%     { transform: translate(6px,-4px) rotate(0.5deg); }
    45%     { transform: translate(-5px, 5px) rotate(-0.4deg); }
    60%     { transform: translate(5px,-4px) rotate(0.4deg); }
    75%     { transform: translate(-3px, 2px); }
    90%     { transform: translate(2px,-2px); }
}
@keyframes shake-insane {
    0%,100% { transform: translate(0,0); }
    10%     { transform: translate(-9px, 5px) rotate(-0.7deg); }
    20%     { transform: translate(9px,-6px) rotate(0.7deg); }
    30%     { transform: translate(-8px, 7px) rotate(-0.6deg); }
    40%     { transform: translate(8px,-6px) rotate(0.6deg); }
    50%     { transform: translate(-6px, 5px) rotate(-0.4deg); }
    60%     { transform: translate(6px,-5px) rotate(0.4deg); }
    70%     { transform: translate(-4px, 3px); }
    80%     { transform: translate(3px,-3px); }
    90%     { transform: translate(-1px, 1px); }
}

/* ────────────────────────────────────────────────────────────────── */
/* ────── Consumable strip β€” horizontal bar below challenge bar ───── */
/* ────────────────────────────────────────────────────────────────── */
.consumable-strip {
    position: absolute;
    bottom: 120px;         /* sits above the bomb wheel */
    left: 0;
    right: 150px;     /* leave room for sidebar */
    display: flex;
    flex-direction: row;
    justify-content: center;
    gap: 8px;
    pointer-events: all;
    z-index: 20;
}

.consumable-slot {
    position: relative;
    left: 70px;
    width: 54px;
    height: 54px;
    border-radius: 10px;
    border: 1.5px solid rgba(255,255,255,0.10);
    background-color: rgba(255,255,255,0.04);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    cursor: none;
    position: relative;
}

.consumable-slot.available {
    border-color: rgba(167,139,250,0.50);
    background-color: rgba(109,40,217,0.15);
    cursor: none;
}

.consumable-slot.available:hover {
    background-color: rgba(109,40,217,0.30);
    border-color: rgba(167,139,250,0.80);
}

.consumable-slot.empty {
    opacity: 0.25;
    cursor: none;
}

.consumable-icon {
    font-size: 22px;
    pointer-events: none;
}

/* Count badge β€” bottom right corner of slot */
.consumable-count {
    position: absolute;
    bottom: 2px;
    right: 4px;
    font-size: 10px;
    font-weight: 900;
    color: rgba(167,139,250,0.95);
    pointer-events: none;
}

/* ── Screen Flash ── */
.screen-flash {
    position: absolute;
    top: 0; left: 0;
    width: 2200px;
    height: 1400px;
    pointer-events: none;
    z-index: 100;
}

.flash-text {
    position: absolute;
    top: 485px;        /* roughly vertical center of 1080p */
    left: 1055px;       /* roughly horizontal center minus half text width */
    font-size: 72px;
    font-weight: 900;
    color: rgba(255,255,255,0.90);
    pointer-events: none;
    animation: flash-text-pop 1.2s ease-out forwards;
        text-shadow:
        -3px -3px 0 #000,
         3px -3px 0 #000,
        -3px  3px 0 #000,
         3px  3px 0 #000,
        -4px  0   0 #000,
         4px  0   0 #000,
         0   -4px  0 #000,
         0    4px  0 #000;
	z-index: 10;
}

@keyframes flash-text-pop {
    0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.6); }
    12%  { opacity: 1; transform: translate(-50%, -50%) scale(1.05); }
    70%  { opacity: 1; transform: translate(-50%, -50%) scale(1.0); }
    100% { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }
}

@keyframes flash-fade {
    0%   { opacity: 0; }
    15%  { opacity: 1; }
    100% { opacity: 0; }
}

@keyframes flash-boss-spawn {
    0%   { opacity: 0; }
    15%  { opacity: 1; }
    100% { opacity: 0; }
}

@keyframes flash-wave {
    0%   { opacity: 0; }
    10%  { opacity: 1; }
    60%  { opacity: 1; }
    100% { opacity: 0; }
}

.screen-flash.flash-boss-spawn {
    background-color: rgba(220, 30, 30, 0.50);
    animation: flash-boss-spawn 2.5s ease-out forwards;
}

.screen-flash.flash-boss-death {
    background-color: rgba(251,191,36,0.55);
    animation: flash-fade 0.6s ease-out forwards;
}

.screen-flash.flash-wave-start {
    background-color: rgba(138, 92, 246, 0.082);  /* was 0.18 β€” lighter */
    animation: flash-wave .9s ease-out forwards;  /* was 2.5s β€” faster */
}

.screen-flash.flash-new-best {
    background-color: rgba(52,211,153,0.45);
    animation: flash-fade 1.0s ease-out forwards;
}

.screen-flash.flash-game-over {
    background-color: rgba(180,20,20,0.60);
    animation: flash-fade 0.8s ease-out forwards;
}

/* ------------------------------------------- */
/* -------------- Pet Companion -------------- */
/* ------------------------------------------- */

.hud-pet-wrap {
    position: absolute;
    bottom: 55px;
    left: 15px;
    width: 220px;
    height: 870px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    padding: 14px 14px 0px 14px;
    z-index: 6;
}

.hud-pet {
    font-size: 110px;
    animation: hud-pet-idle 2.5s ease-in-out infinite alternate;
    opacity: 0.8;
    margin-bottom: 10px;
    margin-left: 0px;
}
.hud-pet image {
    width: 110px;
    height: 110px;
}

.hud-pet-egg-icon
{
    width: 110px;
    height: 110px;
}

/* Top section β€” modifiers + curse */
.hud-pet-top {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 100%;
}

/* Bottom section β€” pet + hearts, pinned to bottom */
.hud-pet-bottom {
    margin-top: auto;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    width: 100%;
    font-size: 64px; 

}

@keyframes pet-bob {
    from { transform: translateY(0px); }
    to   { transform: translateY(-8px); }
}

.pet-bob {
    animation: pet-bob 1.4s ease-in-out infinite alternate;
}

.hud-pet-egg {
    width: 120px;
    height: 120px;
}

.hud-pet-bottom image {
    width: 134px;
    height: 134px;
    margin-left: -10px;
}

.pet-sbl {
    font-size: 10px;
    font-weight: 800;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: rgba(166,154,219,0.5);
}

.hud-pet-card {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    background-color: rgba(18,8,42,0.92);
    border: 1px solid rgba(167,139,250,0.18);
    border-radius: 14px 14px 0px 0px;
    z-index: -1;
}

@keyframes hud-pet-idle {
    0%   { transform: translateY(0px) scale(1.0); }
    100% { transform: translateY(-6px) scale(1.05); }
}

/* -------------------------------------------------- */
/* -------------- Hearts/Lives Sidebar -------------- */
/* -------------------------------------------------- */

.hud-pet-lives {
    position: absolute;
    right: 0px;
    left: 150px;
    bottom: 10px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.hud-pet-heart {
    font-size: 40px;
}

.hud-pet-heart.empty {
    opacity: 0.20;
}

/* -------------------------------------------------- */
/* -------------- BACKGROUND (grid-like) ------------ */
/* -------------------------------------------------- */

.game-bg-grid {
    position: absolute;
    top: 90px; left: 0; right: 0; bottom: 0;
    pointer-events: none;
    z-index: 0;
    overflow: hidden;
}

.game-bg-cell {
    position: absolute;
    width: 232px;
    height: 232px;
    border-radius: 10px;
    border: 2px solid rgba(66, 54, 75, 0.04);
    z-index: 0;
}

.game-bg-cell.gbg-idle  { /* inline style from WaveGridColor β€” already very dark hex */ }
.game-bg-cell.gbg-expl  { background-color: rgba(10,4,24,0.95); border-color: rgba(LoadPetLbLoadPetLb251,146,60,0.22); }
.game-bg-cell.gbg-expl2 { background-color: rgba(10,4,24,0.95); border-color: rgba(251,191,36,0.22); }
.game-bg-cell.gbg-gold  { background-color: rgba(10,4,24,0.95); border-color: rgba(251,191,36,0.16); }
.game-bg-cell.gbg-red   { background-color: rgba(10,4,24,0.95); border-color: rgba(248,113,113,0.16); }
.game-bg-cell.gbg-chain { background-color: rgba(10,4,24,0.95); border-color: rgba(196,181,253,0.14); }

/* SIDEBAR BUTTON (info) */
.sidebar-toggle {
    position: absolute;
    top: 155px;
    right: 12px;
    width: 44px;
    height: 44px;
    border-radius: 50px;
    background-color: rgba(180,30,30,0.85);
    border: 2px solid rgba(255,100,100,0.60);
    color: white;
    font-size: 20px;
    font-weight: 900;
    pointer-events: all;
    z-index: 20;
    cursor: none;
    box-shadow: 2px 2px 0px rgba(0,0,0,0.60);
    display: flex;
    align-items: center;
    justify-content: center;
}

.sidebar-toggle:hover {
    background-color: rgba(220,40,40,0.95);
    border-color: rgba(255,140,140,0.80);
}

/**/
.pet-mod-strip {
    display: flex;
    flex-direction: row;
    gap: 6px;
    margin-top: 8px;
}

.pet-mod-item {
    width: 32px;
    height: 32px;
    border-radius: 8px;
    background-color: rgba(52,211,153,0.10);
    border: 1px solid rgba(52,211,153,0.25);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
}

.pet-curse-warning {
    font-size: 13px;
    font-weight: 800;
    color: rgba(248,113,113,0.85);
    margin-top: 6px;
    padding: 4px 8px;
    background-color: rgba(239,68,68,0.10);
    border: 1px solid rgba(248,113,113,0.20);
    border-radius: 6px;
}

/* Min chest requirement (displayed on left side bar)*/
.hud-min-chests {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 6px;
    padding: 4px 23px;
    background-color: rgba(0,0,0,0.35);
    border-radius: 6px;
    margin-top: 4px;
    justify-content: center;
}

.hud-min-chests-lbl {
    font-size: 18px;
    font-weight: 700;
    color: rgba(255,255,255,0.55);
}

.hud-min-chests-val {
    font-size: 18px;
    font-weight: 900;
    color: rgba(251,191,36,0.90);
}

/* MUSIC DEBUG */
.mm-dev-music-panel {
    position: absolute;
    bottom: 20px;
    right: 20px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 8px;
    background-color: rgba(4,1,13,0.90);
    border: 1px dashed rgba(244,114,182,0.30);
    border-radius: 6px;
    z-index: 99;
    pointer-events: all;
}

.mm-dev-music-label {
    font-size: 9px;
    font-weight: 900;
    color: rgba(244,114,182,0.60);
    letter-spacing: 0.20em;
    text-transform: uppercase;
    margin-bottom: 2px;
}

.mm-dev-btn {
    background-color: rgba(244,114,182,0.15);
    border: 1px solid rgba(244,114,182,0.30);
    border-radius: 4px;
    color: rgba(244,114,182,0.85);
    font-size: 10px;
    font-weight: 700;
    padding: 4px 8px;
    cursor: none;
    pointer-events: all;
    letter-spacing: 0.05em;
}

.mm-dev-btn:hover {
    background-color: rgba(244,114,182,0.25);
}

.mm-dev-btn-active {
    background-color: rgba(52,211,153,0.20);
    border-color: rgba(52,211,153,0.50);
    color: rgba(52,211,153,0.90);
}

/* =============== New SURVIVAL mode =============== */
.survival-timer {
    position: absolute;
    top: 100px;
    left: 50%;
    transform: translateX(-50%);
    width: 160px;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 8px 16px;
    background-color: rgba(10,4,28,0.80);
    border: 2px solid rgba(239,68,68,0.35);
    border-radius: 10px;
    box-shadow: 3px 3px 0px rgba(0,0,0,0.55);
    z-index: 10;
}

.survival-timer-icon {
    font-size: 16px;
}

.survival-timer-val {
    font-size: 20px;
    font-weight: 900;
    color: rgba(251,191,36,0.90);
    letter-spacing: 0.04em;
    font-variant-numeric: tabular-nums;
}

.survival-spawn-track {
    position: absolute;
    top: 145px; /* just below the timer pill */
    left: 50%;
    transform: translateX(-50%);
    width: 160px;
    height: 6px;
    background-color: rgba(255,255,255,0.08);
    border-radius: 3px;
    overflow: hidden;
    z-index: 10;
}

.survival-spawn-fill {
    height: 100%;
    background-color: rgba(251,191,36,0.85);
    transition: width 0.1s linear;
}

/* CAT */
.mm-pet-hero-emoji image {
    width: 170px;   /* bump this up to whatever size you want */
    height: 170px;
    image-rendering: pixelated; /* keep it crisp if it's pixel art */
    margin-left: -10px;
}

.mm-pet-hero-emoji {
    position: relative;
}
.cat-anchor { position: relative; }



.cat-speech-bubble {
    position: absolute;
    top: -50px;
    left: 50%;
    transform: translateX(-50%);
    background-color: rgba(255,255,255,0.95);
    color: #1a1030;
    font-size: 14px;
    font-weight: 800;
    padding: 8px 14px;
    border-radius: 12px;
    white-space: nowrap;
    box-shadow: 2px 2px 0px rgba(0,0,0,0.4);
    z-index: 10;
    animation: bubble-pop 0.2s ease-out both;
}

.cat-speech-bubble::after {
    content: "";
    position: absolute;
    bottom: -6px;
    left: 50%;
    width: 12px;
    height: 12px;
    background-color: rgba(255,255,255,0.95);
    transform: translateX(-50%) rotate(45deg);
}

@keyframes bubble-pop {
    0%   { opacity: 0; transform: translateX(-50%) scale(0.7); }
    100% { opacity: 1; transform: translateX(-50%) scale(1.0); }
}

/* Back button */
.back-toggle {
    top: 211px; /* 155 + 44 + 12 gap */
    background-color: rgba(60,60,180,0.85);
    border-color: rgba(140,140,255,0.60);
}

.back-toggle:hover {
    background-color: rgba(80,80,220,0.95);
    border-color: rgba(160,160,255,0.80);
}

.back-confirm-overlay {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    background-color: rgba(4,1,13,0.85);
    z-index: 90;
    pointer-events: all;
    display: flex;
    align-items: center;
    justify-content: center;
}

.back-confirm-popup {
    background-color: rgba(20,8,50,0.98);
    border: 2px solid rgba(167,139,250,0.55);
    border-radius: 18px;
    padding: 32px 40px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    max-width: 380px;
    text-align: center;
    box-shadow: 6px 6px 0px rgba(20,0,60,0.80);
}

.back-confirm-title { font-size: 22px; font-weight: 900; color: white; }
.back-confirm-desc { font-size: 13px; color: rgba(255,255,255,0.55); line-height: 1.5; }
.back-confirm-btns { display: flex; flex-direction: row; gap: 12px; margin-top: 8px; }

.back-confirm-btn {
    border-radius: 10px;
    font-size: 14px;
    font-weight: 800;
    padding: 12px 20px;
    cursor: none;
    pointer-events: all;
    border: 2px solid transparent;
}

.back-confirm-btn.cancel { background-color: rgba(52,211,153,0.15); border-color: rgba(52,211,153,0.45); color: #6ee7b7; }
.back-confirm-btn.confirm { background-color: rgba(239,68,68,0.15); border-color: rgba(239,68,68,0.45); color: #fca5a5; }


/* Survival hype track */
.survival-strip {
    position: absolute;
    top: 96px;
    left: 0;
    right: 0;
    height: 44px;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 10px;
    z-index: 10;
    pointer-events: none;
}

.survival-timer {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    background-color: rgba(10,4,28,0.85);
    border: 2px solid rgba(239,68,68,0.35);
    border-radius: 8px;
    flex-shrink: 0;
}
.survival-timer-icon { font-size: 14px; }
.survival-timer-val {
    font-size: 18px;
    font-weight: 900;
    color: rgba(251,191,36,0.9);
}

.survival-hype-track {
    position: relative;
    width: 130px;
    height: 20px;
    background-color: rgba(255,255,255,0.08);
    border: 1px solid rgba(251,146,60,0.35);
    border-radius: 6px;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.survival-hype-fill {
    position: absolute;
    top: 0; left: 0; bottom: 0;
    background-color: rgba(251,146,60,0.75);
    transition: width 0.12s linear;
}
.survival-hype-track.maxed {
    border-color: rgba(251,191,36,0.95);
    animation: hype-pulse 0.5s ease-in-out infinite alternate;
}
.survival-hype-track.maxed .survival-hype-fill { background-color: rgba(251,191,36,0.9); }
.survival-hype-lbl {
    font-size: 9px;
    font-weight: 900;
    letter-spacing: 0.12em;
    color: rgba(255,255,255,0.9);
    z-index: 1;
}
@keyframes hype-pulse {
    from { border-color: rgba(251,191,36,0.5); }
    to   { border-color: rgba(251,191,36,1.0); }
}

.survival-goal {
    position: relative;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 8px;
    padding: 5px 12px;
    background-color: rgba(10,4,28,0.85);
    border: 1px solid rgba(167,139,250,0.35);
    border-radius: 8px;
    flex-shrink: 0;
}
.survival-goal-lbl  { font-size: 11px; font-weight: 800; color: rgba(255,255,255,0.85); }
.survival-goal-prog { font-size: 12px; font-weight: 900; color: #a78bfa; }
.survival-goal-timer {
    position: absolute;
    bottom: 0; left: 0; right: 0;
    height: 2px;
    background-color: rgba(255,255,255,0.06);
}
.survival-goal-timer-fill {
    height: 100%;
    background-color: rgba(167,139,250,0.7);
    transition: width 0.2s linear;
}

.survival-spawn-track {
    width: 90px;
    height: 6px;
    background-color: rgba(255,255,255,0.08);
    border-radius: 3px;
    overflow: hidden;
    flex-shrink: 0;
}
.survival-spawn-fill {
    height: 100%;
    background-color: rgba(251,191,36,0.85);
    transition: width 0.1s linear;
}
playnplay.chain_reaction / styles/base/_splitcontainer.scss
Game game
.splitcontainer
{
	flex-grow: 1;

	> .split-left, > .split-right
	{
		flex-grow: 1;
	}

	> .split-left
	{
		height: 100%;
	}

	> .splitter
	{
		flex-grow: 1;
		width: 8px;
		cursor: ew-resize;

		&:hover
		{
		}
	}

	&.dragging > .splitter
	{
	}

	&.vertical
	{
		flex-direction: column;

		> .splitter
		{
			height: 8px;
			width: 100%;
			cursor: ns-resize;
		}
	}
}
playnplay.chain_reaction / styles/form/_coloreditor.scss
Game game
$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 ) );
	}
}
playnplay.chain_reaction / styles/form/_colorproperty.scss
Game game

.colorproperty
{
	align-items: center;
	position: relative;

	.colorsquare
	{
		width: 20px;
		height: 20px;
		border-radius: 4px;
		margin-right: 8px;
		position: absolute;
		left: 7px;
		z-index: 1;
		cursor: pointer;
	}

	> .textentry:not( .a.b.c )
	{
		padding-left: 36px;
	}
}
playnplay.chain_reaction / ui/controls/color/coloralphacontrol.cs.scss
Game game
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;
	}
}
Debug: View Raw JSON Response
{
    "TotalCount": 92,
    "Files": [
        {
            "Ident": "playnplay.chain_reaction",
            "Path": ".obj/__compiler_extra.cs",
            "FileName": "__compiler_extra.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "global using static Sandbox.Internal.GlobalGameNamespace;\r\nglobal using Microsoft.AspNetCore.Components;\r\nglobal using Microsoft.AspNetCore.Components.Rendering;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonTitle\", \"Chain Reaction\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonIdent\", \"chain_reaction\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"OrgIdent\", \"playnplay\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"Ident\", \"playnplay.chain_reaction\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineVersion\", \"28\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineMinorVersion\", \"1\" )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \".NETCoreApp,Version=v9.0\", FrameworkDisplayName = \".NET 9.0\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"CompileTime\", \"2026-08-19T10:24:41.4407817Z\" )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\"0.0.111.0\")]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\"0.0.111.0\")]"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "Game/2D/MusicManager.cs",
            "FileName": "MusicManager.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "using Sandbox;\r\n\r\npublic sealed class MusicManager : Component\r\n{\r\n    public static MusicManager Instance { get; private set; }\r\n\r\n    SoundHandle _menuMusic;\r\n    SoundHandle _windMusic;\r\n    SoundHandle _gameMusic;\r\n\r\n    // Tracked explicitly rather than inferred from SoundHandle.IsValid(), because\r\n    // a handle stopped with a fade is not reliably invalid straight away \u2014 and\r\n    // guessing wrong here means the menu comes back silent.\r\n    bool _menuPlaying;\r\n\r\n    protected override void OnStart()\r\n    {\r\n        Instance = this;\r\n\r\n        // Subscribe to game events\r\n        if (ChainReactionGame.Instance != null)\r\n        {\r\n            ChainReactionGame.Instance.OnGameStarted  += OnGameStarted;\r\n            ChainReactionGame.Instance.OnGameReturned += OnGameReturned;\r\n        }\r\n\r\n        StartMenuLoops(.25f);\r\n    }\r\n\r\n    void StartMenuLoops(float volume)\r\n    {\r\n        _menuMusic   = Sound.Play(\"phonk_menu_music\", volume);\r\n        _windMusic   = Sound.Play(\"wind_blowing_loop\", volume);\r\n        _menuPlaying = true;\r\n    }\r\n\r\n    void StopMenuLoops(float fade)\r\n    {\r\n        if (_menuMusic.IsValid()) _menuMusic.Stop(fade);\r\n        if (_windMusic.IsValid()) _windMusic.Stop(fade);\r\n        _menuPlaying = false;\r\n    }\r\n\r\n    void OnGameStarted()\r\n    {\r\n        // Reactor mode is meant to be left running for a long time \u2014 the combat\r\n        // track would grate after ten minutes, so it keeps the calmer menu loop.\r\n        if (ChainReactionGame.Instance?.Mode == GameMode.Idle)\r\n        {\r\n            if (_gameMusic.IsValid()) _gameMusic.Stop(.5f);\r\n\r\n            if (!_menuPlaying) StartMenuLoops(.18f);\r\n            else if (_menuMusic.IsValid()) _menuMusic.Volume = .18f;\r\n\r\n            return;\r\n        }\r\n\r\n        StopMenuLoops(.5f);\r\n\r\n        // Only start game music if not already playing\r\n        if (!_gameMusic.IsValid())\r\n            _gameMusic = Sound.Play(\"abydos_game\");\r\n    }\r\n\r\n    public void StopMenuMusic() => StopMenuLoops(0f);\r\n\r\n    public void PlayMenuMusic() => StartMenuLoops(.25f);\r\n\r\n    void OnGameReturned()\r\n    {\r\n        // Reactor mode never starts the combat track, so this handle can still be\r\n        // default-constructed here \u2014 calling Stop() on that throws.\r\n        if (_gameMusic.IsValid()) _gameMusic.Stop(.5f);\r\n\r\n        // Coming back from Reactor mode the menu loop never stopped \u2014 restore\r\n        // its volume instead of stacking a second copy on top of it.\r\n        if (_menuPlaying)\r\n        {\r\n            if (_menuMusic.IsValid()) _menuMusic.Volume = .3f;\r\n            return;\r\n        }\r\n\r\n        StartMenuLoops(.3f);\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "Game/Idle/UI/IdleHud.razor",
            "FileName": "IdleHud.razor",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "@using Sandbox;\r\n@using Sandbox.UI;\r\n@using System;\r\n@using System.Linq;\r\n@using System.Collections.Generic;\r\n@inherits PanelComponent\r\n@namespace Sandbox\r\n\r\n@*\r\n\tIMPORTANT \u2014 s&box text rendering quirk.\r\n\r\n\tRazor compiles a literal containing non-ASCII characters (emoji, \u00d7, \u00b7, \u2014)\r\n\tinto AddMarkupContent, and plain-ASCII literals into AddContent. The panel\r\n\trenderer hoists every AddMarkupContent node ahead of every AddContent node,\r\n\tso \"@A \u00d7 @B\" comes out as \"\u00d7AB\".\r\n\r\n\tThe rule this file follows: an element holds EITHER pure literal text OR a\r\n\tsingle @expression. Anything composed goes through a helper below.\r\n*@\r\n\r\n<root>\r\n@if ( Visible )\r\n{\r\n\t<div class=\"idle-root\">\r\n\r\n\t\t@* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 AMBIENT BACKDROP \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *@\r\n\t\t<div class=\"idle-bg\"></div>\r\n\t\t<div class=\"idle-bg-glow @GlowClass()\"></div>\r\n\r\n\t\t@for ( int i = 0; i < 12; i++ )\r\n\t\t{\r\n\t\t\tfloat pLeft = (i * 8.3f) % 100f;\r\n\t\t\tfloat pDur  = 10f + (i * 0.9f) % 8f;\r\n\t\t\tfloat pDel  = (i * 1.4f) % 12f;\r\n\t\t\tint   pSz   = (i % 4 == 0) ? 4 : 2;\r\n\t\t\tstring pCol = (i % 3 == 0) ? \"rgba(94,234,212,0.45)\" : \"rgba(96,165,250,0.30)\";\r\n\t\t\t<div class=\"idle-particle\"\r\n\t\t\t\t style=\"left:@pLeft.ToString(\"F1\")%;width:@(pSz)px;height:@(pSz)px;background-color:@pCol;animation-duration:@pDur.ToString(\"F1\")s;animation-delay:@pDel.ToString(\"F1\")s;\"></div>\r\n\t\t}\r\n\r\n\t\t@* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 TOP BAR \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *@\r\n\t\t<div class=\"idle-top\">\r\n\t\t\t<button class=\"idle-back\" onclick=@OnBack>\u2302</button>\r\n\r\n\t\t\t<div class=\"idle-brand\">\r\n\t\t\t\t<div class=\"idle-brand-title\">THE REACTOR</div>\r\n\t\t\t\t<div class=\"idle-brand-sub\">chain reactions, forever</div>\r\n\t\t\t</div>\r\n\r\n\t\t\t<div class=\"idle-currencies\">\r\n\t\t\t\t<div class=\"idle-cur cores\">\r\n\t\t\t\t\t<span class=\"idle-cur-icon\">\u269b\ufe0f</span>\r\n\t\t\t\t\t<span class=\"idle-cur-val\">@CoresLabel()</span>\r\n\t\t\t\t\t<span class=\"idle-cur-lbl\">Cores</span>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"idle-cur rate\">\r\n\t\t\t\t\t<span class=\"idle-cur-icon\">\ud83d\udcc8</span>\r\n\t\t\t\t\t<span class=\"idle-cur-val\">@RateLabel()</span>\r\n\t\t\t\t\t<span class=\"idle-cur-lbl\">per sec</span>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"idle-cur flux\">\r\n\t\t\t\t\t<span class=\"idle-cur-icon\">\ud83d\udd37</span>\r\n\t\t\t\t\t<span class=\"idle-cur-val\">@FluxLabel()</span>\r\n\t\t\t\t\t<span class=\"idle-cur-lbl\">Flux</span>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"idle-cur frag\">\r\n\t\t\t\t\t<span class=\"idle-cur-icon\">\ud83d\udca0</span>\r\n\t\t\t\t\t<span class=\"idle-cur-val\">@FragLabel()</span>\r\n\t\t\t\t\t<span class=\"idle-cur-lbl\">Frags</span>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\r\n\t\t@* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 BODY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *@\r\n\t\t@* Hidden behind a modal \u2014 the panel renderer draws emoji glyphs above\r\n\t\t   overlay panels, so the board has to actually leave the tree. *@\r\n\t\t@if ( !ModalOpen )\r\n\t\t{\r\n\t\t<div class=\"idle-body\">\r\n\r\n\t\t\t@* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 LEFT COLUMN \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *@\r\n\t\t\t<div class=\"idle-col idle-left\">\r\n\r\n\t\t\t\t<div class=\"idle-card\">\r\n\t\t\t\t\t<div class=\"idle-card-hd\">OUTPUT</div>\r\n\t\t\t\t\t<div class=\"idle-stat-row\">\r\n\t\t\t\t\t\t<span class=\"idle-stat-lbl\">Cores / sec</span>\r\n\t\t\t\t\t\t<span class=\"idle-stat-val hot\">@RateLabel()</span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"idle-stat-row\">\r\n\t\t\t\t\t\t<span class=\"idle-stat-lbl\">Global multiplier</span>\r\n\t\t\t\t\t\t<span class=\"idle-stat-val\">@GlobalMultLabel()</span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"idle-stat-row\">\r\n\t\t\t\t\t\t<span class=\"idle-stat-lbl\">Chain exponent</span>\r\n\t\t\t\t\t\t<span class=\"idle-stat-val\">@ExponentLabel()</span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"idle-stat-row\">\r\n\t\t\t\t\t\t<span class=\"idle-stat-lbl\">Last chain</span>\r\n\t\t\t\t\t\t<span class=\"idle-stat-val\">@LastChainLabel()</span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"idle-stat-row\">\r\n\t\t\t\t\t\t<span class=\"idle-stat-lbl\">Best chain</span>\r\n\t\t\t\t\t\t<span class=\"idle-stat-val\">@BestChainLabel()</span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"idle-stat-row\">\r\n\t\t\t\t\t\t<span class=\"idle-stat-lbl\">Board</span>\r\n\t\t\t\t\t\t<span class=\"idle-stat-val\">@BoardLabel()</span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"idle-stat-row\">\r\n\t\t\t\t\t\t<span class=\"idle-stat-lbl\">Feed line</span>\r\n\t\t\t\t\t\t<span class=\"idle-stat-val @FeedClass()\">@FeedLabel()</span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t@if ( ShowBoosts )\r\n\t\t\t\t{\r\n\t\t\t\t\t<div class=\"idle-card\">\r\n\t\t\t\t\t\t<div class=\"idle-card-hd\">ACTIVE</div>\r\n\t\t\t\t\t\t@if ( R.SurgeActive )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t<div class=\"idle-boost surge\">\r\n\t\t\t\t\t\t\t\t<span class=\"idle-boost-icon\">\u26a1</span>\r\n\t\t\t\t\t\t\t\t<span class=\"idle-boost-lbl\">@SurgeBoostLabel()</span>\r\n\t\t\t\t\t\t\t\t<span class=\"idle-boost-time\">@SecondsLabel( R.SurgeTimeLeft )</span>\r\n\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t@foreach ( var boost in R.Boosts )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar b = boost;\r\n\t\t\t\t\t\t\t<div class=\"idle-boost\">\r\n\t\t\t\t\t\t\t\t<span class=\"idle-boost-icon\">@b.Icon</span>\r\n\t\t\t\t\t\t\t\t<span class=\"idle-boost-lbl\">@BoostLabel( b )</span>\r\n\t\t\t\t\t\t\t\t<span class=\"idle-boost-time\">@SecondsLabel( b.SecondsLeft )</span>\r\n\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t}\r\n\r\n\t\t\t\t<div class=\"idle-card\">\r\n\t\t\t\t\t<div class=\"idle-card-hd\">CONDENSER</div>\r\n\t\t\t\t\t<div class=\"idle-bar-track\">\r\n\t\t\t\t\t\t<div class=\"idle-bar-fill frag\" style=\"width:@Pct( R.FragmentProgressFraction )%;\"></div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"idle-card-note\">@CondenserLabel()</div>\r\n\t\t\t\t\t<div class=\"idle-stat-row\">\r\n\t\t\t\t\t\t<span class=\"idle-stat-lbl\">Today</span>\r\n\t\t\t\t\t\t<span class=\"idle-stat-val @CapClass()\">@FragTodayLabel()</span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t<div class=\"idle-card\">\r\n\t\t\t\t\t<div class=\"idle-card-hd\">MANIFEST</div>\r\n\t\t\t\t\t@foreach ( var t in IdleBalance.Tiers )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar tier = t;\r\n\t\t\t\t\t\tbool got = S.CycleCores >= tier.Requires;\r\n\t\t\t\t\t\t<div class=\"idle-tier @(got ? \"on\" : \"\")\">\r\n\t\t\t\t\t\t\t<span class=\"idle-tier-icon\">@tier.Icon</span>\r\n\t\t\t\t\t\t\t<div class=\"idle-tier-body\">\r\n\t\t\t\t\t\t\t\t<div class=\"idle-tier-name\">@tier.Name</div>\r\n\t\t\t\t\t\t\t\t<div class=\"idle-tier-blurb\">@TierBlurb( tier, got )</div>\r\n\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t}\r\n\t\t\t\t</div>\r\n\r\n\t\t\t</div>\r\n\r\n\t\t\t@* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 CENTER COLUMN \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *@\r\n\t\t\t<div class=\"idle-col idle-center\">\r\n\r\n\t\t\t\t<div class=\"idle-board-wrap\">\r\n\t\t\t\t\t<div class=\"idle-board @SurgeClass() @BoardShake()\">\r\n\t\t\t\t\t\t@for ( int r = 0; r < R.Size; r++ )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t<div class=\"idle-row\">\r\n\t\t\t\t\t\t\t\t@for ( int c = 0; c < R.Size; c++ )\r\n\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\tint row = r;\r\n\t\t\t\t\t\t\t\t\tint col = c;\r\n\t\t\t\t\t\t\t\t\tvar cell = R.Board[row, col];\r\n\t\t\t\t\t\t\t\t\tbool golden = R.GoldenActive && R.GoldenRow == row && R.GoldenCol == col;\r\n\r\n\t\t\t\t\t\t\t\t\t<div class=\"idle-cell @CellClass( cell, golden )\"\r\n\t\t\t\t\t\t\t\t\t\t style=\"width:@(CellPx)px;height:@(CellPx)px;\"\r\n\t\t\t\t\t\t\t\t\t\t onclick=@(() => OnCellClick( row, col ))>\r\n\r\n\t\t\t\t\t\t\t\t\t\t@if ( golden )\r\n\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\t<div class=\"idle-golden\">\ud83c\udf1f</div>\r\n\t\t\t\t\t\t\t\t\t\t\t<div class=\"idle-golden-timer\">@GoldenTimerLabel()</div>\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t@if ( !golden && cell.HasChest )\r\n\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\t<div class=\"idle-chest\" style=\"@ChestStyle( cell )\">@ChestGlyph( cell )</div>\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t@if ( !golden && cell.HasChest && cell.MaxHits > 1 )\r\n\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\t<div class=\"idle-hits\">@cell.HitsLeft</div>\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t@if ( cell.HasDrone )\r\n\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\t<div class=\"idle-drone\" style=\"@DroneStyle( cell )\">\ud83d\udca3</div>\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t</div>\r\n\r\n\t\t\t\t\t@foreach ( var floater in _floaters )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar f = floater;\r\n\t\t\t\t\t\t<div class=\"idle-float @f.Cls\"\r\n\t\t\t\t\t\t\t style=\"left:@f.X.ToString(\"F1\")%;top:@f.Y.ToString(\"F1\")%;opacity:@f.Alpha.ToString(\"F2\");transform:translateY(@f.Rise.ToString(\"F0\")px) scale(@f.Scale.ToString(\"F2\"));\">\r\n\t\t\t\t\t\t\t@f.Text\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t@if ( _calloutLife > 0f )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t<div class=\"idle-callout @_calloutCls\" style=\"opacity:@_calloutLife.ToString(\"F2\");\">@_calloutText</div>\r\n\t\t\t\t\t}\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t<div class=\"idle-controls\">\r\n\r\n\t\t\t\t\t<button class=\"idle-overload @OverloadClass()\" onclick=@OnOverload>\r\n\t\t\t\t\t\t<div class=\"idle-overload-fill\" style=\"width:@OverloadFillPct()%;\"></div>\r\n\t\t\t\t\t\t<span class=\"idle-overload-label\">@OverloadLabel()</span>\r\n\t\t\t\t\t\t<span class=\"idle-overload-sub\">@OverloadSubLabel()</span>\r\n\t\t\t\t\t</button>\r\n\r\n\t\t\t\t\t<div class=\"idle-surge-wrap\">\r\n\t\t\t\t\t\t<div class=\"idle-surge-track @SurgeReadyClass()\">\r\n\t\t\t\t\t\t\t<div class=\"idle-surge-fill\" style=\"width:@Pct( R.Charge )%;\"></div>\r\n\t\t\t\t\t\t\t<span class=\"idle-surge-lbl\">@SurgeLabel()</span>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t@if ( R.SurgeReady )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t<button class=\"idle-surge-btn\" onclick=@OnSurge>@UnleashLabel()</button>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t<div class=\"idle-meltdown-strip @MeltdownStripClass()\">\r\n\t\t\t\t\t<span class=\"idle-melt-icon\">\u2622\ufe0f</span>\r\n\t\t\t\t\t<span class=\"idle-melt-text\">@MeltdownLabel()</span>\r\n\t\t\t\t\t@if ( R.CanMeltdown )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t<button class=\"idle-melt-btn\" onclick=@OnOpenMeltdown>REVIEW</button>\r\n\t\t\t\t\t}\r\n\t\t\t\t\t@if ( !R.CanMeltdown )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t<div class=\"idle-melt-bar\">\r\n\t\t\t\t\t\t\t<div class=\"idle-melt-fill\" style=\"width:@Pct( R.MeltdownProgress )%;\"></div>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t}\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\r\n\t\t\t@* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 RIGHT COLUMN \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *@\r\n\t\t\t<div class=\"idle-col idle-right\">\r\n\r\n\t\t\t\t<div class=\"idle-tabs\">\r\n\t\t\t\t\t<button class=\"idle-tab @TabClass(\"reactor\")\" onclick=@(() => SetTab(\"reactor\"))>\u2699\ufe0f</button>\r\n\t\t\t\t\t<button class=\"idle-tab @TabClass(\"chains\")\"  onclick=@(() => SetTab(\"chains\"))>\u26d3\ufe0f</button>\r\n\t\t\t\t\t<button class=\"idle-tab @TabClass(\"systems\")\" onclick=@(() => SetTab(\"systems\"))>\ud83d\udee0\ufe0f</button>\r\n\t\t\t\t\t<button class=\"idle-tab fluxtab @TabClass(\"flux\")\" onclick=@(() => SetTab(\"flux\"))>\ud83d\udd37</button>\r\n\t\t\t\t\t<button class=\"idle-tab @TabClass(\"log\")\" onclick=@(() => SetTab(\"log\"))>\ud83d\udcdc</button>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t@if ( IsUpgradeTab )\r\n\t\t\t\t{\r\n\t\t\t\t\t<div class=\"idle-buyrow\">\r\n\t\t\t\t\t\t<span class=\"idle-buyrow-lbl\">BUY</span>\r\n\t\t\t\t\t\t@foreach ( var amount in BuyAmounts )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tint amt = amount;\r\n\t\t\t\t\t\t\t<button class=\"idle-buy-amt @BuyAmtClass( amt )\" onclick=@(() => SetBuyAmount( amt ))>@BuyAmountLabel( amt )</button>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t}\r\n\r\n\t\t\t\t<div class=\"idle-panel\">\r\n\r\n\t\t\t\t\t@if ( IsUpgradeTab )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t@foreach ( var upgrade in IdleUpgradeCatalog.InCategory( CurrentCategory ) )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar up = upgrade;\r\n\t\t\t\t\t\t\tbool revealed = R.IsRevealed( up );\r\n\t\t\t\t\t\t\tint  lvl      = S.Level( up.Id );\r\n\t\t\t\t\t\t\tbool maxed    = lvl >= up.MaxLevel;\r\n\t\t\t\t\t\t\tint  count    = revealed ? BuyCount( up.Id ) : 0;\r\n\t\t\t\t\t\t\tdouble cost   = (!revealed || maxed) ? 0 : R.CostFor( up.Id, count );\r\n\t\t\t\t\t\t\tbool afford   = revealed && !maxed && count > 0 && Cores >= cost;\r\n\r\n\t\t\t\t\t\t\t<div class=\"idle-up @UpgradeClass( revealed, maxed, afford )\" onclick=@(() => OnBuy( up.Id ))>\r\n\t\t\t\t\t\t\t\t<div class=\"idle-up-icon\">@UpgradeIcon( up, revealed )</div>\r\n\t\t\t\t\t\t\t\t<div class=\"idle-up-body\">\r\n\t\t\t\t\t\t\t\t\t<div class=\"idle-up-top\">\r\n\t\t\t\t\t\t\t\t\t\t<span class=\"idle-up-name\">@UpgradeName( up, revealed )</span>\r\n\t\t\t\t\t\t\t\t\t\t<span class=\"idle-up-lvl\">@LevelLabel( up, lvl, revealed )</span>\r\n\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t\t<div class=\"idle-up-desc\">@UpgradeDesc( up, revealed )</div>\r\n\t\t\t\t\t\t\t\t\t@if ( revealed )\r\n\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t<div class=\"idle-up-bottom\">\r\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"idle-up-effect\">@up.Effect( lvl )</span>\r\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"idle-up-cost @MaxedClass( maxed )\">@CostLabel( maxed, cost, count )</span>\r\n\t\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t@if ( _tab == \"flux\" )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t<div class=\"idle-flux-head\">\r\n\t\t\t\t\t\t\t<div class=\"idle-flux-amount\">@FluxHeadLabel()</div>\r\n\t\t\t\t\t\t\t<div class=\"idle-flux-sub\">@FluxSubLabel()</div>\r\n\t\t\t\t\t\t</div>\r\n\r\n\t\t\t\t\t\t@foreach ( var perk in IdleUpgradeCatalog.Perks )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar p      = perk;\r\n\t\t\t\t\t\t\tint lvl    = S.PerkLevel( p.Id );\r\n\t\t\t\t\t\t\tbool maxed = lvl >= p.MaxLevel;\r\n\t\t\t\t\t\t\tint cost   = p.CostAt( lvl );\r\n\t\t\t\t\t\t\tbool afford = !maxed && S.Flux >= cost;\r\n\r\n\t\t\t\t\t\t\t<div class=\"idle-up perk @UpgradeClass( true, maxed, afford )\" onclick=@(() => OnBuyPerk( p.Id ))>\r\n\t\t\t\t\t\t\t\t<div class=\"idle-up-icon\">@p.Icon</div>\r\n\t\t\t\t\t\t\t\t<div class=\"idle-up-body\">\r\n\t\t\t\t\t\t\t\t\t<div class=\"idle-up-top\">\r\n\t\t\t\t\t\t\t\t\t\t<span class=\"idle-up-name\">@p.Name</span>\r\n\t\t\t\t\t\t\t\t\t\t<span class=\"idle-up-lvl\">@PerkLevelLabel( p, lvl )</span>\r\n\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t\t<div class=\"idle-up-desc\">@p.Desc</div>\r\n\t\t\t\t\t\t\t\t\t<div class=\"idle-up-bottom\">\r\n\t\t\t\t\t\t\t\t\t\t<span class=\"idle-up-effect\">@p.Effect( lvl )</span>\r\n\t\t\t\t\t\t\t\t\t\t<span class=\"idle-up-cost flux @MaxedClass( maxed )\">@PerkCostLabel( maxed, cost )</span>\r\n\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t@if ( _tab == \"log\" )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t<div class=\"idle-flux-head\">\r\n\t\t\t\t\t\t\t<div class=\"idle-flux-amount\">@LogHeadLabel()</div>\r\n\t\t\t\t\t\t\t<div class=\"idle-flux-sub\">Reactor Log \u2014 every entry pays out once.</div>\r\n\t\t\t\t\t\t</div>\r\n\r\n\t\t\t\t\t\t@foreach ( var achievement in IdleAchievements.All )\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tvar a = achievement;\r\n\t\t\t\t\t\t\tbool done = S.HasAchievement( a.Id );\r\n\r\n\t\t\t\t\t\t\t<div class=\"idle-up ach @(done ? \"done\" : \"\")\">\r\n\t\t\t\t\t\t\t\t<div class=\"idle-up-icon\">@AchIcon( a, done )</div>\r\n\t\t\t\t\t\t\t\t<div class=\"idle-up-body\">\r\n\t\t\t\t\t\t\t\t\t<div class=\"idle-up-top\">\r\n\t\t\t\t\t\t\t\t\t\t<span class=\"idle-up-name\">@a.Name</span>\r\n\t\t\t\t\t\t\t\t\t\t<span class=\"idle-ach-reward\">@RewardLabel( a )</span>\r\n\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t\t<div class=\"idle-up-desc\">@a.Desc</div>\r\n\t\t\t\t\t\t\t\t\t@if ( !done )\r\n\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t<div class=\"idle-ach-bar\">\r\n\t\t\t\t\t\t\t\t\t\t\t<div class=\"idle-ach-fill\" style=\"width:@Pct( a.Fraction( S ) )%;\"></div>\r\n\t\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t\t\t<div class=\"idle-ach-prog\">@ProgressLabel( a )</div>\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t\t}\r\n\r\n\t\t@* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 TOASTS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *@\r\n\t\t<div class=\"idle-toasts\">\r\n\t\t\t@foreach ( var toast in _toasts )\r\n\t\t\t{\r\n\t\t\t\tvar t = toast;\r\n\t\t\t\t<div class=\"idle-toast @t.Cls\" style=\"opacity:@t.Alpha.ToString(\"F2\");\">\r\n\t\t\t\t\t<span class=\"idle-toast-icon\">@t.Icon</span>\r\n\t\t\t\t\t<span class=\"idle-toast-text\">@t.Text</span>\r\n\t\t\t\t</div>\r\n\t\t\t}\r\n\t\t</div>\r\n\r\n\t\t@* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 WELCOME BACK OVERLAY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *@\r\n\t\t@if ( R.OfflinePending )\r\n\t\t{\r\n\t\t\t<div class=\"idle-modal-bg\"></div>\r\n\t\t\t<div class=\"idle-modal offline\">\r\n\t\t\t\t<div class=\"idle-modal-eyebrow\">REACTOR REPORT</div>\r\n\t\t\t\t<div class=\"idle-modal-title\">Welcome back</div>\r\n\t\t\t\t<div class=\"idle-modal-sub\">@OfflineSubLabel()</div>\r\n\r\n\t\t\t\t<div class=\"idle-offline-haul\">\r\n\t\t\t\t\t<div class=\"idle-offline-amount\">@OfflineAmountLabel()</div>\r\n\t\t\t\t\t<div class=\"idle-offline-lbl\">CORES</div>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t@if ( R.PendingOfflineFrags > 0 )\r\n\t\t\t\t{\r\n\t\t\t\t\t<div class=\"idle-offline-extra\">@OfflineFragLabel()</div>\r\n\t\t\t\t}\r\n\r\n\t\t\t\t@if ( OfflineTankFull )\r\n\t\t\t\t{\r\n\t\t\t\t\t<div class=\"idle-offline-warn\">@OfflineWarnLabel()</div>\r\n\t\t\t\t}\r\n\r\n\t\t\t\t<button class=\"idle-modal-btn\" onclick=@OnClaimOffline>COLLECT</button>\r\n\t\t\t</div>\r\n\t\t}\r\n\r\n\t\t@* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 DAILY STREAK OVERLAY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *@\r\n\t\t@if ( !R.OfflinePending && !_showIntro && R.DailyPending )\r\n\t\t{\r\n\t\t\t<div class=\"idle-modal-bg\"></div>\r\n\t\t\t<div class=\"idle-modal daily\">\r\n\t\t\t\t<div class=\"idle-modal-eyebrow\">DAILY CYCLE</div>\r\n\t\t\t\t<div class=\"idle-modal-title\">@DailyTitleLabel()</div>\r\n\t\t\t\t<div class=\"idle-modal-sub\">Come back tomorrow to keep the streak climbing. Day 7 pays eight times.</div>\r\n\r\n\t\t\t\t<div class=\"idle-streak-row\">\r\n\t\t\t\t\t@for ( int d = 1; d <= 7; d++ )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tint day = d;\r\n\t\t\t\t\t\t<div class=\"idle-streak-pip @StreakPipClass( day )\">@day</div>\r\n\t\t\t\t\t}\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t<div class=\"idle-daily-reward\">\r\n\t\t\t\t\t<div class=\"idle-daily-mult\">@DailyMultLabel()</div>\r\n\t\t\t\t\t<div class=\"idle-daily-lbl\">@DailyRewardLabel()</div>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t<button class=\"idle-modal-btn\" onclick=@OnClaimDaily>CLAIM</button>\r\n\t\t\t</div>\r\n\t\t}\r\n\r\n\t\t@* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 MELTDOWN MODAL \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *@\r\n\t\t@if ( !R.OfflinePending && !_showIntro && !R.DailyPending && _showMeltdown )\r\n\t\t{\r\n\t\t\t<div class=\"idle-modal-bg\"></div>\r\n\t\t\t<div class=\"idle-modal meltdown\">\r\n\t\t\t\t<div class=\"idle-modal-eyebrow\">PRESTIGE</div>\r\n\t\t\t\t<div class=\"idle-modal-title\">MELTDOWN</div>\r\n\t\t\t\t<div class=\"idle-modal-sub\">Collapse this cycle into permanent Flux. Every Flux you have ever earned adds income forever, and Flux buys perks that survive every reset.</div>\r\n\r\n\t\t\t\t<div class=\"idle-melt-payout\">\r\n\t\t\t\t\t<div class=\"idle-melt-payout-val\">@MeltPayoutLabel()</div>\r\n\t\t\t\t\t<div class=\"idle-melt-payout-lbl\">FLUX</div>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t<div class=\"idle-melt-cols\">\r\n\t\t\t\t\t<div class=\"idle-melt-col keep\">\r\n\t\t\t\t\t\t<div class=\"idle-melt-col-hd\">KEPT</div>\r\n\t\t\t\t\t\t<div class=\"idle-melt-line\">Flux and every perk</div>\r\n\t\t\t\t\t\t<div class=\"idle-melt-line\">Reactor Log entries</div>\r\n\t\t\t\t\t\t<div class=\"idle-melt-line\">Records and Fragments</div>\r\n\t\t\t\t\t\t<div class=\"idle-melt-line\">Kickstart head start</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"idle-melt-col lose\">\r\n\t\t\t\t\t\t<div class=\"idle-melt-col-hd\">BURNED</div>\r\n\t\t\t\t\t\t<div class=\"idle-melt-line\">@BurnedCoresLabel()</div>\r\n\t\t\t\t\t\t<div class=\"idle-melt-line\">All Core upgrades</div>\r\n\t\t\t\t\t\t<div class=\"idle-melt-line\">Reactor Frame size</div>\r\n\t\t\t\t\t\t<div class=\"idle-melt-line\">Unlocked chest tiers</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t<div class=\"idle-modal-btns\">\r\n\t\t\t\t\t<button class=\"idle-modal-btn ghost\" onclick=@OnCloseMeltdown>Not yet</button>\r\n\t\t\t\t\t<button class=\"idle-modal-btn danger\" onclick=@OnMeltdown>MELT DOWN</button>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t}\r\n\r\n\t\t@* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 INTRO MODAL \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *@\r\n\t\t@if ( !R.OfflinePending && _showIntro )\r\n\t\t{\r\n\t\t\t<div class=\"idle-modal-bg\"></div>\r\n\t\t\t<div class=\"idle-modal intro\">\r\n\t\t\t\t<div class=\"idle-modal-eyebrow\">HOW IT WORKS</div>\r\n\t\t\t\t<div class=\"idle-modal-title\">THE REACTOR</div>\r\n\t\t\t\t<div class=\"idle-modal-sub\">A board that plays itself. Chests drop, drones bomb them, chain reactions pay Cores. You never lose here \u2014 you only grow.</div>\r\n\r\n\t\t\t\t<div class=\"idle-intro-steps\">\r\n\t\t\t\t\t<div class=\"idle-intro-step\">\r\n\t\t\t\t\t\t<span class=\"idle-intro-num\">1</span>\r\n\t\t\t\t\t\t<span class=\"idle-intro-txt\">Spend Cores on the right. Longer chains are worth far more than richer chests.</span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"idle-intro-step\">\r\n\t\t\t\t\t\t<span class=\"idle-intro-num\">2</span>\r\n\t\t\t\t\t\t<span class=\"idle-intro-txt\">Hit OVERLOAD, or press SPACE, for a burst. It charges the Surge meter \u2014 ten times output when you unleash it.</span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"idle-intro-step\">\r\n\t\t\t\t\t\t<span class=\"idle-intro-num\">3</span>\r\n\t\t\t\t\t\t<span class=\"idle-intro-txt\">It keeps running while you are on the menu or another screen. Close the game and it stops.</span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"idle-intro-step\">\r\n\t\t\t\t\t\t<span class=\"idle-intro-num\">4</span>\r\n\t\t\t\t\t\t<span class=\"idle-intro-txt\">Cores condense into Fragments, which feed your companion for a permanent output bonus. Meltdown turns a whole cycle into Flux.</span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t<button class=\"idle-modal-btn\" onclick=@OnCloseIntro>START THE REACTOR</button>\r\n\t\t\t</div>\r\n\t\t}\r\n\t</div>\r\n}\r\n</root>\r\n\r\n@code {\r\n\tpublic static IdleHud Instance { get; private set; }\r\n\r\n\tIdleReactor       R    => IdleReactor.Instance;\r\n\tChainReactionGame Game => ChainReactionGame.Instance;\r\n\tIdleSave          S    => ChainReactionGame.Save?.Idle;\r\n\r\n\tbool Visible => Game?.GameStarted == true\r\n\t\t\t\t && Game?.Mode == GameMode.Idle\r\n\t\t\t\t && R != null && S != null;\r\n\r\n\tdouble Cores => S?.Cores ?? 0;\r\n\r\n\tbool ModalOpen  => R.OfflinePending || R.DailyPending || _showMeltdown || _showIntro;\r\n\tbool ShowBoosts => R.Boosts.Count > 0 || R.SurgeActive;\r\n\tbool OfflineTankFull => R.PendingOfflineSeconds > R.OfflineCapHours * 3600f;\r\n\r\n\t// \u2500\u2500 UI state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\tstring _tab       = \"reactor\";\r\n\tint    _buyAmount = 1;                      // -1 == MAX\r\n\tbool   _showMeltdown;\r\n\tbool   _showIntro;\r\n\tbool   _subscribed;\r\n\tbool   _wasVisible;\r\n\r\n\tstatic readonly int[] BuyAmounts = { 1, 10, 25, -1 };\r\n\r\n\tbool IsUpgradeTab => _tab == \"reactor\" || _tab == \"chains\" || _tab == \"systems\";\r\n\r\n\tIdleUpgradeCategory CurrentCategory => _tab switch\r\n\t{\r\n\t\t\"reactor\" => IdleUpgradeCategory.Reactor,\r\n\t\t\"chains\"  => IdleUpgradeCategory.Chains,\r\n\t\t_         => IdleUpgradeCategory.Systems,\r\n\t};\r\n\r\n\t// \u2500\u2500 Floating numbers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\tclass Floater\r\n\t{\r\n\t\tpublic string Text;\r\n\t\tpublic string Cls;\r\n\t\tpublic float X, Y;\r\n\t\tpublic float Life, MaxLife;\r\n\t\tpublic float Alpha => MathF.Min( 1f, Life / 0.35f );\r\n\t\tpublic float Rise  => -(MaxLife - Life) * 42f;\r\n\t\tpublic float Scale => 0.85f + MathF.Min( 0.35f, (MaxLife - Life) * 1.2f );\r\n\t}\r\n\treadonly List<Floater> _floaters = new();\r\n\r\n\tclass ToastMsg\r\n\t{\r\n\t\tpublic string Icon;\r\n\t\tpublic string Text;\r\n\t\tpublic string Cls;\r\n\t\tpublic float Life;\r\n\t\tpublic float Alpha => MathF.Min( 1f, Life / 0.4f );\r\n\t}\r\n\treadonly List<ToastMsg> _toasts = new();\r\n\r\n\tstring _calloutText = \"\";\r\n\tstring _calloutCls  = \"\";\r\n\tfloat  _calloutLife;\r\n\r\n\t// Board shake is kept local rather than going through ScreenShaker so the\r\n\t// class string stays stable between rebuilds \u2014 a changing class would\r\n\t// restart the animation on every repaint and read as a vibration.\r\n\tstring _shakeCls = \"\";\r\n\tfloat  _shakeUntil;\r\n\r\n\tint CellPx => R == null ? 92 : R.Size switch\r\n\t{\r\n\t\t5 => 96,\r\n\t\t6 => 84,\r\n\t\t7 => 74,\r\n\t\t8 => 66,\r\n\t\t_ => 59,\r\n\t};\r\n\r\n\t// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\t// Lifecycle\r\n\t// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tInstance = this;\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tif ( !_subscribed && R != null ) Subscribe();\r\n\r\n\t\tbool vis = Visible;\r\n\t\tif ( vis != _wasVisible )\r\n\t\t{\r\n\t\t\t_wasVisible = vis;\r\n\t\t\tif ( vis )\r\n\t\t\t{\r\n\t\t\t\t_floaters.Clear();\r\n\t\t\t\t_toasts.Clear();\r\n\t\t\t\t_calloutLife  = 0f;\r\n\t\t\t\t_shakeUntil   = 0f;\r\n\t\t\t\t_showMeltdown = false;\r\n\t\t\t\t_showIntro    = S != null && !S.IntroShown;\r\n\t\t\t\t_tab          = \"reactor\";\r\n\t\t\t}\r\n\t\t\tStateHasChanged();\r\n\t\t}\r\n\r\n\t\tif ( !vis ) return;\r\n\r\n\t\t// SPACE is the detonate key everywhere else in the game, so it fires the\r\n\t\t// reactor's manual burst here too.\r\n\t\tif ( Input.Pressed( \"Jump\" ) && R.OverloadReady && !ModalOpen )\r\n\t\t\tR.Overload();\r\n\r\n\t\tfloat dt = Time.Delta;\r\n\r\n\t\tfor ( int i = _floaters.Count - 1; i >= 0; i-- )\r\n\t\t{\r\n\t\t\t_floaters[i].Life -= dt;\r\n\t\t\tif ( _floaters[i].Life <= 0f ) _floaters.RemoveAt( i );\r\n\t\t}\r\n\r\n\t\tfor ( int i = _toasts.Count - 1; i >= 0; i-- )\r\n\t\t{\r\n\t\t\t_toasts[i].Life -= dt;\r\n\t\t\tif ( _toasts[i].Life <= 0f ) _toasts.RemoveAt( i );\r\n\t\t}\r\n\r\n\t\tif ( _calloutLife > 0f ) _calloutLife -= dt * 1.4f;\r\n\t}\r\n\r\n\tvoid Subscribe()\r\n\t{\r\n\t\tvar r = R;\r\n\t\tif ( r == null || _subscribed ) return;\r\n\r\n\t\tr.OnPop        += OnPop;\r\n\t\tr.OnToast      += OnToastEvent;\r\n\t\tr.OnDetonation += OnDetonation;\r\n\r\n\t\t_subscribed = true;\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Rebuilds only when something on screen would actually differ.\r\n\t///\r\n\t/// The first version forced a repaint 15 times a second, which meant\r\n\t/// rebuilding the board, both side columns and every upgrade row constantly\r\n\t/// \u2014 that was the source of the stutter. Between detonations almost nothing\r\n\t/// changes, so hashing real state instead of a clock cuts the repaint rate\r\n\t/// by roughly an order of magnitude.\r\n\t/// </summary>\r\n\tprotected override int BuildHash()\r\n\t{\r\n\t\tif ( !Visible ) return 0;\r\n\r\n\t\tvar hc = new HashCode();\r\n\t\thc.Add( _tab );\r\n\t\thc.Add( _buyAmount );\r\n\t\thc.Add( _showMeltdown );\r\n\t\thc.Add( _showIntro );\r\n\t\thc.Add( ModalOpen );\r\n\r\n\t\t// Currencies \u2014 quantised to what the labels actually show\r\n\t\thc.Add( (long)Cores );\r\n\t\thc.Add( (long)R.Cps );\r\n\t\thc.Add( S.Flux );\r\n\t\thc.Add( ChainReactionGame.Save?.TotalFragments ?? 0 );\r\n\t\thc.Add( S.TodayReactorFragments );\r\n\t\thc.Add( (int)(R.FragmentProgressFraction * 60f) );\r\n\r\n\t\t// Live controls\r\n\t\thc.Add( (int)(R.OverloadCooldownLeft * 10f) );\r\n\t\thc.Add( (int)(R.Charge * 100f) );\r\n\t\thc.Add( R.SurgeActive );\r\n\t\thc.Add( (int)R.SurgeTimeLeft );\r\n\t\thc.Add( (int)R.GoldenTimeLeft );\r\n\t\thc.Add( R.Boosts.Count );\r\n\t\thc.Add( R.Boosts.Count > 0 ? (int)R.Boosts[0].SecondsLeft : 0 );\r\n\t\thc.Add( R.FeedStarved );\r\n\r\n\t\t// Transient effects\r\n\t\thc.Add( _floaters.Count );\r\n\t\thc.Add( (int)(_calloutLife * 20f) );\r\n\t\thc.Add( Time.Now < _shakeUntil ? _shakeCls : \"\" );\r\n\t\thc.Add( _toasts.Count );\r\n\r\n\t\t// Board \u2014 chest layout, damage, drones and any live flash\r\n\t\thc.Add( R.Size );\r\n\t\tfloat now = Time.Now;\r\n\t\tbool animating = _floaters.Count > 0 || _calloutLife > 0f || now < _shakeUntil;\r\n\r\n\t\tfor ( int r = 0; r < R.Size; r++ )\r\n\t\t\tfor ( int c = 0; c < R.Size; c++ )\r\n\t\t\t{\r\n\t\t\t\tvar cell = R.Board[r, c];\r\n\t\t\t\thc.Add( (int)cell.Chest );\r\n\t\t\t\thc.Add( cell.HitsLeft );\r\n\t\t\t\thc.Add( cell.HasDrone );\r\n\t\t\t\thc.Add( cell.FlashUntil > now ? cell.FlashKind : 0 );\r\n\r\n\t\t\t\tif ( cell.FlashUntil > now ) animating = true;\r\n\t\t\t\tif ( cell.HasChest && now - cell.SpawnAt < 0.25f ) animating = true;\r\n\t\t\t\tif ( cell.HasDrone && now - cell.DroneAt < 0.18f ) animating = true;\r\n\t\t\t}\r\n\r\n\t\thc.Add( R.GoldenRow );\r\n\t\thc.Add( R.GoldenCol );\r\n\t\thc.Add( R.LastChain );\r\n\r\n\t\t// Anything mid-flight is drawn from inline styles rather than keyframes,\r\n\t\t// so it needs a clock \u2014 but only while it is actually moving. The rest of\r\n\t\t// the time this hash is stable and the panel is never rebuilt at all.\r\n\t\tif ( animating ) hc.Add( (int)(now * 20f) );\r\n\r\n\t\treturn hc.ToHashCode();\r\n\t}\r\n\r\n\t// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\t// Reactor events\r\n\t// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\r\n\tvoid OnPop( int row, int col, double value, bool crit )\r\n\t{\r\n\t\tif ( R == null || _floaters.Count > 10 ) return;\r\n\r\n\t\tfloat step = 100f / R.Size;\r\n\t\t_floaters.Add( new Floater {\r\n\t\t\tText = \"+\" + IdleFormat.Short( value ),\r\n\t\t\tCls  = crit ? \"crit\" : \"\",\r\n\t\t\tX = step * col + step * 0.5f,\r\n\t\t\tY = step * row + step * 0.5f,\r\n\t\t\tLife = 0.9f,\r\n\t\t\tMaxLife = 0.9f,\r\n\t\t} );\r\n\t}\r\n\r\n\tvoid OnToastEvent( string icon, string text, string cls )\r\n\t{\r\n\t\t_toasts.Insert( 0, new ToastMsg { Icon = icon, Text = text, Cls = cls, Life = 3.2f } );\r\n\t\twhile ( _toasts.Count > 4 ) _toasts.RemoveAt( _toasts.Count - 1 );\r\n\t}\r\n\r\n\tvoid OnDetonation( int chain, double payout, bool crit )\r\n\t{\r\n\t\tstring shake = crit || chain >= 25 ? \"idle-shake-3\"\r\n\t\t\t\t\t : chain >= 12 ? \"idle-shake-2\"\r\n\t\t\t\t\t : chain >= 4  ? \"idle-shake-1\"\r\n\t\t\t\t\t : \"\";\r\n\r\n\t\tif ( shake != \"\" )\r\n\t\t{\r\n\t\t\t_shakeCls   = shake;\r\n\t\t\t_shakeUntil = Time.Now + (shake == \"idle-shake-3\" ? 0.45f : 0.28f);\r\n\t\t}\r\n\r\n\t\tif ( chain < 15 && !crit ) return;\r\n\r\n\t\tdouble critPower = IdleBalance.CritPower( S?.Level( IdleUpgradeId.CritPower ) ?? 0 );\r\n\r\n\t\t_calloutText = crit\r\n\t\t\t? $\"CRIT \u00d7{critPower:F0}  \u00b7  +{IdleFormat.Short( payout )}\"\r\n\t\t\t: $\"{chain} CHAIN  \u00b7  +{IdleFormat.Short( payout )}\";\r\n\r\n\t\t_calloutCls  = crit ? \"crit\" : chain >= 35 ? \"mega\" : \"big\";\r\n\t\t_calloutLife = 1f;\r\n\t}\r\n\r\n\t// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\t// Input\r\n\t// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\r\n\tvoid SetTab( string tab )       { _tab = tab; Sound.Play( \"UI_Button\" ); }\r\n\tvoid SetBuyAmount( int amount ) { _buyAmount = amount; Sound.Play( \"UI_Button\" ); }\r\n\r\n\tint BuyCount( IdleUpgradeId id )\r\n\t\t=> _buyAmount < 0 ? Math.Max( 1, R.MaxAffordable( id ) ) : _buyAmount;\r\n\r\n\tvoid OnBuy( IdleUpgradeId id )\r\n\t{\r\n\t\tif ( R == null ) return;\r\n\t\tif ( !R.TryBuy( id, BuyCount( id ) ) ) Sound.Play( \"Pop_Button_1\" );\r\n\t}\r\n\r\n\tvoid OnBuyPerk( IdlePerkId id )\r\n\t{\r\n\t\tif ( R == null ) return;\r\n\t\tif ( !R.TryBuyPerk( id ) ) Sound.Play( \"Pop_Button_1\" );\r\n\t}\r\n\r\n\tvoid OnOverload()  => R?.Overload();\r\n\tvoid OnSurge()     => R?.ActivateSurge();\r\n\tvoid OnCellClick( int r, int c ) => R?.ClickCell( r, c );\r\n\r\n\tvoid OnClaimOffline() => R?.ClaimOffline();\r\n\tvoid OnClaimDaily()   => R?.ClaimDaily();\r\n\r\n\tvoid OnOpenMeltdown()  { _showMeltdown = true;  Sound.Play( \"UI_Button\" ); }\r\n\tvoid OnCloseMeltdown() { _showMeltdown = false; Sound.Play( \"UI_Button\" ); }\r\n\r\n\tvoid OnMeltdown()\r\n\t{\r\n\t\t_showMeltdown = false;\r\n\t\tR?.DoMeltdown();\r\n\t}\r\n\r\n\tvoid OnCloseIntro()\r\n\t{\r\n\t\t_showIntro = false;\r\n\t\tif ( S != null )\r\n\t\t{\r\n\t\t\tS.IntroShown = true;\r\n\t\t\tChainReactionGame.Save?.Save();\r\n\t\t}\r\n\t\tSound.Play( \"PlayButton\" );\r\n\t}\r\n\r\n\tvoid OnBack()\r\n\t{\r\n\t\tSound.Play( \"UI_Button\" );\r\n\t\tGame?.ReturnToMenu();\r\n\t}\r\n\r\n\t// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\t// Class helpers\r\n\t// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\r\n\tstring Pct( float f ) => (Math.Clamp( f, 0f, 1f ) * 100f).ToString( \"F1\" );\r\n\r\n\tstring GlowClass()        => R.SurgeActive ? \"surging\" : \"\";\r\n\tstring SurgeClass()       => R.SurgeActive ? \"surging\" : \"\";\r\n\tstring SurgeReadyClass()  => R.SurgeReady ? \"ready\" : \"\";\r\n\tstring OverloadClass()    => R.OverloadReady ? \"ready\" : \"cooling\";\r\n\tstring BoardShake()       => Time.Now < _shakeUntil ? _shakeCls : \"\";\r\n\tstring TabClass( string t )     => _tab == t ? \"on\" : \"\";\r\n\tstring BuyAmtClass( int amt )   => _buyAmount == amt ? \"on\" : \"\";\r\n\tstring MaxedClass( bool maxed ) => maxed ? \"maxed\" : \"\";\r\n\tstring CapClass()  => R.FragmentsToday >= R.DailyFragmentCap ? \"capped\" : \"\";\r\n\tstring FeedClass() => R.FeedStarved ? \"starved\" : \"\";\r\n\tstring MeltdownStripClass() => R.CanMeltdown ? \"ready\" : \"\";\r\n\r\n\tstring StreakPipClass( int day )\r\n\t{\r\n\t\tint streak = S?.DailyStreak ?? 1;\r\n\t\tif ( day == streak ) return \"on today\";\r\n\t\treturn day < streak ? \"on\" : \"\";\r\n\t}\r\n\r\n\t// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\t// Text helpers \u2014 one composed string per element (see note at top)\r\n\t// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\r\n\tstring CoresLabel() => IdleFormat.Short( Cores );\r\n\tstring RateLabel()  => IdleFormat.Short( R.Cps );\r\n\tstring FluxLabel()  => (S?.Flux ?? 0).ToString();\r\n\tstring FragLabel()  => (ChainReactionGame.Save?.TotalFragments ?? 0).ToString();\r\n\r\n\tstring GlobalMultLabel() => \"\u00d7\" + IdleFormat.Short( R.GlobalMultiplier );\r\n\tstring ExponentLabel()   => R.ChainExponent.ToString( \"F2\" );\r\n\tstring LastChainLabel()  => $\"{R.LastChain} chests\";\r\n\tstring BestChainLabel()  => $\"{S.BestChain} chests\";\r\n\tstring BoardLabel()      => $\"{R.Size} \u00d7 {R.Size}\";\r\n\r\n\tstring FeedLabel() => R.FeedStarved ? \"starving\" : \"keeping up\";\r\n\r\n\tstring SurgeBoostLabel() => $\"SURGE \u00d7{IdleBalance.SurgeMultiplier:F0}\";\r\n\tstring BoostLabel( IdleReactor.Boost b ) => $\"{b.Label} \u00d7{b.Mult:F1}\";\r\n\tstring SecondsLabel( float s ) => $\"{s:F0}s\";\r\n\r\n\tstring CondenserLabel()\r\n\t\t=> $\"next at {IdleFormat.Short( R.FragmentCost )} Cores \u00b7 price climbs with each one\";\r\n\r\n\tstring FragTodayLabel() => $\"{R.FragmentsToday} / {R.DailyFragmentCap}\";\r\n\r\n\tstring TierBlurb( IdleBalance.ChestTier tier, bool unlocked )\r\n\t\t=> unlocked ? tier.Blurb : $\"unlocks at {IdleFormat.Short( tier.Requires )} cycle Cores\";\r\n\r\n\tstring GoldenTimerLabel() => R.GoldenTimeLeft.ToString( \"F0\" );\r\n\r\n\tstring OverloadLabel()\r\n\t\t=> R.OverloadReady ? \"\u26a1 OVERLOAD\" : $\"{R.OverloadCooldownLeft:F1}s\";\r\n\r\n\tstring OverloadSubLabel() => $\"\u00d7{IdleBalance.OverloadMultiplier:F1} burst \u00b7 SPACE\";\r\n\tstring OverloadFillPct()  => ((1f - R.OverloadCooldownFraction) * 100f).ToString( \"F0\" );\r\n\tstring UnleashLabel()     => $\"UNLEASH \u00d7{IdleBalance.SurgeMultiplier:F0}\";\r\n\r\n\tstring SurgeLabel()\r\n\t{\r\n\t\tif ( R.SurgeActive ) return $\"SURGE {R.SurgeTimeLeft:F0}s\";\r\n\t\tif ( R.SurgeReady )  return \"SURGE READY\";\r\n\t\treturn $\"CHARGE {R.Charge * 100f:F0}%\";\r\n\t}\r\n\r\n\tstring MeltdownLabel()\r\n\t\t=> R.CanMeltdown\r\n\t\t\t? $\"Meltdown available \u2014 +{R.PendingFlux} Flux\"\r\n\t\t\t: $\"Meltdown unlocks at {IdleFormat.Short( IdleBalance.MeltdownRequirement )} cycle Cores \u00b7 {IdleFormat.Short( S.CycleCores )} banked\";\r\n\r\n\tstring BuyAmountLabel( int amount ) => amount < 0 ? \"MAX\" : $\"\u00d7{amount}\";\r\n\r\n\tstring UpgradeClass( bool revealed, bool maxed, bool afford )\r\n\t{\r\n\t\tif ( !revealed ) return \"locked\";\r\n\t\tif ( maxed ) return \"maxed\";\r\n\t\treturn afford ? \"afford\" : \"poor\";\r\n\t}\r\n\r\n\tstring UpgradeIcon( IdleUpgrade up, bool revealed ) => revealed ? up.Icon : \"\ud83d\udd12\";\r\n\tstring UpgradeName( IdleUpgrade up, bool revealed ) => revealed ? up.Name : \"Locked\";\r\n\r\n\tstring UpgradeDesc( IdleUpgrade up, bool revealed )\r\n\t\t=> revealed ? up.Desc : $\"Reach {IdleFormat.Short( up.RevealAt )} lifetime Cores to reveal.\";\r\n\r\n\tstring LevelLabel( IdleUpgrade up, int lvl, bool revealed )\r\n\t{\r\n\t\tif ( !revealed ) return \"\u2014\";\r\n\t\treturn up.MaxLevel < 100 ? $\"Lv {lvl} / {up.MaxLevel}\" : $\"Lv {lvl}\";\r\n\t}\r\n\r\n\tstring CostLabel( bool maxed, double cost, int count )\r\n\t{\r\n\t\tif ( maxed ) return \"MAXED\";\r\n\t\treturn count > 1\r\n\t\t\t? $\"{IdleFormat.Short( cost )}  \u00d7{count}\"\r\n\t\t\t: IdleFormat.Short( cost );\r\n\t}\r\n\r\n\tstring PerkLevelLabel( IdlePerk p, int lvl ) => $\"Lv {lvl} / {p.MaxLevel}\";\r\n\tstring PerkCostLabel( bool maxed, int cost ) => maxed ? \"MAXED\" : $\"\ud83d\udd37 {cost}\";\r\n\r\n\tstring FluxHeadLabel() => $\"\ud83d\udd37 {S.Flux}\";\r\n\r\n\tstring FluxSubLabel()\r\n\t\t=> $\"{S.FluxEarned} earned \u00b7 \u00d7{IdleBalance.FluxMultiplier( S.FluxEarned ):F2} permanent income\";\r\n\r\n\tstring LogHeadLabel() => $\"{IdleAchievements.CompletedCount( S )} / {IdleAchievements.All.Count}\";\r\n\r\n\tstring AchIcon( IdleAchievement a, bool done ) => done ? a.Icon : \"\ud83d\udd18\";\r\n\r\n\tstring RewardLabel( IdleAchievement a )\r\n\t{\r\n\t\tif ( a.RewardFlux > 0 && a.RewardFrags > 0 ) return $\"+{a.RewardFlux}\ud83d\udd37  +{a.RewardFrags}\ud83d\udca0\";\r\n\t\tif ( a.RewardFlux > 0 ) return $\"+{a.RewardFlux}\ud83d\udd37\";\r\n\t\treturn $\"+{a.RewardFrags}\ud83d\udca0\";\r\n\t}\r\n\r\n\tstring ProgressLabel( IdleAchievement a )\r\n\t{\r\n\t\tdouble cur = a.Progress( S );\r\n\t\treturn a.BigNumbers\r\n\t\t\t? $\"{IdleFormat.Short( cur )} / {IdleFormat.Short( a.Target )}\"\r\n\t\t\t: $\"{(long)cur} / {(long)a.Target}\";\r\n\t}\r\n\r\n\t// \u2500\u2500 Modal labels \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\tstring OfflineSubLabel()\r\n\t\t=> $\"The reactor ran for {IdleFormat.Duration( R.PendingOfflineSeconds )} at {IdleFormat.Pct( R.OfflineEfficiency )} efficiency.\";\r\n\r\n\tstring OfflineAmountLabel() => \"+\" + IdleFormat.Short( R.PendingOfflineCores );\r\n\r\n\tstring OfflineFragLabel() => $\"condensing {R.PendingOfflineFrags} Fragments\";\r\n\r\n\tstring OfflineWarnLabel()\r\n\t\t=> $\"Storage filled up after {R.OfflineCapHours:F0}h \u2014 upgrade Core Storage to bank longer trips.\";\r\n\r\n\tstring DailyTitleLabel() => $\"Day {S?.DailyStreak ?? 1}\";\r\n\tstring DailyMultLabel()  => $\"\u00d7{IdleBalance.DailyReward( S?.DailyStreak ?? 1 ).mult:F1}\";\r\n\r\n\tstring DailyRewardLabel()\r\n\t{\r\n\t\tvar reward = IdleBalance.DailyReward( S?.DailyStreak ?? 1 );\r\n\t\treturn $\"for {IdleFormat.Duration( reward.seconds )}  \u00b7  +{reward.frags} Fragments\";\r\n\t}\r\n\r\n\tstring MeltPayoutLabel()  => \"+\" + R.PendingFlux;\r\n\tstring BurnedCoresLabel() => $\"{IdleFormat.Short( Cores )} Cores\";\r\n\r\n\t// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\t// Cell rendering\r\n\t// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n\r\n\tstring CellClass( IdleReactor.Cell cell, bool golden )\r\n\t{\r\n\t\tif ( golden ) return \"golden\";\r\n\r\n\t\tstring cls = cell.HasChest ? \"filled\" : \"empty\";\r\n\r\n\t\tif ( cell.FlashUntil > Time.Now )\r\n\t\t{\r\n\t\t\tcls += cell.FlashKind switch\r\n\t\t\t{\r\n\t\t\t\t3 => \" fx-drone\",\r\n\t\t\t\t2 => \" fx-kill\",\r\n\t\t\t\t_ => \" fx-blast\",\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\tif ( cell.HasDrone ) cls += \" armed\";\r\n\r\n\t\tif ( cell.HasChest )\r\n\t\t{\r\n\t\t\tcls += cell.Chest switch\r\n\t\t\t{\r\n\t\t\t\tGridManager.ChestType.Gem       => \" c-gem\",\r\n\t\t\t\tGridManager.ChestType.BombChest => \" c-bomb\",\r\n\t\t\t\tGridManager.ChestType.Locked    => \" c-locked\",\r\n\t\t\t\tGridManager.ChestType.Cursed    => \" c-cursed\",\r\n\t\t\t\tGridManager.ChestType.Void      => \" c-void\",\r\n\t\t\t\tGridManager.ChestType.Boss      => \" c-boss\",\r\n\t\t\t\t_                               => \" c-normal\",\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\treturn cls;\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Spawn pop, computed here rather than as a keyframe: the panel can rebuild\r\n\t/// at any moment and a class-driven animation would restart mid-flight.\r\n\t/// </summary>\r\n\tstring ChestStyle( IdleReactor.Cell cell )\r\n\t{\r\n\t\tfloat age = Time.Now - cell.SpawnAt;\r\n\t\tif ( age >= 0.25f || age < 0f ) return \"\";\r\n\r\n\t\tfloat scale = 0.4f + 0.6f * (age / 0.25f);\r\n\t\treturn $\"transform:scale({scale.ToString( \"F2\" )});\";\r\n\t}\r\n\r\n\t/// <summary>Drone drop \u2014 falls onto the cell as the volley arms.</summary>\r\n\tstring DroneStyle( IdleReactor.Cell cell )\r\n\t{\r\n\t\tfloat age = Time.Now - cell.DroneAt;\r\n\t\tif ( age >= 0.18f || age < 0f ) return \"\";\r\n\r\n\t\tfloat t = age / 0.18f;\r\n\t\tfloat scale = 2.0f - 1.0f * t;\r\n\t\treturn $\"transform:scale({scale.ToString( \"F2\" )});opacity:{t.ToString( \"F2\" )};\";\r\n\t}\r\n\r\n\tstring ChestGlyph( IdleReactor.Cell cell ) => cell.Chest switch\r\n\t{\r\n\t\tGridManager.ChestType.BombChest => \"\ud83d\udca3\",\r\n\t\tGridManager.ChestType.Locked    => \"\ud83d\udd12\",\r\n\t\tGridManager.ChestType.Cursed    => \"\ud83d\udc80\",\r\n\t\tGridManager.ChestType.Void      => \"\ud83c\udf11\",\r\n\t\tGridManager.ChestType.Boss      => \"\ud83d\udc51\",\r\n\t\tGridManager.ChestType.Gem       => \"\ud83d\udc8e\",\r\n\t\t_                               => \"\ud83c\udf81\",\r\n\t};\r\n}\r\n"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "styles/form/_switch.scss",
            "FileName": "_switch.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "\r\n$primary: red !default;\r\n$primary-alt: white !default;\r\n\r\n$switch-padding: 6px !default;\r\n\r\n.checkbox.switch\r\n{\r\n\tcursor: pointer;\r\n\r\n\t> .checkmark\r\n\t{\r\n\t\tfont-size: 22px;\r\n\t\tborder: 0px solid $primary;\r\n\t\tborder-radius: 100px;\r\n\t\ttext-align: center;\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\tcolor: $primary-alt;\r\n\t\tpadding: $switch-padding;\r\n\t\tpadding-right: 32px;\r\n\t\tpadding-left: $switch-padding;\r\n\t\ttransition: all 0.3s ease;\r\n\t\tbackground-color: rgba( $primary, 0.1 );\r\n\r\n\t\t> .handle\r\n\t\t{\r\n\t\t\tbackground-color: $primary-alt;\r\n\t\t\twidth: 20px;\r\n\t\t\theight: 20px;\r\n\t\t\tborder-radius: 100px;\r\n\t\t\tbox-shadow: 2px 2px 12px black;\r\n\t\t}\r\n\t}\r\n\r\n\t&.checked\r\n\t{\r\n\t\t> .checkmark\r\n\t\t{\r\n\t\t\tbackground-color: $primary;\r\n\t\t\tpadding-left: 32px;\r\n\t\t\tpadding-right: $switch-padding;\r\n\t\t}\r\n\t}\r\n\r\n\t&:active\r\n\t{\r\n\t\ttransform: scale( 0.9 );\r\n\t\ttransform-origin: 20px 50%;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "ui/controls/vectorcontrol.cs.scss",
            "FileName": "vectorcontrol.cs.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "VectorControl\r\n{\r\n\tgap: 2px;\r\n\tflex-grow: 1;\r\n}\r\n\r\nVectorControl NumberEntry\r\n{\r\n\tflex-basis: 50%;\r\n}"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "ui/controls/enumcontrol.cs.scss",
            "FileName": "enumcontrol.cs.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "EnumControl\r\n{\r\n\tgap: 2px;\r\n\tflex-grow: 1;\r\n}\r\n\r\nEnumControl DropDown,\r\nEnumControl ButtonGroup\r\n{\r\n\tborder-radius: 8px;\r\n\tbackground-color: #000a;\r\n\tflex-grow: 1;\r\n}\r\n\r\nEnumControl DropDown\r\n{\r\n\tflex-grow: 1;\r\n\tmin-height: 32px;\r\n}\r\n\r\nEnumControl ButtonGroup\r\n{\r\n\tborder-radius: 12px;\r\n\toverflow: hidden;\r\n\tmin-height: 32px;\r\n\r\n\tButton\r\n\t{\r\n\t\tflex-grow: 1;\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\tgap: 4px;\r\n\t\tcolor: #aaa;\r\n\t\tfont-size: 1rem;\r\n\t\tcursor: pointer;\r\n\r\n\t\t.icon\r\n\t\t{\r\n\t\t\tcolor: #08f;\r\n\t\t}\r\n\r\n\t\t&:hover\r\n\t\t{\r\n\t\t\tcolor: #ddd;\r\n\r\n\t\t\t.icon\r\n\t\t\t{\r\n\t\t\t\tcolor: #3af;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&:active\r\n\t\t{\r\n\t\t\tbackground-color: #04a;\r\n\t\t\tcolor: white;\r\n\t\t\ttransform: translateX( 1px ) translateY( 1px );\r\n\r\n\t\t\t.icon\r\n\t\t\t{\r\n\t\t\t\tcolor: #fff;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&.active\r\n\t\t{\r\n\t\t\tbackground-color: #08f;\r\n\t\t\tcolor: white;\r\n\t\t\tpointer-events: none;\r\n\r\n\t\t\t.icon\r\n\t\t\t{\r\n\t\t\t\tcolor: #fff;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "ui/controls/color/colorpickercontrol.cs.scss",
            "FileName": "colorpickercontrol.cs.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "ColorPickerControl\r\n{\r\n\tflex-direction: column;\r\n\tflex-shrink: 0;\r\n\tgap: 0.5rem;\r\n\tmargin: 1rem;\r\n}"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "game/idle/ui/idlehud.razor.scss",
            "FileName": "idlehud.razor.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n   THE REACTOR \u2014 Idle mode HUD\n   Palette leans teal/amber against the same deep purple-black the rest of\n   10XPLOSION uses, so it reads as \"the same game, different room\".\n   \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\nroot {\n    /* Declared so no panel in this tree ever claims the OS pointer -- a hovered\n       panel outranks Mouse.CursorType, and one that declares nothing still claims\n       the default arrow. GameCursor draws the cursor instead. */\n    cursor: none;\n    position: absolute;\n    top: 0; left: 0; right: 0; bottom: 0;\n    pointer-events: none;\n    overflow: hidden;\n    /* Sits above the wave HUD's layers \u2014 those hide themselves in this mode,\n       but ScreenShaker and PopupLayer share the same ScreenPanel. */\n    z-index: 200;\n}\n\n.idle-root {\n    position: absolute;\n    top: 0; left: 0; right: 0; bottom: 0;\n    display: flex;\n    flex-direction: column;\n    pointer-events: none;\n}\n\n/* \u2500\u2500 Backdrop \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.idle-bg {\n    position: absolute;\n    top: 0; left: 0; right: 0; bottom: 0;\n    background-color: #04070d;\n}\n\n.idle-bg-glow {\n    position: absolute;\n    top: -20%; left: 15%;\n    width: 70%;\n    height: 120%;\n    border-radius: 50%;\n    background-color: rgba(20, 130, 130, 0.10);\n    filter: blur(120px);\n    animation: idle-breathe 9s ease-in-out infinite alternate;\n    transition: background-color 0.5s;\n}\n\n.idle-bg-glow.surging {\n    background-color: rgba(251, 146, 60, 0.22);\n}\n\n@keyframes idle-breathe {\n    0%   { transform: scale(1.00); opacity: 0.75; }\n    100% { transform: scale(1.18); opacity: 1.00; }\n}\n\n.idle-particle {\n    position: absolute;\n    bottom: -12px;\n    border-radius: 50%;\n    animation: idle-rise linear infinite;\n}\n\n@keyframes idle-rise {\n    0%   { transform: translateY(0px);      opacity: 0; }\n    12%  {                                  opacity: 1; }\n    100% { transform: translateY(-1180px);  opacity: 0; }\n}\n\n/* \u2500\u2500 Top bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.idle-top {\n    position: relative;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 16px;\n    padding: 12px 22px;\n    flex-shrink: 0;\n    background-color: rgba(6, 12, 20, 0.82);\n    border-bottom: 2px solid rgba(45, 212, 191, 0.18);\n    pointer-events: all;\n    overflow: hidden;\n}\n\n.idle-back {\n    width: 46px;\n    height: 46px;\n    flex-shrink: 0;\n    border-radius: 10px;\n    border: 2px solid rgba(148, 163, 184, 0.28);\n    background-color: rgba(15, 23, 35, 0.9);\n    color: rgba(226, 232, 240, 0.85);\n    font-size: 22px;\n    font-weight: 900;\n    align-items: center;\n    justify-content: center;\n    cursor: none;\n    pointer-events: all;\n    transition: background-color 0.1s, border-color 0.1s;\n}\n.idle-back:hover {\n    background-color: rgba(45, 212, 191, 0.18);\n    border-color: rgba(45, 212, 191, 0.6);\n}\n\n/* Without an explicit no-shrink the currency row squeezes this to nothing and\n   the title wraps one letter per line. */\n.idle-brand {\n    display: flex;\n    flex-direction: column;\n    flex-shrink: 0;\n    flex-grow: 0;\n}\n\n.idle-brand-title {\n    white-space: nowrap;\n    font-size: 22px;\n    font-weight: 900;\n    letter-spacing: 0.24em;\n    color: #5eead4;\n    text-shadow: 0px 0px 18px rgba(45, 212, 191, 0.45);\n}\n\n.idle-brand-sub {\n    font-size: 11px;\n    font-weight: 700;\n    letter-spacing: 0.16em;\n    text-transform: uppercase;\n    color: rgba(148, 163, 184, 0.55);\n}\n\n.idle-currencies {\n    display: flex;\n    flex-direction: row;\n    gap: 8px;\n    margin-left: auto;\n    flex-shrink: 0;\n    flex-grow: 0;\n}\n\n.idle-cur {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 7px;\n    padding: 7px 13px;\n    border-radius: 10px;\n    background-color: rgba(12, 20, 30, 0.92);\n    border: 2px solid rgba(100, 116, 139, 0.22);\n    width: 172px;\n    flex-shrink: 0;\n    flex-grow: 0;\n    overflow: hidden;\n}\n\n.idle-cur-icon { font-size: 17px; flex-shrink: 0; }\n\n.idle-cur-val {\n    font-size: 19px;\n    font-weight: 900;\n    color: #e2e8f0;\n    white-space: nowrap;\n    flex-shrink: 0;\n}\n\n.idle-cur-lbl {\n    font-size: 9px;\n    font-weight: 800;\n    letter-spacing: 0.06em;\n    text-transform: uppercase;\n    color: rgba(148, 163, 184, 0.55);\n    margin-left: auto;\n    white-space: nowrap;\n    flex-shrink: 0;\n}\n\n.idle-cur.cores { border-color: rgba(45, 212, 191, 0.45); }\n.idle-cur.cores .idle-cur-val { color: #5eead4; }\n.idle-cur.rate  .idle-cur-val { color: #a7f3d0; font-size: 17px; }\n.idle-cur.flux  { border-color: rgba(96, 165, 250, 0.42); width: 132px; }\n.idle-cur.flux  .idle-cur-val { color: #93c5fd; }\n.idle-cur.frag  { border-color: rgba(167, 139, 250, 0.42); width: 152px; }\n.idle-cur.frag  .idle-cur-val { color: #c4b5fd; }\n\n/* \u2500\u2500 Body layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.idle-body {\n    position: relative;\n    display: flex;\n    flex-direction: row;\n    align-items: stretch;\n    gap: 14px;\n    padding: 14px 20px 16px 20px;\n    flex-grow: 1;\n    flex-shrink: 1;\n    height: 0;          /* lets flex-grow decide the height, so children can scroll */\n    overflow: hidden;\n}\n\n.idle-col { display: flex; flex-direction: column; gap: 12px; }\n\n.idle-left {\n    width: 300px;\n    flex-shrink: 0;\n    overflow-y: scroll;\n    overflow-x: hidden;\n    pointer-events: all;\n}\n\n.idle-right {\n    width: 400px;\n    flex-shrink: 0;\n    overflow: hidden;\n    pointer-events: all;\n}\n\n.idle-center {\n    flex-grow: 1;\n    align-items: center;\n    justify-content: center;\n    gap: 16px;\n    pointer-events: all;\n}\n\n/* \u2500\u2500 Info cards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.idle-card {\n    background-color: rgba(10, 17, 26, 0.88);\n    border: 2px solid rgba(100, 116, 139, 0.18);\n    border-radius: 12px;\n    padding: 12px 14px;\n    display: flex;\n    flex-direction: column;\n    gap: 6px;\n    flex-shrink: 0;\n}\n\n.idle-card-hd {\n    font-size: 11px;\n    font-weight: 900;\n    letter-spacing: 0.18em;\n    text-transform: uppercase;\n    color: rgba(94, 234, 212, 0.75);\n    margin-bottom: 4px;\n}\n\n.idle-card-note {\n    font-size: 11px;\n    font-weight: 600;\n    color: rgba(148, 163, 184, 0.65);\n}\n.idle-card-note.dim { color: rgba(251, 191, 36, 0.75); }\n\n.idle-stat-row {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: space-between;\n    gap: 8px;\n}\n\n.idle-stat-lbl {\n    font-size: 12px;\n    font-weight: 600;\n    color: rgba(148, 163, 184, 0.75);\n}\n\n.idle-stat-val {\n    font-size: 14px;\n    font-weight: 900;\n    color: #e2e8f0;\n}\n.idle-stat-val.hot    { color: #5eead4; }\n.idle-stat-val.capped  { color: #fbbf24; }\n.idle-stat-val.starved { color: #f87171; }\n\n.idle-bar-track {\n    width: 100%;\n    height: 10px;\n    border-radius: 5px;\n    background-color: rgba(30, 41, 59, 0.9);\n    overflow: hidden;\n}\n\n.idle-bar-fill {\n    height: 100%;\n    border-radius: 5px;\n    background-color: #5eead4;\n    transition: width 0.2s;\n}\n.idle-bar-fill.frag { background-color: #a78bfa; }\n\n/* Boosts */\n.idle-boost {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 8px;\n    padding: 6px 8px;\n    border-radius: 8px;\n    background-color: rgba(30, 41, 59, 0.7);\n    border-left: 3px solid rgba(94, 234, 212, 0.7);\n}\n.idle-boost.surge {\n    border-left-color: #fb923c;\n    background-color: rgba(120, 53, 15, 0.45);\n}\n.idle-boost-icon { font-size: 15px; }\n.idle-boost-lbl  { font-size: 12px; font-weight: 800; color: #e2e8f0; }\n.idle-boost-time {\n    margin-left: auto;\n    font-size: 11px;\n    font-weight: 900;\n    color: rgba(251, 191, 36, 0.9);\n}\n\n/* Chest tiers */\n.idle-tier {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 9px;\n    padding: 5px 0px;\n    opacity: 0.42;\n}\n.idle-tier.on { opacity: 1; }\n\n.idle-tier-icon { font-size: 20px; width: 26px; text-align: center; }\n.idle-tier-body { display: flex; flex-direction: column; flex-grow: 1; }\n.idle-tier-name { font-size: 12px; font-weight: 900; color: #e2e8f0; }\n.idle-tier-blurb {\n    font-size: 10px;\n    font-weight: 600;\n    color: rgba(148, 163, 184, 0.7);\n}\n.idle-tier.on .idle-tier-name { color: #5eead4; }\n\n/* \u2500\u2500 The board \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.idle-board-wrap {\n    position: relative;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    flex-shrink: 0;\n}\n\n.idle-board {\n    display: flex;\n    flex-direction: column;\n    gap: 5px;\n    padding: 14px;\n    border-radius: 16px;\n    background-color: rgba(6, 14, 22, 0.9);\n    border: 2px solid rgba(45, 212, 191, 0.22);\n    box-shadow: 0px 0px 40px rgba(13, 148, 136, 0.14);\n    transition: border-color 0.3s, box-shadow 0.3s;\n}\n\n.idle-board.surging {\n    border-color: rgba(251, 146, 60, 0.65);\n    box-shadow: 0px 0px 60px rgba(251, 146, 60, 0.35);\n}\n\n.idle-row { display: flex; flex-direction: row; gap: 5px; }\n\n.idle-cell {\n    position: relative;\n    border-radius: 9px;\n    background-color: rgba(17, 27, 40, 0.85);\n    border: 2px solid rgba(71, 85, 105, 0.28);\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    flex-shrink: 0;\n    cursor: none;\n    pointer-events: all;\n    overflow: hidden;\n}\n\n.idle-cell.empty  { background-color: rgba(13, 21, 32, 0.7); }\n.idle-cell.filled { border-color: rgba(148, 163, 184, 0.35); }\n\n.idle-chest {\n    font-size: 30px;\n    text-shadow: 0px 2px 6px rgba(0, 0, 0, 0.6);\n}\n\n/* chest type tinting */\n.idle-cell.c-normal { background-color: rgba(59, 44, 20, 0.55);  border-color: rgba(217, 160, 60, 0.45); }\n.idle-cell.c-gem    { background-color: rgba(14, 60, 78, 0.6);   border-color: rgba(56, 189, 248, 0.55); }\n.idle-cell.c-bomb   { background-color: rgba(70, 24, 24, 0.62);  border-color: rgba(248, 113, 113, 0.6); }\n.idle-cell.c-locked { background-color: rgba(45, 39, 74, 0.65);  border-color: rgba(167, 139, 250, 0.6); }\n.idle-cell.c-cursed { background-color: rgba(56, 20, 52, 0.7);   border-color: rgba(232, 121, 249, 0.65); }\n.idle-cell.c-void   { background-color: rgba(10, 10, 20, 0.95);  border-color: rgba(129, 140, 248, 0.7); }\n.idle-cell.c-boss   { background-color: rgba(80, 55, 8, 0.75);   border-color: rgba(251, 191, 36, 0.85); }\n\n/* detonation feedback */\n.idle-cell.fx-blast {\n    background-color: rgba(94, 234, 212, 0.30);\n    border-color: rgba(153, 246, 228, 0.85);\n}\n.idle-cell.fx-kill {\n    background-color: rgba(251, 191, 36, 0.55);\n    border-color: rgba(254, 240, 138, 0.95);\n}\n.idle-cell.fx-drone {\n    background-color: rgba(248, 113, 113, 0.6);\n    border-color: rgba(254, 202, 202, 0.95);\n}\n\n.idle-hits {\n    position: absolute;\n    bottom: 2px;\n    right: 4px;\n    font-size: 11px;\n    font-weight: 900;\n    color: rgba(255, 255, 255, 0.85);\n    text-shadow: 0px 1px 3px rgba(0, 0, 0, 0.9);\n}\n\n/* A drone sits on its target for a beat before the volley goes off, so the\n   explosion has something to come from. */\n.idle-drone {\n    position: absolute;\n    font-size: 26px;\n    text-shadow: 0px 0px 10px rgba(248, 113, 113, 0.9);\n}\n\n.idle-cell.armed {\n    border-color: rgba(248, 113, 113, 0.95);\n    background-color: rgba(80, 20, 20, 0.55);\n}\n\n/* golden chest */\n.idle-cell.golden {\n    background-color: rgba(120, 82, 6, 0.85);\n    border-color: #fde047;\n    box-shadow: 0px 0px 26px rgba(253, 224, 71, 0.7);\n    animation: idle-golden-pulse 0.55s ease-in-out infinite alternate;\n}\n\n@keyframes idle-golden-pulse {\n    0%   { transform: scale(1.00); }\n    100% { transform: scale(1.10); }\n}\n\n.idle-golden { font-size: 32px; }\n\n.idle-golden-timer {\n    position: absolute;\n    bottom: 1px;\n    font-size: 11px;\n    font-weight: 900;\n    color: #fef9c3;\n}\n\n/* floating values */\n.idle-float {\n    position: absolute;\n    font-size: 17px;\n    font-weight: 900;\n    color: #a7f3d0;\n    text-shadow: 0px 2px 6px rgba(0, 0, 0, 0.9);\n    pointer-events: none;\n}\n.idle-float.crit {\n    font-size: 22px;\n    color: #fde047;\n}\n\n.idle-callout {\n    position: absolute;\n    top: 40%;\n    font-size: 34px;\n    font-weight: 900;\n    letter-spacing: 0.06em;\n    color: #5eead4;\n    text-shadow: 0px 0px 22px rgba(45, 212, 191, 0.8), 0px 3px 8px rgba(0, 0, 0, 0.9);\n    pointer-events: none;\n}\n.idle-callout.big  { color: #a7f3d0; }\n.idle-callout.mega { color: #fbbf24; font-size: 40px; }\n.idle-callout.crit { color: #fb923c; font-size: 44px; }\n\n/* \u2500\u2500 Controls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.idle-controls {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 16px;\n    flex-shrink: 0;\n}\n\n.idle-overload {\n    position: relative;\n    overflow: hidden;\n    width: 290px;\n    padding-top: 14px;\n    padding-bottom: 14px;\n    border-radius: 14px;\n    border: 3px solid rgba(248, 113, 113, 0.55);\n    background-color: rgba(60, 12, 12, 0.9);\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    gap: 1px;\n    cursor: none;\n    pointer-events: all;\n    transition: transform 0.06s, border-color 0.12s;\n}\n\n.idle-overload.ready {\n    border-color: #f87171;\n    animation: idle-overload-pulse 1.4s ease-in-out infinite alternate;\n}\n.idle-overload.cooling { opacity: 0.62; cursor: none; }\n.idle-overload:active { transform: translateY(2px); }\n\n@keyframes idle-overload-pulse {\n    0%   { box-shadow: 0px 0px 12px rgba(248, 113, 113, 0.25); }\n    100% { box-shadow: 0px 0px 34px rgba(248, 113, 113, 0.65); }\n}\n\n.idle-overload-fill {\n    position: absolute;\n    left: 0; top: 0; bottom: 0;\n    background-color: rgba(248, 113, 113, 0.18);\n    pointer-events: none;\n}\n\n.idle-overload-label {\n    font-size: 20px;\n    font-weight: 900;\n    letter-spacing: 0.14em;\n    color: #fecaca;\n}\n\n.idle-overload-sub {\n    font-size: 10px;\n    font-weight: 800;\n    letter-spacing: 0.14em;\n    text-transform: uppercase;\n    color: rgba(254, 202, 202, 0.6);\n}\n\n.idle-surge-wrap {\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    width: 300px;\n}\n\n.idle-surge-track {\n    position: relative;\n    height: 34px;\n    border-radius: 9px;\n    background-color: rgba(15, 23, 35, 0.92);\n    border: 2px solid rgba(148, 163, 184, 0.24);\n    overflow: hidden;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n}\n.idle-surge-track.ready {\n    border-color: #fb923c;\n    animation: idle-overload-pulse 0.9s ease-in-out infinite alternate;\n}\n\n.idle-surge-fill {\n    position: absolute;\n    left: 0; top: 0; bottom: 0;\n    background-color: rgba(251, 146, 60, 0.42);\n    transition: width 0.2s;\n}\n\n.idle-surge-lbl {\n    position: relative;\n    font-size: 12px;\n    font-weight: 900;\n    letter-spacing: 0.16em;\n    color: #fed7aa;\n}\n\n.idle-surge-btn {\n    padding-top: 10px;\n    padding-bottom: 10px;\n    border-radius: 9px;\n    border: 2px solid #fb923c;\n    background-color: rgba(154, 52, 18, 0.85);\n    color: #ffedd5;\n    font-size: 14px;\n    font-weight: 900;\n    letter-spacing: 0.12em;\n    align-items: center;\n    justify-content: center;\n    cursor: none;\n    pointer-events: all;\n}\n.idle-surge-btn:hover { background-color: rgba(194, 65, 12, 0.95); }\n\n/* \u2500\u2500 Meltdown strip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.idle-meltdown-strip {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 14px;\n    width: 620px;\n    padding: 10px 16px;\n    border-radius: 12px;\n    background-color: rgba(10, 17, 26, 0.85);\n    border: 2px solid rgba(100, 116, 139, 0.18);\n    flex-shrink: 0;\n}\n.idle-meltdown-strip.ready {\n    border-color: rgba(251, 191, 36, 0.7);\n    background-color: rgba(69, 26, 3, 0.6);\n}\n\n.idle-melt-icon { font-size: 20px; flex-shrink: 0; }\n.idle-melt-text {\n    font-size: 12px;\n    font-weight: 700;\n    color: rgba(203, 213, 225, 0.85);\n    flex-grow: 1;\n}\n.idle-meltdown-strip.ready .idle-melt-text { color: #fde68a; font-weight: 900; }\n\n.idle-melt-bar {\n    margin-left: auto;\n    width: 180px;\n    height: 8px;\n    border-radius: 4px;\n    background-color: rgba(30, 41, 59, 0.9);\n    overflow: hidden;\n}\n.idle-melt-fill {\n    height: 100%;\n    background-color: #fbbf24;\n    border-radius: 4px;\n}\n\n.idle-melt-btn {\n    margin-left: auto;\n    padding: 8px 20px;\n    border-radius: 9px;\n    border: 2px solid #fbbf24;\n    background-color: rgba(120, 53, 15, 0.9);\n    color: #fef3c7;\n    font-size: 12px;\n    font-weight: 900;\n    letter-spacing: 0.14em;\n    cursor: none;\n    pointer-events: all;\n}\n.idle-melt-btn:hover { background-color: rgba(180, 83, 9, 0.95); }\n\n/* \u2500\u2500 Right panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.idle-tabs { display: flex; flex-direction: row; gap: 6px; flex-shrink: 0; }\n\n.idle-tab {\n    flex-grow: 1;\n    padding-top: 10px;\n    padding-bottom: 10px;\n    border-radius: 9px;\n    border: 2px solid rgba(100, 116, 139, 0.22);\n    background-color: rgba(12, 20, 30, 0.85);\n    font-size: 18px;\n    align-items: center;\n    justify-content: center;\n    cursor: none;\n    pointer-events: all;\n    opacity: 0.55;\n    transition: opacity 0.1s, border-color 0.1s;\n}\n.idle-tab:hover { opacity: 0.85; }\n.idle-tab.on {\n    opacity: 1;\n    border-color: rgba(45, 212, 191, 0.7);\n    background-color: rgba(13, 40, 44, 0.9);\n}\n.idle-tab.fluxtab.on { border-color: rgba(96, 165, 250, 0.8); background-color: rgba(15, 32, 60, 0.9); }\n\n.idle-buyrow {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 6px;\n    flex-shrink: 0;\n}\n\n.idle-buyrow-lbl {\n    font-size: 10px;\n    font-weight: 900;\n    letter-spacing: 0.16em;\n    color: rgba(148, 163, 184, 0.6);\n    margin-right: 4px;\n}\n\n.idle-buy-amt {\n    padding: 5px 14px;\n    border-radius: 7px;\n    border: 2px solid rgba(100, 116, 139, 0.22);\n    background-color: rgba(12, 20, 30, 0.85);\n    color: rgba(203, 213, 225, 0.7);\n    font-size: 11px;\n    font-weight: 900;\n    cursor: none;\n    pointer-events: all;\n}\n.idle-buy-amt.on {\n    border-color: rgba(45, 212, 191, 0.75);\n    color: #5eead4;\n    background-color: rgba(13, 40, 44, 0.9);\n}\n\n/* height:0 + flex-grow is what makes this actually scroll instead of pushing\n   its content off the bottom of the screen. */\n.idle-panel {\n    flex-grow: 1;\n    flex-shrink: 1;\n    height: 0;\n    overflow-y: scroll;\n    overflow-x: hidden;\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    padding-right: 8px;\n}\n\n/* \u2500\u2500 Upgrade rows \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.idle-up {\n    display: flex;\n    flex-direction: row;\n    gap: 12px;\n    padding: 10px 12px;\n    border-radius: 11px;\n    background-color: rgba(10, 17, 26, 0.9);\n    border: 2px solid rgba(100, 116, 139, 0.16);\n    cursor: none;\n    pointer-events: all;\n    flex-shrink: 0;\n    transition: border-color 0.1s, background-color 0.1s, transform 0.06s;\n}\n.idle-up:hover  { background-color: rgba(18, 28, 42, 0.95); }\n.idle-up:active { transform: translateY(1px); }\n\n.idle-up.afford { border-color: rgba(45, 212, 191, 0.55); }\n.idle-up.afford:hover { border-color: rgba(94, 234, 212, 0.95); }\n.idle-up.poor   { opacity: 0.62; }\n.idle-up.maxed  { border-color: rgba(251, 191, 36, 0.45); opacity: 0.9; }\n.idle-up.locked { opacity: 0.35; cursor: none; }\n.idle-up.perk.afford { border-color: rgba(96, 165, 250, 0.7); }\n.idle-up.ach    { cursor: none; }\n.idle-up.ach.done { border-color: rgba(52, 211, 153, 0.5); }\n\n.idle-up-icon {\n    font-size: 26px;\n    width: 34px;\n    flex-shrink: 0;\n    text-align: center;\n    align-items: center;\n    justify-content: center;\n}\n\n.idle-up-body {\n    display: flex;\n    flex-direction: column;\n    gap: 3px;\n    flex-grow: 1;\n}\n\n.idle-up-top {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: space-between;\n    gap: 8px;\n}\n\n.idle-up-name {\n    font-size: 14px;\n    font-weight: 900;\n    color: #e2e8f0;\n}\n\n.idle-up-lvl {\n    font-size: 11px;\n    font-weight: 800;\n    color: rgba(148, 163, 184, 0.7);\n}\n\n.idle-up-desc {\n    font-size: 11px;\n    font-weight: 600;\n    color: rgba(148, 163, 184, 0.72);\n}\n\n.idle-up-bottom {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: space-between;\n    gap: 8px;\n    margin-top: 3px;\n}\n\n.idle-up-effect {\n    font-size: 11px;\n    font-weight: 800;\n    color: rgba(94, 234, 212, 0.85);\n}\n\n.idle-up-cost {\n    font-size: 13px;\n    font-weight: 900;\n    color: #cbd5e1;\n}\n.idle-up.afford .idle-up-cost { color: #5eead4; }\n.idle-up-cost.flux  { color: #93c5fd; }\n.idle-up-cost.maxed { color: #fbbf24; }\n\n/* \u2500\u2500 Flux / log headers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.idle-flux-head {\n    display: flex;\n    flex-direction: column;\n    gap: 2px;\n    padding: 12px 14px;\n    border-radius: 11px;\n    background-color: rgba(13, 25, 46, 0.9);\n    border: 2px solid rgba(96, 165, 250, 0.3);\n    flex-shrink: 0;\n}\n\n.idle-flux-amount {\n    font-size: 22px;\n    font-weight: 900;\n    color: #93c5fd;\n}\n\n.idle-flux-sub {\n    font-size: 11px;\n    font-weight: 700;\n    color: rgba(148, 163, 184, 0.75);\n}\n\n.idle-ach-reward {\n    font-size: 11px;\n    font-weight: 900;\n    color: rgba(251, 191, 36, 0.9);\n}\n\n.idle-ach-bar {\n    width: 100%;\n    height: 6px;\n    border-radius: 3px;\n    background-color: rgba(30, 41, 59, 0.9);\n    overflow: hidden;\n    margin-top: 4px;\n}\n\n.idle-ach-fill {\n    height: 100%;\n    border-radius: 3px;\n    background-color: rgba(94, 234, 212, 0.8);\n}\n\n.idle-ach-prog {\n    font-size: 10px;\n    font-weight: 800;\n    color: rgba(148, 163, 184, 0.6);\n    margin-top: 2px;\n}\n\n/* \u2500\u2500 Toasts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.idle-toasts {\n    position: absolute;\n    top: 96px;\n    left: 50%;\n    transform: translateX(-50%);\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 6px;\n    pointer-events: none;\n}\n\n.idle-toast {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 9px;\n    padding: 8px 18px;\n    border-radius: 10px;\n    background-color: rgba(8, 14, 22, 0.94);\n    border: 2px solid rgba(100, 116, 139, 0.35);\n}\n\n.idle-toast-icon { font-size: 17px; }\n.idle-toast-text { font-size: 13px; font-weight: 800; color: #e2e8f0; }\n\n.idle-toast.idle-toast-gold        { border-color: rgba(253, 224, 71, 0.8); }\n.idle-toast.idle-toast-chain       { border-color: rgba(94, 234, 212, 0.75); }\n.idle-toast.idle-toast-crit        { border-color: rgba(251, 146, 60, 0.85); }\n.idle-toast.idle-toast-frag        { border-color: rgba(167, 139, 250, 0.8); }\n.idle-toast.idle-toast-flux        { border-color: rgba(96, 165, 250, 0.85); }\n.idle-toast.idle-toast-surge       { border-color: rgba(251, 146, 60, 0.9); }\n.idle-toast.idle-toast-big         { border-color: rgba(52, 211, 153, 0.85); }\n.idle-toast.idle-toast-achievement { border-color: rgba(251, 191, 36, 0.9); }\n.idle-toast.idle-toast-dim         { opacity: 0.6; }\n\n/* \u2500\u2500 Modals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.idle-modal-bg {\n    position: absolute;\n    top: 0; left: 0; right: 0; bottom: 0;\n    background-color: rgba(2, 4, 8, 0.86);\n    pointer-events: all;\n}\n\n.idle-modal {\n    position: absolute;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    width: 640px;\n    max-height: 88%;\n    overflow-y: auto;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 10px;\n    padding: 34px 40px;\n    border-radius: 18px;\n    background-color: rgba(9, 15, 24, 0.98);\n    border: 3px solid rgba(45, 212, 191, 0.45);\n    box-shadow: 0px 0px 70px rgba(13, 148, 136, 0.28);\n    pointer-events: all;\n}\n\n.idle-modal.meltdown { border-color: rgba(251, 191, 36, 0.6); box-shadow: 0px 0px 70px rgba(180, 83, 9, 0.35); }\n.idle-modal.daily    { border-color: rgba(167, 139, 250, 0.6); }\n.idle-modal.intro    { width: 720px; }\n\n.idle-modal-eyebrow {\n    font-size: 11px;\n    font-weight: 900;\n    letter-spacing: 0.30em;\n    text-transform: uppercase;\n    color: rgba(148, 163, 184, 0.55);\n}\n\n.idle-modal-title {\n    font-size: 34px;\n    font-weight: 900;\n    letter-spacing: 0.06em;\n    color: #5eead4;\n    text-shadow: 0px 0px 24px rgba(45, 212, 191, 0.4);\n}\n.idle-modal.meltdown .idle-modal-title { color: #fbbf24; }\n.idle-modal.daily    .idle-modal-title { color: #c4b5fd; }\n\n.idle-modal-sub {\n    font-size: 13px;\n    font-weight: 600;\n    color: rgba(203, 213, 225, 0.75);\n    text-align: center;\n    max-width: 520px;\n    margin-bottom: 8px;\n}\n\n.idle-modal-btn {\n    margin-top: 14px;\n    padding: 14px 40px;\n    border-radius: 11px;\n    border: 2px solid #2dd4bf;\n    background-color: rgba(13, 78, 74, 0.9);\n    color: #ccfbf1;\n    font-size: 16px;\n    font-weight: 900;\n    letter-spacing: 0.16em;\n    align-items: center;\n    justify-content: center;\n    cursor: none;\n    pointer-events: all;\n}\n.idle-modal-btn:hover { background-color: rgba(15, 118, 110, 0.95); }\n\n.idle-modal-btn.ghost {\n    border-color: rgba(148, 163, 184, 0.4);\n    background-color: rgba(30, 41, 59, 0.7);\n    color: rgba(203, 213, 225, 0.85);\n}\n.idle-modal-btn.danger {\n    border-color: #fbbf24;\n    background-color: rgba(146, 64, 14, 0.9);\n    color: #fef3c7;\n}\n.idle-modal-btn.danger:hover { background-color: rgba(180, 83, 9, 0.95); }\n\n.idle-modal-btns { display: flex; flex-direction: row; gap: 14px; }\n\n/* offline report */\n.idle-offline-haul {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 2px;\n    padding: 18px 44px;\n    border-radius: 14px;\n    background-color: rgba(13, 40, 44, 0.7);\n    border: 2px solid rgba(45, 212, 191, 0.35);\n    margin-top: 6px;\n}\n\n.idle-offline-amount {\n    font-size: 46px;\n    font-weight: 900;\n    color: #5eead4;\n    text-shadow: 0px 0px 26px rgba(45, 212, 191, 0.5);\n}\n\n.idle-offline-lbl {\n    font-size: 12px;\n    font-weight: 900;\n    letter-spacing: 0.20em;\n    color: rgba(148, 163, 184, 0.7);\n}\n\n.idle-offline-extra {\n    font-size: 13px;\n    font-weight: 800;\n    color: #c4b5fd;\n    margin-top: 6px;\n}\n\n.idle-offline-warn {\n    font-size: 12px;\n    font-weight: 700;\n    color: rgba(251, 191, 36, 0.9);\n    text-align: center;\n    max-width: 480px;\n    margin-top: 8px;\n}\n\n/* daily */\n.idle-streak-row { display: flex; flex-direction: row; gap: 8px; margin-top: 6px; }\n\n.idle-streak-pip {\n    width: 44px;\n    height: 44px;\n    border-radius: 10px;\n    background-color: rgba(30, 41, 59, 0.8);\n    border: 2px solid rgba(100, 116, 139, 0.25);\n    align-items: center;\n    justify-content: center;\n    font-size: 15px;\n    font-weight: 900;\n    color: rgba(148, 163, 184, 0.6);\n}\n.idle-streak-pip.on {\n    background-color: rgba(76, 29, 149, 0.6);\n    border-color: rgba(167, 139, 250, 0.7);\n    color: #ddd6fe;\n}\n.idle-streak-pip.today {\n    border-color: #fbbf24;\n    color: #fde68a;\n    animation: idle-overload-pulse 1.1s ease-in-out infinite alternate;\n}\n\n.idle-daily-reward {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    margin-top: 14px;\n}\n.idle-daily-mult {\n    font-size: 44px;\n    font-weight: 900;\n    color: #fbbf24;\n}\n.idle-daily-lbl {\n    font-size: 13px;\n    font-weight: 800;\n    color: rgba(203, 213, 225, 0.8);\n}\n\n/* meltdown */\n.idle-melt-payout {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 10px;\n    padding: 14px 38px;\n    border-radius: 14px;\n    background-color: rgba(30, 41, 90, 0.6);\n    border: 2px solid rgba(96, 165, 250, 0.45);\n}\n.idle-melt-payout-val { font-size: 44px; font-weight: 900; color: #93c5fd; }\n.idle-melt-payout-lbl { font-size: 15px; font-weight: 900; color: rgba(148, 163, 184, 0.85); }\n\n.idle-melt-cols {\n    display: flex;\n    flex-direction: row;\n    gap: 16px;\n    margin-top: 14px;\n    width: 100%;\n}\n\n.idle-melt-col {\n    flex-grow: 1;\n    display: flex;\n    flex-direction: column;\n    gap: 4px;\n    padding: 12px 14px;\n    border-radius: 11px;\n    background-color: rgba(15, 23, 35, 0.85);\n    border: 2px solid rgba(100, 116, 139, 0.2);\n}\n.idle-melt-col.keep { border-color: rgba(52, 211, 153, 0.4); }\n.idle-melt-col.lose { border-color: rgba(248, 113, 113, 0.4); }\n\n.idle-melt-col-hd {\n    font-size: 11px;\n    font-weight: 900;\n    letter-spacing: 0.16em;\n    color: rgba(203, 213, 225, 0.8);\n    margin-bottom: 4px;\n}\n\n.idle-melt-line {\n    font-size: 12px;\n    font-weight: 700;\n    color: rgba(148, 163, 184, 0.85);\n}\n\n/* intro */\n.idle-intro-steps {\n    display: flex;\n    flex-direction: column;\n    gap: 12px;\n    margin-top: 10px;\n    width: 100%;\n}\n\n.idle-intro-step {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 14px;\n    padding: 10px 14px;\n    border-radius: 11px;\n    background-color: rgba(15, 23, 35, 0.8);\n    border-left: 3px solid rgba(45, 212, 191, 0.6);\n}\n\n.idle-intro-num {\n    width: 28px;\n    height: 28px;\n    flex-shrink: 0;\n    border-radius: 50%;\n    background-color: rgba(13, 78, 74, 0.9);\n    border: 2px solid rgba(45, 212, 191, 0.6);\n    align-items: center;\n    justify-content: center;\n    font-size: 13px;\n    font-weight: 900;\n    color: #99f6e4;\n}\n\n.idle-intro-txt {\n    font-size: 13px;\n    font-weight: 600;\n    color: rgba(203, 213, 225, 0.9);\n}\n\n\n/* \u2500\u2500 Board shake \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* Own keyframes rather than reusing Gamehud's, so this stylesheet stands\n   on its own if that file ever changes. */\n\n.idle-board.idle-shake-1 { animation: idle-shake-1 0.28s ease-out; }\n.idle-board.idle-shake-2 { animation: idle-shake-2 0.28s ease-out; }\n.idle-board.idle-shake-3 { animation: idle-shake-3 0.45s ease-out; }\n\n@keyframes idle-shake-1 {\n    0%   { transform: translate(0px, 0px); }\n    30%  { transform: translate(-2px, 1px); }\n    60%  { transform: translate(2px, -1px); }\n    100% { transform: translate(0px, 0px); }\n}\n\n@keyframes idle-shake-2 {\n    0%   { transform: translate(0px, 0px) rotate(0deg); }\n    20%  { transform: translate(-4px, 2px) rotate(-0.3deg); }\n    45%  { transform: translate(4px, -3px) rotate(0.3deg); }\n    70%  { transform: translate(-3px, -1px) rotate(-0.2deg); }\n    100% { transform: translate(0px, 0px) rotate(0deg); }\n}\n\n@keyframes idle-shake-3 {\n    0%   { transform: translate(0px, 0px) rotate(0deg) scale(1.00); }\n    15%  { transform: translate(-7px, 4px) rotate(-0.6deg) scale(1.01); }\n    35%  { transform: translate(7px, -5px) rotate(0.6deg) scale(1.02); }\n    55%  { transform: translate(-5px, -3px) rotate(-0.4deg) scale(1.01); }\n    78%  { transform: translate(4px, 2px) rotate(0.3deg) scale(1.00); }\n    100% { transform: translate(0px, 0px) rotate(0deg) scale(1.00); }\n}\n"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "Game/2D/UI/MainMenuTutorial.razor",
            "FileName": "MainMenuTutorial.razor",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "@using System;\r\n@using Sandbox;\r\n@using Sandbox.Game.Menu\r\n@using Sandbox.UI;\r\n@inherits PanelComponent\r\n@namespace Sandbox\r\n\r\n<root>\r\n    @if(_active)\r\n    {\r\n\t\t@* \u2500\u2500 Debug card \u2500\u2500 *@\r\n\t\t@if(_debugMode)\r\n\t\t{\r\n\t\t\t<div class=\"tut-debug-card\">\r\n\t\t\t\t<div class=\"tut-debug-title\">MM TUT \u2014 Step @_step</div>\r\n\t\t\t\t<div class=\"tut-debug-row\">\r\n\t\t\t\t\t<span>BL: @_debugBL.ToString(\"F1\")%</span>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugBL -= 1f; StateHasChanged(); })>\u25c0</button>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugBL += 1f; StateHasChanged(); })>\u25b6</button>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"tut-debug-row\">\r\n\t\t\t\t\t<span>BY: @_debugBY.ToString(\"F0\")px</span>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugBY -= 10f; StateHasChanged(); })>\u25b2</button>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugBY += 10f; StateHasChanged(); })>\u25bc</button>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"tut-debug-row\">\r\n\t\t\t\t\t<span>BR: @_debugBR.ToString(\"F1\")%</span>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugBR -= 1f; StateHasChanged(); })>\u25c0</button>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugBR += 1f; StateHasChanged(); })>\u25b6</button>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"tut-debug-row\">\r\n\t\t\t\t\t<span>HX: @_debugHX.ToString(\"F1\")%</span>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugHX -= 1f; StateHasChanged(); })>\u25c0</button>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugHX += 1f; StateHasChanged(); })>\u25b6</button>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"tut-debug-row\">\r\n\t\t\t\t\t<span>HY: @_debugHY.ToString(\"F0\")px</span>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugHY -= 10f; StateHasChanged(); })>\u25b2</button>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugHY += 10f; StateHasChanged(); })>\u25bc</button>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"tut-debug-row\">\r\n\t\t\t\t\t<span>HW: @_debugHW.ToString(\"F0\")px</span>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugHW -= 10f; StateHasChanged(); })>\u25c0</button>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugHW += 10f; StateHasChanged(); })>\u25b6</button>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"tut-debug-row\">\r\n\t\t\t\t\t<span>HH: @_debugHH.ToString(\"F0\")px</span>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugHH -= 10f; StateHasChanged(); })>\u25b2</button>\r\n\t\t\t\t\t<button class=\"tut-debug-btn\" onclick=@(() => { _debugHH += 10f; StateHasChanged(); })>\u25bc</button>\r\n\t\t\t\t</div>\r\n\t\t\t\t<button class=\"tut-debug-log\" onclick=@LogCurrentValues>\ud83d\udccb Log</button>\r\n\t\t\t</div>\r\n\t\t}\r\n\r\n\t\t@* \u2500\u2500 Dim layer \u2500\u2500 *@\r\n\t\t<div class=\"tut-dim\"></div>\r\n\r\n\t\t@* \u2500\u2500 Highlight \u2500\u2500 *@\r\n\t\t@if(_step < _steps.Length && _steps[_step].HW > 0)\r\n\t\t{\r\n\t\t\tvar s = _steps[_step];\r\n\t\t\tfloat hx = _debugHX != 0f ? _debugHX : s.HX;\r\n\t\t\tfloat hy = _debugHY != 0f ? _debugHY : s.HY;\r\n\t\t\tfloat hw = _debugHW != 0f ? _debugHW : s.HW;\r\n\t\t\tfloat hh = _debugHH != 0f ? _debugHH : s.HH;\r\n\t\t\t<div class=\"tut-highlight\"\r\n\t\t\t\t\tstyle=\"left:@hx.ToString(\"F1\")%;top:@hy.ToString(\"F0\")px;width:@hw.ToString(\"F0\")px;height:@hh.ToString(\"F0\")px;\">\r\n\t\t\t</div>\r\n\t\t}\r\n\r\n\t\t@* \u2500\u2500 Instruction box \u2500\u2500 *@\r\n\t\t@if(_step < _steps.Length)\r\n\t\t{\r\n\t\t\tvar s = _steps[_step];\r\n\t\t\tfloat bl = _debugBL != 0f ? _debugBL : s.BL;\r\n\t\t\tfloat br = _debugBR != 0f ? _debugBR : s.BR;\r\n\t\t\tfloat by = _debugBY != 0f ? _debugBY : s.BY;\r\n\r\n\t\t\tfloat boxWidthPct = s.BW > 0f ? (s.BW / 1920f * 100f) : (380f / 1920f * 100f);\r\n\r\n\t\t\tstring posStyle = br != 0f\r\n\t\t\t\t? $\"left:{(100f - br - boxWidthPct).ToString(\"F1\")}%;\"\r\n\t\t\t\t: $\"left:{bl.ToString(\"F1\")}%;\";\r\n\t\t\tstring boxStyle = $\"{posStyle}top:{by.ToString(\"F0\")}px;{(s.BW > 0f ? $\"width:{s.BW.ToString(\"F0\")}px;\" : \"\")}\";\r\n\r\n\t\t\t<div class=\"tut-box\" style=\"@boxStyle\">\r\n\t\t\t\t<div class=\"tut-step-count\">@(_step + 1) / @_steps.Length</div>\r\n\t\t\t\t<div class=\"tut-title\">@s.Title</div>\r\n\t\t\t\t<div class=\"tut-text\">@s.Text</div>\r\n\r\n\t\t\t\t@* Action hint \u2014 shown when player must act *@\r\n\t\t\t\t@if(s.Trigger == MMTutTrigger.HeartPurchased)\r\n\t\t\t\t{\r\n\t\t\t\t\t<div class=\"tut-action-hint\">\ud83d\udc46 @s.ActionHint</div>\r\n\t\t\t\t}\r\n\r\n\t\t\t\t@* Next button \u2014 shown on Any steps *@\r\n\t\t\t\t@if(s.Trigger == MMTutTrigger.Any)\r\n\t\t\t\t{\r\n\t\t\t\t\t<button class=\"tut-btn-next\" onclick=@NextStep>Got it \u2192</button>\r\n\t\t\t\t}\r\n\r\n\t\t\t\t@* Final step \u2014 Play button *@\r\n\t\t\t\t@if(s.Trigger == MMTutTrigger.Done)\r\n\t\t\t\t{\r\n\t\t\t\t\t<button class=\"tut-btn-next\" onclick=@OnPlayAndComplete>\u25b6 Let's play!</button>\r\n\t\t\t\t}\r\n\t\t\t</div>\r\n\t\t}\r\n    }\r\n</root>\r\n\r\n@code {\r\n    public static MainMenuTutorial Instance { get; private set; }\r\n    public bool IsActive => _active;\r\n\r\n    bool _active = false;\r\n\tpublic int Step => _step;\r\n    int  _step   = 0;\r\n\r\n    // \u2500\u2500 Debug mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 //\r\n    const bool _debugMode = false; // set false before release\r\n    float _debugBL = 0f;\r\n    float _debugBR = 0f;\r\n    float _debugBY = 0f;\r\n    float _debugHX = 0f;\r\n    float _debugHY = 0f;\r\n    float _debugHW = 0f;\r\n    float _debugHH = 0f;\r\n\r\n    void LogCurrentValues()\r\n    {\r\n        Log.Info($\"// MM Step {_step} \u2014 {_steps[_step].Title}\");\r\n        Log.Info($\"BL={_debugBL:F1}f, BY={_debugBY:F0}f, BR={_debugBR:F1}f,\");\r\n        Log.Info($\"HX={_debugHX:F1}f, HY={_debugHY:F0}f, HW={_debugHW:F0}f, HH={_debugHH:F0}f\");\r\n    }\r\n\r\n    // \u2500\u2500 Trigger types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 //\r\n    public enum MMTutTrigger\r\n    {\r\n        Any,           // advances via Got it button\r\n        HeartPurchased, // advances when player buys Heart consumable\r\n        Done,          // final step \u2014 shows Play button\r\n    }\r\n\r\n    // \u2500\u2500 Step data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 //\r\n    public struct MMTutStep\r\n    {\r\n        public string Title;\r\n        public string Text;\r\n        public string ActionHint;\r\n        public MMTutTrigger Trigger;\r\n        public float HX, HY, HW, HH; // highlight\r\n        public float BL, BR, BY, BW;  // box position\r\n    }\r\n\r\n    // \u2500\u2500 Steps \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 //\r\n    // Positions are placeholder \u2014 tune with debug card in-game\r\n    static readonly MMTutStep[] _steps = new MMTutStep[]\r\n    {\r\n        // Step 1 \u2014 Welcome\r\n        new MMTutStep {\r\n            Title   = \"Welcome to 10XPLOSION!\",\r\n            Text    = \"Here's a quick tour of how the game works. It'll take 30 seconds.\",\r\n            Trigger = MMTutTrigger.Any,\r\n            HW = 0,\r\n            BL = 47f, BY = 370f\r\n        },\r\n\r\n        // Step 2 \u2014 Play button\r\n        new MMTutStep {\r\n            Title   = \"Play runs to earn \ud83d\udca0 Fragments\",\r\n            Text    = \"Fragments are the game's currency. The further you go each run, the more you earn.\",\r\n            Trigger = MMTutTrigger.Any,\r\n            HW = 0,\r\n            BL = 47f, BY = 370f\r\n        },\r\n\r\n        // Step 3 \u2014 Shop \u2192 Modifiers\r\n        new MMTutStep {\r\n            Title   = \"The Shop \u2014 Modifiers \ud83c\udfb2\",\r\n            Text    = \"Spend fragments to unlock Modifiers. These permanently strengthen every run you play. Unlock once, keep forever.\",\r\n            Trigger = MMTutTrigger.Any,\r\n            HX = 40f, HY = 200f, HW = 110f, HH = 40f,\r\n            BR = 61f, BY = 120f\r\n        },\r\n\r\n        // Step 4 \u2014 Consumables explanation\r\n        new MMTutStep {\r\n            Title   = \"Consumables \ud83c\udf81\",\r\n            Text    = \"Consumables are single-use items you bring into a run. Buy them once, use them at any time during a run. A bar will appear in-game when you own one.\",\r\n            Trigger = MMTutTrigger.Any,\r\n            HX = 46f, HY = 200f, HW = 130f, HH = 40f,\r\n            BR = 61f, BY = 110\r\n        },\r\n\r\n        // Step 5 \u2014 Buy Heart (action step)\r\n        new MMTutStep {\r\n            Title   = \"Get yourself a Heart \u2764\ufe0f\",\r\n            Text    = \"We've added 100 \ud83d\udca0 to your balance. Buy the Heart consumable \u2014 it gives you an extra life mid-run when you need it most.\",\r\n            ActionHint = \"Click the buy button on the Heart card\",\r\n            Trigger = MMTutTrigger.HeartPurchased,\r\n            HX = 65f, HY = 860f, HW = 80f, HH = 70f,\r\n            BR = 7f, BY = 800f\r\n        },\r\n\r\n        // Step 6 \u2014 Pet tab\r\n        new MMTutStep {\r\n            Title = \"Your secret package \ud83d\udce6\",\r\n            Text = \"Play 3 runs to unlock what's inside. This section is for cosmetic skins \u2014 coming soon.\",\r\n    \t\tTrigger = MMTutTrigger.Any,\r\n            HX = 53f, HY = 360f, HW = 70f, HH = 40f,\r\n            BR = 50f, BY = 340f\r\n        },\r\n\r\n        // Step 7 \u2014 Bombs\r\n        new MMTutStep {\r\n            Title   = \"Unlock new Bombs \ud83d\udca3\",\r\n            Text    = \"Different bomb types open up new chain reaction strategies. Mix and match to find your playstyle.\",\r\n            Trigger = MMTutTrigger.Any,\r\n            HX = 57f, HY = 220f, HW = 90f, HH = 40f,\r\n            BR = 5f, BY = 130f\r\n        },\r\n\r\n        // Step 8 \u2014 Leaderboard\r\n        new MMTutStep {\r\n            Title   = \"Compete Globally \ud83c\udfc5\",\r\n            Text    = \"The leaderboard tracks your best score, chain reactions, and pet level. How far can you go?\",\r\n            Trigger = MMTutTrigger.Any,\r\n            HX = 75f, HY = 400f, HW = 10f, HH = 10f,\r\n            BR = 5f, BY = 160f\r\n        },\r\n\r\n        // Step 9 \u2014 Done\r\n        new MMTutStep {\r\n            Title   = \"You're ready. \ud83d\udca5\",\r\n            Text    = \"Play runs \u2192 earn fragments \u2192 unlock things \u2192 become stronger. Good luck!\",\r\n            Trigger = MMTutTrigger.Done,\r\n\t\t\tBR = 20f, BY = 370f\r\n        },\r\n    };\r\n\r\n    // \u2500\u2500 Lifecycle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 //\r\n    protected override void OnStart()\r\n    {\r\n        Instance = this;\r\n        _active  = false;\r\n    }\r\n\r\n    public void Activate(PlayerSave save)\r\n    {\r\n        //var save = ChainReactionGame.Save ?? PlayerSave.Load();\r\n        if (save?.MenuTutorialCompleted == true) return;\r\n\r\n        _step   = save?.MenuTutorialStep ?? 0;\r\n        _active = true;\r\n        ResetDebug();\r\n\r\n\t\t// If resuming at step 4 (Buy Heart) but heart already purchased, skip it\r\n\t\tif (_step == 4 && (save?.GetConsumableCount(ConsumableId.Heart) ?? 0) > 0)\r\n\t\t\t_step = 5;\r\n\r\n        // Step 3 requires shop/mods to be visible \u2014 navigate there\r\n        ApplySideEffect(_step);\r\n        StateHasChanged();\r\n    }\r\n\r\n    // \u2500\u2500 Called from MainMenu when Heart is bought \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 //\r\n    public void OnHeartPurchased()\r\n    {\r\n        if (!_active || _step >= _steps.Length) return;\r\n        if (_steps[_step].Trigger != MMTutTrigger.HeartPurchased) return;\r\n        NextStep();\r\n    }\r\n\r\n    // \u2500\u2500 Step advancement \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 //\r\n  \tvoid NextStep() \r\n\t{\r\n\t\t_step++;\r\n\t\tResetDebug();\r\n\r\n\t\tif (_step >= _steps.Length)\r\n\t\t{\r\n\t\t\tComplete();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Persist current step so we can resume after crash - save tuto step\r\n\t\tvar save = PlayerSave.Load();\r\n\t\tif (save != null) \r\n\t\t{ \r\n\t\t\tsave.MenuTutorialStep = _step; \r\n\t\t\tsave.Save(); \r\n\t\t}\r\n\r\n\t\tApplySideEffect(_step);\r\n\t\tSound.Play(\"UI_Button\");\r\n\t\tStateHasChanged();\r\n}\r\n\r\n    // \u2500\u2500 Side effects \u2014 navigate tabs automatically \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 //\r\n    void ApplySideEffect(int step)\r\n    {\r\n        var mm = MainMenu.Instance;\r\n        if (mm == null) return;\r\n\r\n        switch(step)\r\n        {\r\n            case 2: // Show shop modifiers\r\n                mm.SetTabPublic(\"shop\");\r\n                mm.SetShopTabPublic(\"mods\");\r\n                // Grant frags on step before heart purchase\r\n                break;\r\n\r\n            case 3: // Show consumables\r\n                mm.SetTabPublic(\"shop\");\r\n                mm.SetShopTabPublic(\"consumables\");\r\n                break;\r\n\r\n\t\t    case 4: // Buy Heart \u2014 shop consumables + grant frags if not already granted\r\n            var save = ChainReactionGame.Save ?? PlayerSave.Load();\r\n            if (save != null && !save.TutorialHeartFragsGranted)\r\n            {\r\n                save.TotalFragments += 100;\r\n                save.TutorialHeartFragsGranted = true;\r\n                save.Save();\r\n            }\r\n            mm.SetTabPublic(\"shop\");\r\n            mm.SetShopTabPublic(\"consumables\");\r\n            mm.ReloadSave();\r\n            break;\r\n\t\t\r\n            case 5: // Pet tab\r\n\t\t\t    mm.SetTabPublic(\"shop\");\r\n                mm.SetShopTabPublic(\"pets\");\r\n                break;\r\n\r\n            case 6: // Bombs\r\n                mm.SetTabPublic(\"shop\");\r\n                mm.SetShopTabPublic(\"bombs\");\r\n                break;\r\n\r\n            case 7: // Leaderboard\r\n                mm.SetTabPublic(\"lb\");\r\n                break;\r\n\t\t\tcase 8: // Done \u2014 no navigation needed\r\n            break;\r\n        }\r\n    }\r\n\r\n    void Skip() => Complete();\r\n\r\n    void OnPlayAndComplete()\r\n    {\r\n        Complete();\r\n        MainMenu.Instance?.StartGamePublic();\r\n    }\r\n\r\n    void Complete()\r\n    {\r\n        _active = false;\r\n\r\n\t\t// Get save from MainMenu directly\r\n\t\tvar mm = MainMenu.Instance;\r\n\r\n\t\t// just use ChainReactionGame.Save or load fresh\r\n\t\tvar save = PlayerSave.Load();\r\n\r\n\t\tif (save != null)\r\n\t\t{\r\n\t\t\tsave.MenuTutorialCompleted = true;\r\n\t\t\tsave.MenuTutorialStep = 0;\r\n\t\t\tsave.Save();\r\n\t\t}\r\n\t\tStateHasChanged();\r\n    }\r\n\r\n    void ResetDebug()\r\n    {\r\n        _debugBL = 0f; _debugBR = 0f; _debugBY = 0f;\r\n        _debugHX = 0f; _debugHY = 0f; _debugHW = 0f; _debugHH = 0f;\r\n    }\r\n\r\n\tpublic void ForceReset()\r\n\t{\r\n\t\t_active = false;\r\n\t\t_step   = 0;\r\n\t\tResetDebug();\r\n\t\tStateHasChanged();\r\n\t}\r\n\r\n    protected override int BuildHash() =>\r\n        System.HashCode.Combine(_active, _step, _debugBL, _debugBY, _debugHX, _debugHY, _debugHW, _debugHH);\r\n}"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "Game/2D/GameMode.cs",
            "FileName": "GameMode.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "using Sandbox;\r\n\r\npublic enum GameMode\r\n{\r\n    Wave,\r\n    Survival,\r\n    Idle,       // Reactor mode \u2014 the self-running chain reaction economy\r\n}"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "Game/Idle/IdlePet.cs",
            "FileName": "IdlePet.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "using System;\n\n/// <summary>\n/// The pet is the Reactor's Fragment sink.\n///\n/// Cores buy upgrades that reset on Meltdown, and Flux buys perks that never do.\n/// The pet sits outside both: Fragments condense slowly, on a daily ceiling, and\n/// go into a permanent output multiplier that takes weeks rather than hours to\n/// finish. It is the long-horizon goal that survives every reset.\n/// </summary>\npublic static class IdlePet\n{\n\tpublic const int MaxLevel = 6;\n\n\t/// <summary>Fragments needed to go from <paramref name=\"level\"/> to the next one.</summary>\n\tpublic static int CostToUpgrade( int level ) => level switch\n\t{\n\t\t1 => 120,\n\t\t2 => 300,\n\t\t3 => 700,\n\t\t4 => 1_500,\n\t\t5 => 3_200,\n\t\t_ => 0,\n\t};\n\n\t/// <summary>Total Fragments to take a fresh pet all the way to level 6.</summary>\n\tpublic static int TotalCost\n\t{\n\t\tget\n\t\t{\n\t\t\tint total = 0;\n\t\t\tfor ( int lvl = 1; lvl < MaxLevel; lvl++ ) total += CostToUpgrade( lvl );\n\t\t\treturn total;\n\t\t}\n\t}\n\n\tpublic static bool IsMaxed( int level ) => level >= MaxLevel;\n\n\t/// <summary>Reactor output multiplier granted by the pet, before Pet Link.</summary>\n\tpublic static double OutputBonus( int level ) => 0.15 * Math.Max( 0, level - 1 );\n\n\tpublic static string Name( int level ) => level switch\n\t{\n\t\t1 => \"Mysterious Egg\",\n\t\t2 => \"Cat\",\n\t\t3 => \"Wolf\",\n\t\t4 => \"Tiger\",\n\t\t5 => \"Baby Drake\",\n\t\t_ => \"Dragon\",\n\t};\n\n\tpublic static string Emoji( int level ) => level switch\n\t{\n\t\t1 => \"\ud83e\udd5a\",\n\t\t2 => \"\ud83d\udc31\",\n\t\t3 => \"\ud83d\udc3a\",\n\t\t4 => \"\ud83d\udc2f\",\n\t\t5 => \"\ud83d\udc23\",\n\t\t_ => \"\ud83d\udc09\",\n\t};\n\n\tpublic static string Blurb( int level ) => level switch\n\t{\n\t\t1 => \"Something is moving in there. Feed it Fragments and find out what.\",\n\t\t2 => \"It watches the reactor and purrs at every chain.\",\n\t\t3 => \"Faster, hungrier, and it has started organising the drones.\",\n\t\t4 => \"Big enough now to shove chests into the blast radius itself.\",\n\t\t5 => \"Wings, and the beginnings of a temper.\",\n\t\t_ => \"Fully grown. The reactor runs hot around it.\",\n\t};\n}\n"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "Game/Idle/IdleSave.cs",
            "FileName": "IdleSave.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\n\n/// <summary>\n/// Persistent state for Reactor (Idle) mode.\n/// Serialized as a nested object inside PlayerSave \u2014 see PlayerSave.Idle.\n/// Everything here survives a Meltdown except what ResetForMeltdown() clears.\n/// </summary>\npublic class IdleSave\n{\n\t// \u2500\u2500 Unlock / onboarding \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\tpublic bool Unlocked      { get; set; } = false;  // granted after the first finished run\n\tpublic bool IntroShown    { get; set; } = false;  // first-entry explainer\n\tpublic bool MeltdownSeen  { get; set; } = false;  // has the prestige panel ever been opened\n\n\t// \u2500\u2500 Currencies \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\tpublic double Cores         { get; set; } = 0;   // spendable, wiped by Meltdown\n\tpublic double CycleCores    { get; set; } = 0;   // earned since last Meltdown \u2014 drives Flux payout\n\tpublic double AllTimeCores  { get; set; } = 0;   // never resets\n\tpublic int    Flux          { get; set; } = 0;   // unspent prestige currency\n\tpublic int    FluxEarned    { get; set; } = 0;   // lifetime, for milestones\n\tpublic int    Meltdowns     { get; set; } = 0;\n\n\t// \u2500\u2500 Upgrade levels \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\t// Keys are IdleUpgradeId.ToString(); missing key == level 0.\n\tpublic Dictionary<string, int> Upgrades { get; set; } = new();\n\tpublic Dictionary<string, int> Perks    { get; set; } = new();\n\n\t// \u2500\u2500 Offline bookkeeping \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\tpublic long   LastSeenUnix   { get; set; } = 0;\n\tpublic double MeasuredCps    { get; set; } = 0;   // smoothed cores/sec, used for offline payout\n\tpublic double BestCps        { get; set; } = 0;\n\n\t// Pending offline haul, staged so the welcome-back panel can show it\n\tpublic double PendingOfflineCores   { get; set; } = 0;\n\tpublic double PendingOfflineSeconds { get; set; } = 0;\n\tpublic int    PendingOfflineFrags   { get; set; } = 0;\n\n\t// \u2500\u2500 Daily loop \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\tpublic string LastDailyDate { get; set; } = \"\";\n\tpublic int    DailyStreak   { get; set; } = 0;\n\tpublic bool   DailyClaimed  { get; set; } = false;\n\n\t// \u2500\u2500 Fragment drip (own bucket \u2014 does not touch the pet farm cap) \u2500\u2500\n\tpublic int    TodayReactorFragments { get; set; } = 0;\n\tpublic string LastFragDate          { get; set; } = \"\";\n\tpublic double FragProgress          { get; set; } = 0; // cores banked toward the next fragment\n\n\t// \u2500\u2500 Records / milestones \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\tpublic int    TotalDetonations { get; set; } = 0;\n\tpublic long   TotalChestsBlown { get; set; } = 0;\n\tpublic int    BestChain        { get; set; } = 0;\n\tpublic double BestSingleBlast  { get; set; } = 0;\n\tpublic int    GoldenGrabbed    { get; set; } = 0;\n\tpublic double SecondsRun       { get; set; } = 0;\n\tpublic List<string> Achievements { get; set; } = new();\n\n\t// \u2500\u2500 Upgrade helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\tpublic int Level( IdleUpgradeId id )\n\t\t=> Upgrades.TryGetValue( id.ToString(), out int v ) ? v : 0;\n\n\tpublic void SetLevel( IdleUpgradeId id, int level )\n\t\t=> Upgrades[id.ToString()] = Math.Max( 0, level );\n\n\tpublic int PerkLevel( IdlePerkId id )\n\t\t=> Perks.TryGetValue( id.ToString(), out int v ) ? v : 0;\n\n\tpublic void SetPerkLevel( IdlePerkId id, int level )\n\t\t=> Perks[id.ToString()] = Math.Max( 0, level );\n\n\tpublic bool HasAchievement( string id ) => Achievements.Contains( id );\n\n\t// \u2500\u2500 Meltdown (prestige) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\t/// <summary>Wipes the run-scoped economy. Flux, perks, records and achievements stay.</summary>\n\tpublic void ResetForMeltdown()\n\t{\n\t\tCores      = 0;\n\t\tCycleCores = 0;\n\t\tUpgrades.Clear();\n\t\tMeltdowns++;\n\n\t\t// Kickstart perk hands back a head start so the early grind shrinks each time\n\t\tint kick = PerkLevel( IdlePerkId.Kickstart );\n\t\tif ( kick > 0 )\n\t\t{\n\t\t\tSetLevel( IdleUpgradeId.ChestValue, kick * 6 );\n\t\t\tSetLevel( IdleUpgradeId.SpawnRate,  kick * 4 );\n\t\t\tSetLevel( IdleUpgradeId.CycleSpeed, kick * 4 );\n\t\t\tSetLevel( IdleUpgradeId.DroneCount, Math.Min( kick, 3 ) );\n\t\t}\n\t}\n\n\t// \u2500\u2500 Day rollover for the fragment bucket \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\tpublic void EnsureFragDay()\n\t{\n\t\tstring today = DateTime.UtcNow.ToString( \"yyyy-MM-dd\" );\n\t\tif ( LastFragDate == today ) return;\n\t\tLastFragDate = today;\n\t\tTodayReactorFragments = 0;\n\t}\n\n\tpublic int FragmentsToday\n\t{\n\t\tget { EnsureFragDay(); return TodayReactorFragments; }\n\t}\n}\n"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "styles/form/_dropdown.scss",
            "FileName": "_dropdown.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "\r\n$primary: red !default;\r\n$primary-alt: white !default;\r\n\r\n$switch-padding: 6px !default;\r\n\r\n.button.popupbutton.dropdown\r\n{\r\n\tcursor: pointer;\r\n\ttransition: all .1s ease-out;\r\n\tposition: relative;\r\n\r\n\t> .dropdown_indicator\r\n\t{\r\n\t\tposition: absolute;\r\n\t\tright: 8px;\r\n\t}\r\n\r\n\t&.open\r\n\t{\r\n\t\tborder-bottom-left-radius: 1px;\r\n\t\tborder-bottom-right-radius: 1px;\r\n\t\ttransition: border-radius 0.2s ease-out;\r\n\t}\r\n}\r\n\r\nselect\r\n{\r\n\tmin-height: 40px;\r\n\r\n\t> option\r\n\t{\r\n\t\tdisplay: none;\r\n\t}\r\n}"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "ui/components/packagecard.razor.scss",
            "FileName": "packagecard.razor.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "@import \"/styles/_theme.scss\";\r\n$background-size: 8px;\r\n\r\nPackageCard\r\n{\r\n\tflex-shrink: 0;\r\n\r\n\t&:hover\r\n\t{\r\n\t\tsound-in: \"ui.button.over\";\r\n\t}\r\n\r\n\t&:active\r\n\t{\r\n\t\tsound-in: \"ui.button.press\";\r\n\t}\r\n\r\n\tflex-direction: column;\r\n\tposition: relative;\r\n\tbackground-color: rgba( $default-950, 0 );\r\n\tborder-radius: $rounding-default;\r\n\ttransition: all 150ms ease;\r\n\tcursor: pointer;\r\n\tz-index: 0;\r\n\theight: 200px;\r\n\r\n\t.image\r\n\t{\r\n\t\tflex-grow: 1;\r\n\t\tflex-shrink: 0;\r\n\t\ttransition: all 150ms ease;\r\n\t\tborder: 1px solid rgba( white, 0.025 );\r\n\t\taspect-ratio: 16 / 9;\r\n\t\tbackground-position: center;\r\n\t\tbackground-size: cover;\r\n\t\tborder-radius: $rounding-small;\r\n\t\tposition: relative;\r\n\t}\r\n\r\n\tcolumn\r\n\t{\r\n\t\tpadding: 8px 2px; // Optically aligned\r\n\t}\r\n\r\n\t\r\n\r\n\t&.list\r\n\t{\r\n\t\tflex-grow: 1;\r\n\t\theight: 64px;\r\n\t\tflex-direction: row;\r\n\t\tgap: 12px;\r\n\t\tpadding: 4px;\r\n\r\n\t\t.inner column\r\n\t\t{\r\n\t\t\tflex-grow: 1;\r\n\t\t}\r\n\r\n\t\t.image\r\n\t\t{\r\n\t\t\theight: 100%;\r\n\t\t\taspect-ratio: 1;\r\n\t\t\tflex-grow: 0;\r\n\t\t\tflex-shrink: 0;\r\n\t\t}\r\n\r\n\t\tcolumn\r\n\t\t{\r\n\t\t\tgap: 3px;\r\n\t\t\tjustify-content: center;\r\n\t\t}\r\n\r\n\t\t.package-title\r\n\t\t{\r\n\t\t\tflex-shrink: 0;\r\n\t\t\tmax-width: 500px;\r\n\t\t}\r\n\r\n\t\t.package-users\r\n\t\t{\r\n\t\t\ttop: 1px;\r\n\t\t\tright: 1px;\r\n\t\t}\r\n\r\n\t\t&:hover\r\n\t\t{\r\n\t\t\tbackground-color: $default-800;\r\n\t\t\ttransform: none;\r\n\r\n\t\t\t&::after\r\n\t\t\t{\r\n\t\t\t\tdisplay: none;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t&.wide\r\n\t{\r\n\t\theight: 250px;\r\n\r\n\t\t.package-title\r\n\t\t{\r\n\t\t\tmax-width: 300px;\r\n\t\t}\r\n\t}\r\n\r\n\t&.small\r\n\t{\r\n\t\theight: 200px;\r\n\t\taspect-ratio: 3/4;\r\n\r\n\t\t.image\r\n\t\t{\r\n\t\t\taspect-ratio: 1;\r\n\t\t}\r\n\r\n\t\t.package-title\r\n\t\t{\r\n\t\t\tmax-width: 140px;\r\n\t\t}\r\n\t}\r\n\r\n\t&.tall\r\n\t{\r\n\t\theight: 400px;\r\n\r\n\t\t.image\r\n\t\t{\r\n\t\t\taspect-ratio: 9/16;\r\n\t\t}\r\n\r\n\t\t.package-title\r\n\t\t{\r\n\t\t\tmax-width: 200px;\r\n\t\t}\r\n\t}\r\n\r\n\t.package-title\r\n\t{\r\n\t\ttext-overflow: ellipsis;\r\n\t\tmax-height: 24px;\r\n\t\tflex-shrink: 1;\r\n\t\tfont-size: 14px;\r\n\t}\r\n\r\n\t.package-users\r\n\t{\r\n\t\tposition: absolute;\r\n\t\tbottom: 4px;\r\n\t\tright: 4px;\r\n\t\tbackground-color: rgba( 10, 40, 10, 0.95 );\r\n\t\tpadding: 3px 5px;\r\n\t\tborder-radius: 2px;\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\tgap: 3px;\r\n\t\tfont-size: 11px;\r\n\t\tcolor: #def;\r\n\t\tborder: 1px solid #252;\r\n\t\tcolor: #2f3;\r\n\r\n\t\t&:before\r\n\t\t{\r\n\t\t\tcontent: '\u25cf';\r\n\t\t\tfont-size: 0.5rem;\r\n\t\t}\r\n\t}\r\n\t// Hover effect (not for list)\r\n\t&:not(.list)\r\n\t{\r\n\t\t&::after\r\n\t\t{\r\n\t\t\tcontent: \"\";\r\n\t\t\tposition: absolute;\r\n\t\t\ttop: 0;\r\n\t\t\tleft: 0;\r\n\t\t\tbottom: 0;\r\n\t\t\tright: 0;\r\n\t\t\tbackground-color: rgba( $default-950, 0 );\r\n\t\t\ttransition: all 150ms ease;\r\n\t\t\tz-index: -10;\r\n\t\t\tborder-radius: $rounding-large;\r\n\t\t\tpointer-events: none;\r\n\t\t}\r\n\r\n\t\t&:hover\r\n\t\t{\r\n\t\t\ttransform: scale( 1.05 );\r\n\r\n\t\t\t&::after\r\n\t\t\t{\r\n\t\t\t\tbackground-color: $default-800;\r\n\t\t\t\ttop: -$background-size;\r\n\t\t\t\tleft: -$background-size;\r\n\t\t\t\tright: -$background-size;\r\n\t\t\t\tbottom: -$background-size;\r\n\t\t\t\tbox-shadow: 0 0 25px rgba( black, 0.3 );\r\n\t\t\t}\r\n\r\n\t\t\tz-index: 100;\r\n\t\t\tbackground-color: $default-900;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n\r\n.package-card.list packageflairbar\r\n{\r\n\tdisplay: none;\r\n}\r\n\r\n.package-card.list .package-users\r\n{\r\n\tdisplay: none;\r\n}"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "game/2d/ui/bombwheel.razor.scss",
            "FileName": "bombwheel.razor.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "root {\n    /* Declared so no panel in this tree ever claims the OS pointer -- a hovered\n       panel outranks Mouse.CursorType, and one that declares nothing still claims\n       the default arrow. GameCursor draws the cursor instead. */\n    cursor: none;\n    flex-grow:1; width:100%; height:100%;\n    pointer-events:none; position:relative;\n    overflow:visible;\n}\n\n.mini-hand {\n    position:absolute;\n    bottom:15px;\n    left:0; right:0px;           /* right edge = sidebar width */\n    display:flex; flex-direction:row; gap:14px; align-items:flex-end;\n    justify-content:center;        /* centers within the grid area, not full viewport */\n    pointer-events:all;\n    z-index:30;\n}\n\n.mini-card {\n    width:80px;\n    height: 80px;\n    background-color:rgba(31,27,83,0.85);   /* more opaque \u2014 was barely visible */\n    border:2px solid rgba(255,255,255,0.18);\n    border-radius:16px; padding:16px 10px 12px;\n    display:flex; flex-direction:column; align-items:center; gap:10px;\n    cursor: none; position:relative;\n    transition:background-color 0.1s, border-color 0.1s, transform 0.1s;\n}\n.mini-card:hover  { background-color:rgba(117,110,194,0.75); transform:translateY(-5px); }\n.mini-card.active {\n    background-color:rgba(83,74,183,0.90);\n    border-color:#a78bfa;\n    border-width:3px;\n    transform:translateY(-16px);\n}\n.mini-card.used {\n    background-color:rgba(29,158,117,0.28);\n    border-color:rgba(29,158,117,0.60);\n    opacity:0.75;\n    cursor: none;\n}\n\n.mini-icon { font-size:30px; position: relative; bottom: 4px;}     /* was 40px \u2014 easier to see */\n.mini-name { font-size:13px; font-weight:800; color:rgba(255,255,255,0.80); }\n.mini-card.active .mini-name { color:#fff; font-size:15px; }\n\n.mini-badge {\n    position:absolute; top:5px; right:7px;\n    font-size:12px; font-weight:700;\n    color:rgba(80,220,140,0.95);\n    background-color:rgba(29,158,117,0.40);\n    border-radius:4px; padding:0 5px;\n}\n\n.kb-hint {\n    position:absolute; top:5px; left:7px;\n    font-size:12px; font-weight:800;\n    color:rgba(255,255,255,0.45);\n    background-color:rgba(255,255,255,0.10);\n    border-radius:4px; padding:0 6px;\n}\n.mini-card.active .kb-hint {\n    color:rgba(255,255,255,0.80);\n    background-color:rgba(255,255,255,0.18);\n}\n\n.mini-det-wrap { align-self:flex-end; }\n.mini-det {\n    width:90px;                    /* was 90px */\n    background:linear-gradient(135deg,#7c3aed,#a855f7);\n    border:2px solid rgba(167,139,250,0.40);\n    border-radius:14px; \n    padding:12px 10px 5px;\n    display:flex; flex-direction:column; align-items:center; gap:5px;\n    cursor: none;\n    transition:opacity 0.15s;\n}\n.mini-det-wrap.ready .mini-det { border-color:#a78bfa; }\n.mini-det-wrap.off   .mini-det { opacity:0.25; pointer-events:none; }\n.mini-det-icon  { font-size:28px; }\n.mini-det-label { font-size:13px; font-weight:800; color:#fff; letter-spacing:0.08em; }\n\n/* SCREEN SHAKE */\n.mini-hand.shake-small  { animation: shake-small  0.25s ease-out; }\n.mini-hand.shake-medium { animation: shake-medium 0.35s ease-out; }\n.mini-hand.shake-strong { animation: shake-strong 0.45s ease-out; }\n.mini-hand.shake-insane { animation: shake-insane 0.55s ease-out; }\n\n@keyframes shake-small {\n    0%,100% { transform: translate(0,0); }\n    25%     { transform: translate(-2px, 1px); }\n    50%     { transform: translate(2px, -1px); }\n    75%     { transform: translate(-1px, 2px); }\n}\n@keyframes shake-medium {\n    0%,100% { transform: translate(0,0); }\n    20%     { transform: translate(-4px, 2px) rotate(-0.3deg); }\n    40%     { transform: translate(4px,-3px) rotate(0.3deg); }\n    60%     { transform: translate(-3px, 3px) rotate(-0.2deg); }\n    80%     { transform: translate(3px,-2px) rotate(0.2deg); }\n}\n@keyframes shake-strong {\n    0%,100% { transform: translate(0,0); }\n    15%     { transform: translate(-6px, 3px) rotate(-0.5deg); }\n    30%     { transform: translate(6px,-4px) rotate(0.5deg); }\n    45%     { transform: translate(-5px, 5px) rotate(-0.4deg); }\n    60%     { transform: translate(5px,-4px) rotate(0.4deg); }\n    75%     { transform: translate(-3px, 2px); }\n    90%     { transform: translate(2px,-2px); }\n}\n@keyframes shake-insane {\n    0%,100% { transform: translate(0,0); }\n    10%     { transform: translate(-9px, 5px) rotate(-0.7deg); }\n    20%     { transform: translate(9px,-6px) rotate(0.7deg); }\n    30%     { transform: translate(-8px, 7px) rotate(-0.6deg); }\n    40%     { transform: translate(8px,-6px) rotate(0.6deg); }\n    50%     { transform: translate(-6px, 5px) rotate(-0.4deg); }\n    60%     { transform: translate(6px,-5px) rotate(0.4deg); }\n    70%     { transform: translate(-4px, 3px); }\n    80%     { transform: translate(3px,-3px); }\n    90%     { transform: translate(-1px, 1px); }\n}\n\n/* Lock icon, if random event Bomb jam hit */\n.mini-jam-overlay {\n    position: absolute;\n    top: 0; left: 0; right: 0; bottom: 0;\n    background-color: rgba(239,68,68,0.25);\n    border-radius: 14px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    font-size: 28px;\n    pointer-events: none;\n}"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "game/2d/ui/gamehud.razor.scss",
            "FileName": "gamehud.razor.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "root {\n    /* Declared so no panel in this tree ever claims the OS pointer -- a hovered\n       panel outranks Mouse.CursorType, and one that declares nothing still claims\n       the default arrow. GameCursor draws the cursor instead. */\n    cursor: none;\n    position: absolute;\n    top: 0; left: 0; right: 0; bottom: 0;\n    pointer-events: none;\n    overflow: hidden;\n}\n\n/* TOP HUD */\n.topbar {\n    position: absolute;\n    top: 0; left: 0; right: 0;\n    height: 90px;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: space-evenly;\n    border-bottom: 1px solid rgba(167,139,250,0.15);\n    z-index: 10;\n    background-color: rgba(18,8,42,0.97);\n    animation: topbar-shift 20s ease-in-out infinite alternate;\n    overflow: hidden;\n    z-index: 11;\n}\n\n@keyframes topbar-shift {\n    0%   { background-color: rgba(18,8,42,0.97); }\n    25%  { background-color: rgba(8,24,48,0.97); }\n    50%  { background-color: rgba(24,8,40,0.97); }\n    75%  { background-color: rgba(8,18,50,0.97); }\n    100% { background-color: rgba(20,6,38,0.97); }\n}\n.hud-block {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 5px;\n}\n.hud-lbl {\n    font-size: 22px;\n    font-weight: 700;\n    letter-spacing: 0.12em;\n    color: rgba(255,255,255,0.30);\n    text-transform: uppercase;\n}\n.hud-val {\n    font-size: 32px;\n    font-weight: 900;\n    color: white;\n}\n.hud-block.wave  .hud-val { color: #a78bfa; }\n.hud-block.score .hud-val { color: #34d399; }\n.hud-block.best  .hud-val { color: #fbbf24; }\n.hud-sep {\n    width: 1px;\n    height: 120px;\n    background-color: rgba(128,95,128,0.07);\n}\n\n/* TIMER */\n.timer-track {\n    position: absolute;\n    top: 90px; left: 0; right: 0;\n    height: 14px;\n    background-color: rgba(255,255,255,0.06);\n    z-index: 10;\n}\n.timer-fill {\n    position: absolute;\n    top: 0; left: 0; bottom: 0;\n    background-color: #34d399;\n    transition: width 0.25s linear, background-color 0.8s ease;\n    min-width: 4px;\n}\n\n.timer-fill.mid {\n    background-color: #a78bfa;           /* half time: purple */\n    transition: width 0.25s linear, background-color 0.6s ease;\n}\n.timer-fill.hurry {\n    background-color: #f59e0b;           /* 30%: amber */\n    transition: width 0.25s linear, background-color 0.5s ease;\n}\n.timer-fill.panic {\n    background-color: #ef4444;           /* 15%: red */\n    transition: width 0.25s linear, background-color 0.3s ease;\n    animation: pulse-bar 0.55s ease-in-out infinite alternate;\n}\n@keyframes pulse-bar {\n    from { opacity: 1.0; }\n    to   { opacity: 0.5; }\n}\n/* \n   Three-stage color shift:\n   full \u2192 hurry  : purple  \u2192 amber   (feels warm, attention-grabbing)\n   hurry \u2192 panic : amber   \u2192 red     (urgent)\n   panic         : red + pulse animation\n*/\n\n/* Challenge sentence \u2014 between timer and grid */\n\n.challenge-bar {\n    position: absolute;\n    top: 104px; \n    left: 0;\n    right: 0px;\n    height: 36px;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: center;\n    gap: 10px;\n    background-color: rgba(13,8,32,0.80);\n    border-bottom: 1px solid rgba(167,139,250,0.12);\n    pointer-events: none;\n    z-index: 9;\n    transition: background-color 0.4s ease;\n}\n.challenge-bar.done {\n    background-color: rgba(16,42,16,0.85);\n    border-bottom-color: rgba(52,211,153,0.25);\n}\n\n.ch-icon {\n    font-size: 16px;\n    color: rgba(167,139,250,0.70);\n}\n.challenge-bar.done .ch-icon {\n    color: #34d399;\n}\n\n.ch-label {\n    font-size: 20px;\n    font-weight: 700;\n    color: rgba(255,255,255,0.55);\n    letter-spacing: 0.04em;\n}\n.challenge-bar.done .ch-label {\n    color: rgba(52,211,153,0.85);\n}\n\n.ch-bonus {\n    font-size: 13px;\n    font-weight: 800;\n    color: rgba(251,191,36,0.60);\n    background-color: rgba(251,191,36,0.08);\n    border-radius: 5px;\n    padding: 2px 7px;\n}\n.challenge-bar.done .ch-bonus {\n    color: rgba(52,211,153,0.80);\n    background-color: rgba(52,211,153,0.10);\n}\n\n/* CHAIN TRACK \u2014 above grid, pointer-events none */\n.chain-track {\n    position: absolute;\n    top: 146px;\n    left: 0;\n    right: 0px;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: center;\n    gap: 8px;\n    pointer-events: none;\n    z-index: 15;\n}\n.chain-track-inner {\n    background-color: rgba(13,8,32,0.75);\n    border-radius: 14px;\n    padding: 10px 20px;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 8px;\n}\n.step {\n    background-color: rgba(255,255,255,0.05);\n    color: rgba(255,255,255,0.25);\n    border-radius: 10px;\n    padding: 6px 17px;\n    font-size: 15px;\n    border: 1.5px solid rgba(255,255,255,0.06);\n    transition: all 0.15s;\n}\n.step.lit {\n    background-color: rgba(167,139,250,0.2);\n    color: #c4b5fd;\n    border-color: #7c3aed;\n    transform: scale(1.15);\n}\n.arr { font-size: 15px; color: rgba(255,255,255,0.15); }\n\n/* SIDEBAR */\n.sidebar {\n    position: absolute;\n    top: 140px; right: 0; bottom: 0;\n    width: 260px;\n    background-color: rgba(18,8,42,0.97);\n    border-left: 2px solid rgba(167,139,250,0.18);\n    display: flex;\n    flex-direction: column;\n    gap: 14px;\n    padding: 16px;\n    pointer-events: all;\n    z-index: 10;\n}\n.divider {\n    height: 1px;\n    background-color: rgba(167,139,250,0.10);\n}\n.sbl {\n    font-size: 12px; font-weight: 800;\n    letter-spacing: 0.2em; text-transform: uppercase;\n    color: rgba(166,154,219,0.5);\n}\n\n/* Legend */\n.legend { display: flex; flex-direction: column; gap: 8px; }\n.leg    { display: flex; flex-direction: row; align-items: center; gap: 6px; font-size: 14px; color: rgba(255,255,255,0.38); font-weight: 600; }\n.dot    { width: 10px; height: 10px; border-radius: 2px; }\n.dot.p0    { background-color: #60a5fa; }\n.dot.p1    { background-color: #34d399; }\n.dot.p2    { background-color: #fb923c; }\n.dot.red   { background-color: #f87171; }\n.dot.chain { background-color: #c4b5fd; }\n\n/* Controls */\n.controls-hint {\n    display: flex; flex-direction: column; gap: 6px;\n    font-size: 13px; color: rgba(255,255,255,0.30); font-weight: 500;\n}\n\n.spacer { flex-grow: 1; }\n\n/* Message bottom bar \u2014 pointer-events none, no text-align */\n.msg {\n    position: absolute;\n    bottom: 8px;\n    left: 12px;\n    background-color: rgba(4,1,12,1.0);\n    border: 1px solid rgba(167,139,250,0.15);\n    border-radius: 8px;\n    padding: 6px 14px;\n    font-size: 18px;\n    color: rgba(255,255,255,0.55);\n    pointer-events: none;\n    font-weight: 600;\n    z-index: 5;\n}\n/* GAME OVER */\n/* \u2500\u2500 Three column layout \u2500\u2500 */\n.ov-bg {\n    position: absolute;\n    top: 0; left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: rgba(4,1,12,1.0);\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: center;\n    gap: 24px;\n    pointer-events: all;\n    z-index: 50;\n    overflow: hidden;\n}\n\n.ov-side-panel {\n    display: flex;\n    flex-direction: column;\n    gap: 16px;\n    width: 220px;\n    pointer-events: none;\n    z-index: 3;\n    max-height: 92vh;\n    overflow-y: auto;\n}\n\n.ov-side-card {\n    position: relative; /* needed for ::before */\n    background-color: rgba(8,3,22,0.90);\n    border: 1px solid rgba(139,92,246,0.18);\n    border-radius: 16px;\n    padding: 16px;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 5px;\n    overflow: hidden;\n}\n\n.ov-side-card.journey {\n    gap: 4px;\n}\n/* Streak card \u2014 orange glow line */\n.ov-side-card.streak::before {\n    background-color: rgba(251,146,60,0.55);\n}\n\n.ov-side-card::before {\n    content: \"\";\n    position: absolute;\n    top: -1px; left: 20%; right: 20%;\n    height: 1px;\n    background-color: rgba(139,92,246,0.40);\n    border-radius: 50%;\n}\n\n.ov-side-card.streak {\n    border-color: rgba(251,146,60,0.30);\n    background-color: rgba(251,146,60,0.05);\n}\n\n.ov-side-card-title {\n    font-size: 14px;\n    font-weight: 900;\n    color: rgb(255, 214, 161);\n    letter-spacing: 0.22em;\n    text-transform: uppercase;\n    width: 100%;\n    justify-content: center;\n       margin-bottom: 8px;\n       gap: 3px;\n}\n\n/* \u2500\u2500 Personal best bar \u2500\u2500 */\n.ov-pb-bar-wrap { width: 100%; }\n\n.ov-pb-bar-track {\n    width: 100%;\n    height: 6px;\n    background-color: rgba(255,255,255,0.06);\n    border-radius: 3px;\n    position: relative;\n}\n\n.ov-pb-bar-fill {\n    position: absolute;\n    top: 0; left: 0; bottom: 0;\n    background-color: #a78bfa;\n    border-radius: 3px;\n    min-width: 4px;\n}\n\n.ov-pb-marker {\n    position: absolute;\n    top: -3px;\n    width: 2px;\n    height: 12px;\n    background-color: rgba(251,191,36,0.80);\n    border-radius: 1px;\n}\n\n.ov-pb-scores {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 2px;\n    width: 100%;\n}\n\n.ov-pb-score-current {\n    font-size: 22px;\n    font-weight: 900;\n    color: #a78bfa;\n}\n\n.ov-pb-score-best {\n    font-size: 11px;\n    font-weight: 600;\n    color: rgba(255,255,255,0.30);\n}\n\n.ov-pb-newbest {\n    font-size: 12px;\n    font-weight: 900;\n    color: #fbbf24;\n    background-color: rgba(251,191,36,0.10);\n    border: 1px solid rgba(251,191,36,0.25);\n    border-radius: 8px;\n    padding: 3px 10px;\n}\n\n/* Game over background Squares */\n@keyframes sq-float-1 {\n    0%   { margin-top: 0px;    margin-left: 0px; }\n    25%  { margin-top: -14px;  margin-left: 8px; }\n    50%  { margin-top: -6px;   margin-left: -10px; }\n    75%  { margin-top: 10px;   margin-left: 4px; }\n    100% { margin-top: 0px;    margin-left: 0px; }\n}\n\n@keyframes sq-float-2 {\n    0%   { margin-top: 0px;   margin-left: 0px; }\n    30%  { margin-top: 12px;  margin-left: -8px; }\n    60%  { margin-top: -8px;  margin-left: 12px; }\n    100% { margin-top: 0px;   margin-left: 0px; }\n}\n\n@keyframes sq-pulse {\n    0%,100% { opacity: 0.4; }\n    50%     { opacity: 1.0; }\n}\n\n.ov-bg-squares {\n    position: absolute;\n    top: 0; left: 0;\n    width: 100%;\n    height: 100%;\n    pointer-events: none;\n    z-index: 1;\n}\n\n.ov-bg-sq {\n    position: absolute;\n    border: 1.5px solid;\n}\n\n@keyframes sq-drift-a {\n    0%   { margin-top: 0px;   margin-left: 0px;   opacity: 0.35; }\n    20%  { margin-top: -12px; margin-left: 8px;   opacity: 0.80; }\n    40%  { margin-top: -20px; margin-left: -6px;  opacity: 0.50; }\n    60%  { margin-top: -8px;  margin-left: -14px; opacity: 0.90; }\n    80%  { margin-top: 6px;   margin-left: 4px;   opacity: 0.45; }\n    100% { margin-top: 0px;   margin-left: 0px;   opacity: 0.35; }\n}\n\n@keyframes sq-drift-b {\n    0%   { margin-top: 0px;  margin-left: 0px;  opacity: 0.25; }\n    25%  { margin-top: 14px; margin-left: -10px; opacity: 0.70; }\n    50%  { margin-top: 6px;  margin-left: 16px;  opacity: 0.90; }\n    75%  { margin-top: -10px; margin-left: 8px;  opacity: 0.40; }\n    100% { margin-top: 0px;  margin-left: 0px;   opacity: 0.25; }\n}\n\n.ov-bg-sq.sq-a {\n    animation-name: sq-drift-a;\n    animation-timing-function: ease-in-out;\n    animation-iteration-count: infinite;\n}\n\n.ov-bg-sq.sq-b {\n    animation-name: sq-drift-b;\n    animation-timing-function: ease-in-out;\n    animation-iteration-count: infinite;\n}\n\n/* Alternate float pattern on even squares */\n.ov-bg-sq:nth-child(even) {\n    animation-name: sq-float-2, sq-pulse;\n}\n\n/* \u2500\u2500 Journey \u2500\u2500 */\n.ov-journey-wave {\n    font-size: 32px;\n    font-weight: 900;\n    color: white;\n    gap: 3px;\n}\n\n.ov-journey-sub {\n    font-size: 12px;\n    font-weight: 600;\n    color: rgba(167, 255, 179, 0.719);\n    word-spacing: 1px;\n    width: 100%;\n    display: flex;\n    flex-direction: row;\n    justify-content: space-between;  /* \u2190 pushes label left, value right */\n}\n\n.ov-journey-tag {\n    font-size: 10px;\n    font-weight: 800;\n    color: rgba(255, 255, 255, 0.562);\n    background-color: rgba(255,255,255,0.04);\n    border-radius: 6px;\n    padding: 3px 10px;\n}\n\n.ov-journey-tag.good {\n    color: #34d399;\n    background-color: rgba(52,211,153,0.08);\n}\n\n/* \u2500\u2500 Streak \u2500\u2500 */\n.ov-streak-count {\n    font-size: 48px;\n    font-weight: 900;\n    color: #fb923c;\n}\n\n.ov-streak-label {\n    font-size: 11px;\n    font-weight: 700;\n    color: rgba(255, 199, 125, 0.404);\n}\n\n/* \u2500\u2500 Next unlock \u2500\u2500 */\n.ov-unlock-icon { font-size: 32px; }\n\n.ov-unlock-name {\n    font-size: 13px;\n    font-weight: 800;\n    color: rgba(255,255,255,0.75);\n    width: 100%;\n    text-align: center;\n    justify-content: center;\n    margin-bottom: 2px;\n}\n\n.ov-unlock-cost {\n    font-size: 12px;\n    flex-direction: row;\n    font-weight: 700;\n    color: rgba(129,140,248,0.70);\n    width: 100%;\n    justify-content: center;\n    text-align: center;\n    gap: 3px; /* adds space b/w elements */\n}\n\n.ov-unlock-ready {\n    font-size: 12px;\n    font-weight: 900;\n    color: #34d399;\n}\n\n.ov-unlock-bar-track {\n    width: 100%;\n    height: 4px;\n    background-color: rgba(255,255,255,0.06);\n    border-radius: 2px;\n    position: relative;\n    margin-top: 4px;\n}\n\n.ov-unlock-bar-fill {\n    position: absolute;\n    top: 0; left: 0; bottom: 0;\n    background-color: #818cf8;\n    border-radius: 2px;\n    min-width: 4px;\n}\n\n.ov-particles {\n    position: absolute;\n    top: 0; left: 0; right: 0; bottom: 0;\n    pointer-events: none;\n    overflow: hidden;\n    z-index: 2;\n}\n.ov-particle {\n    position: absolute;\n    bottom: -20px;\n    opacity: 0;\n    animation-name: particle-rise;\n    animation-timing-function: ease-in;\n    animation-iteration-count: infinite;\n}\n@keyframes particle-rise {\n    0%   { opacity: 0;   transform: translateY(0px); }\n    15%  { opacity: 1; }\n    80%  { opacity: 0.7; transform: translateY(-65vh); }\n    100% { opacity: 0;   transform: translateY(-105vh); }\n}\n\n.ov-box {\n    position: relative;\n    background-color: rgba(8,3,22,0.98);\n    border: 1px solid rgba(139,92,246,0.25);\n    border-radius: 24px;\n    padding: 2rem 2.2rem 2.2rem 2.2rem;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 16px;\n    width: 600px;\n    max-height: 92vh;\n    overflow-y: auto;\n    z-index: 3;\n    box-shadow: 0px 0px 0px 1px rgba(139,92,246,0.08),\n                0px 2px 0px rgba(139,92,246,0.15),\n                0px 24px 60px rgba(0s,0,0,0.80),\n                inset 0px 1px 0px rgba(255,255,255,0.04);\n}\n\n/* Subtle top glow on the box */\n.ov-box::before {\n    content: \"\";\n    position: absolute;\n    top: -1px; left: 15%; right: 15%;\n    height: 1px;\n    background-color: rgba(167,139,250,0.50);\n    border-radius: 50%;\n}\n\n.ov-wave-big {\n    font-size: 64px;\n    font-weight: 900;\n    color: #a78bfa;\n    letter-spacing: 0.06em;\n    animation: ov-section-in 0.6s cubic-bezier(0.22,1,0.36,1) 0.3s both;\n}\n\n.ov-stats {\n    display: flex;\n    flex-direction: row;\n    gap: 8px;\n    flex-shrink: 0;\n    width: 100%;\n    justify-content: center;\n}\n\n.ov-stat {\n    flex: 1;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 5px;\n    background-color: rgba(255,255,255,0.02);\n    border-radius: 14px;\n    padding: 16px 8px 12px 8px;\n    position: relative;\n    overflow: hidden;\n}\n\n.ov-stat::before {\n    content: \"\";\n    position: absolute;\n    top: 0; left: 0; right: 0;\n    height: 2px;\n}\n\n.ov-stat-icon { font-size: 18px; opacity: 0.70; }\n\n.ov-stat-val {\n    font-size: 36px;\n    font-weight: 900;\n    letter-spacing: -0.03em;\n    line-height: 1;\n}\n\n.ov-stat-lbl {\n    font-size: 11px;\n    font-weight: 700;\n    color: rgba(255, 255, 255, 0.575);\n    text-transform: uppercase;\n    letter-spacing: 0.14em;\n}\n\n.ov-stat:nth-child(1) {\n    border: 1px solid rgba(52,211,153,0.18);\n    background-color: rgba(52,211,153,0.06);\n}\n.ov-stat:nth-child(2) {\n    border: 1px solid rgba(167,139,250,0.18);\n    background-color: rgba(167,139,250,0.06);\n}\n.ov-stat:nth-child(3) {\n    border: 1px solid rgba(251,191,36,0.18);\n    background-color: rgba(251,191,36,0.06);\n}\n\n/* Color accent per stat \u2014 target each by position */\n.ov-stat:nth-child(1)::before { background-color: #34d399; }\n.ov-stat:nth-child(2)::before { background-color: #a78bfa; }\n.ov-stat:nth-child(3)::before { background-color: #fbbf24; }\n\n.ov-stat:nth-child(1) { border: 1px solid rgba(52,211,153,0.12); }\n.ov-stat:nth-child(2) { border: 1px solid rgba(167,139,250,0.12); }\n.ov-stat:nth-child(3) { border: 1px solid rgba(251,191,36,0.12); }\n\n/* \u2500\u2500 Stat label colors \u2500\u2500 */\n.ov-stat:nth-child(1) .ov-stat-val { color: #6ee7b7; }\n.ov-stat:nth-child(2) .ov-stat-val { color: #d8b4fe; }\n.ov-stat:nth-child(3) .ov-stat-val { color: #fde68a; }\n\n\n/* Background orbs still game over (ov)*/\n@keyframes orb-drift-1 {\n    0%   { margin-left: 0px;   margin-top: 0px; }\n    33%  { margin-left: 18px;  margin-top: -12px; }\n    66%  { margin-left: -10px; margin-top: 20px; }\n    100% { margin-left: 0px;   margin-top: 0px; }\n}\n\n@keyframes orb-drift-2 {\n    0%   { margin-left: 0px;   margin-top: 0px; }\n    40%  { margin-left: -20px; margin-top: 14px; }\n    70%  { margin-left: 12px;  margin-top: -8px; }\n    100% { margin-left: 0px;   margin-top: 0px; }\n}\n\n@keyframes orb-drift-3 {\n    0%   { margin-left: 0px;  margin-top: 0px; }\n    30%  { margin-left: 14px; margin-top: 18px; }\n    60%  { margin-left: -8px; margin-top: -14px; }\n    100% { margin-left: 0px;  margin-top: 0px; }\n}\n\n.ov-orb {\n    position: absolute;\n    border-radius: 50%;\n    pointer-events: none;\n    z-index: 0;\n}\n\n.ov-orb-1 {\n    width: 280px;\n    height: 280px;\n    top: -60px;\n    left: -80px;\n    background-color: rgba(109,40,217,0.06);\n    animation: orb-drift-1 12s ease-in-out infinite;\n}\n\n.ov-orb-2 {\n    width: 200px;\n    height: 200px;\n    bottom: 40px;\n    right: -60px;\n    background-color: rgba(99,102,241,0.05);\n    animation: orb-drift-2 16s ease-in-out infinite;\n}\n\n.ov-orb-3 {\n    width: 150px;\n    height: 150px;\n    top: 40%;\n    left: 30%;\n    background-color: rgba(244,114,182,0.03);\n    animation: orb-drift-3 20s ease-in-out infinite;\n}\n\n/* Game over - secret */\n.ov-secret-title {\n    font-size: 13px;\n    font-weight: 800;\n    color: rgba(244,114,182,0.70);\n    letter-spacing: 0.18em;\n    text-transform: uppercase;\n    animation: ov-in 0.5s ease-out 1.6s both;\n}\n.ov-secrets {\n    display: flex;\n    flex-direction: row;\n    gap: 10px;\n    flex-wrap: wrap;\n    justify-content: center;\n}\n.ov-secret {\n    position: relative;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 6px;\n    background-color: rgba(244,114,182,0.08);\n    border: 1px solid rgba(244,114,182,0.25);\n    border-radius: 14px;\n    padding: 14px 18px;\n    min-width: 80px;\n    overflow: hidden;\n}\n.ov-secret.locked {\n    background-color: rgba(255,255,255,0.03);\n    border-color: rgba(255,255,255,0.08);\n    animation: chest-idle 2s ease-in-out infinite;\n}\n.ov-secret.rare {\n    background-color: rgba(251,191,36,0.10);\n    border-color: rgba(251,191,36,0.45);\n}\n.ov-secret.pop      { animation: chest-pop      0.7s cubic-bezier(0.22,1,0.36,1) forwards; }\n.ov-secret.rare.pop { animation: chest-pop-rare 1.0s cubic-bezier(0.22,1,0.36,1) forwards; }\n.ov-secret-icon { font-size: 28px; color: white; }\n.ov-secret-lbl  { font-size: 13px; font-weight: 800; color: rgba(255,255,255,0.80); }\n.ov-secret.rare .ov-secret-lbl { color: #fbbf24; }\n.ov-secret-shine {\n    position: absolute;\n    top: 0; left: 0; right: 0; bottom: 0;\n    background: linear-gradient(105deg, transparent 30%, rgba(255,255,255,0.35) 50%, transparent 70%);\n    animation: shine-sweep 0.6s ease-out 0.1s forwards;\n    pointer-events: none;\n}\n\n.ov-section-enter {\n    animation: ov-section-in 0.45s cubic-bezier(0.22,1,0.36,1) forwards;\n}\n\n@keyframes ov-section-in {\n    0%   { margin-top: 24px; }\n    100% { margin-top: 0px; }\n}\n\n/* Clickable secret chests during Game Over */\n.ov-secret.clickable {\n    cursor: none;\n    border-color: rgba(167,139,250,0.55);\n    background-color: rgba(167,139,250,0.10);\n    animation: chest-idle 1.0s ease-in-out infinite;\n    pointer-events: all;\n}\n\n.ov-secret.clickable:hover {\n    background-color: rgba(167,139,250,0.20);\n    border-color: rgba(167,139,250,0.85);\n}\n\n@keyframes chest-idle {\n    0%,100% { transform: rotate(0deg); }\n    25%     { transform: rotate(-2deg) scale(1.02); }\n    75%     { transform: rotate(2deg)  scale(1.02); }\n}\n@keyframes chest-pop {\n    0%   { transform: scale(1.0); }\n    20%  { transform: scale(0.88); }\n    55%  { transform: scale(1.22); }\n    75%  { transform: scale(0.96); }\n    100% { transform: scale(1.0); }\n}\n@keyframes chest-pop-rare {\n    0%   { transform: scale(1.0)  rotate(0deg); }\n    15%  { transform: scale(0.82) rotate(-4deg); }\n    45%  { transform: scale(1.35) rotate(3deg); }\n    65%  { transform: scale(0.93) rotate(-1deg); }\n    82%  { transform: scale(1.06) rotate(0deg); }\n    100% { transform: scale(1.0)  rotate(0deg); }\n}\n@keyframes shine-sweep {\n    0%   { transform: translateX(-200%); }\n    100% { transform: translateX(300%); }\n}\n\n/* Secret section - when clickicking */\n.ov-secret-section {\n    width: 100%;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 12px;\n}\n\n.ov-secret-eyebrow {\n    font-size: 11px;\n    font-weight: 800;\n    color: rgba(244,114,182,0.70);\n    letter-spacing: 0.22em;\n    text-transform: uppercase;\n}\n\n.ov-secret-hint {\n    font-size: 13px;\n    font-weight: 600;\n    color: rgba(255,255,255,0.35);\n}\n\n/* \u2500\u2500 Chest cards \u2500\u2500 */\n\n.ov-chest-cards {\n    display: flex;\n    flex-direction: row;\n    gap: 16px;\n    justify-content: center;\n}\n\n.ov-chest-card {\n    width: 130px;\n    min-height: 150px; /* was height: 150px */\n    border-radius: 16px;\n    border: 1.5px solid rgba(167,139,250,0.15);\n    background-color: rgba(13,6,32,0.90);\n    position: relative;\n    overflow: visible;\n    cursor: none;\n    box-shadow: 0px 4px 0px rgba(0,0,0,0.40);\n}\n\n.ov-chest-card.ready {\n    border-color: rgba(167,139,250,0.55);\n    background-color: rgba(60,20,140,0.25);\n    cursor: none;\n    pointer-events: all;\n    animation: chest-card-ready 1.4s ease-in-out infinite alternate;\n    box-shadow: 0px 4px 0px rgba(0,0,0,0.40),\n                0px 0px 20px rgba(167,139,250,0.15);\n}\n\n.ov-chest-card.ready:hover {\n    border-color: rgba(167,139,250,0.90);\n    background-color: rgba(109,40,217,0.30);\n}\n\n.ov-chest-card.waiting {\n    border-color: rgba(255,255,255,0.06);\n    background-color: rgba(255,255,255,0.02);\n    box-shadow: none;\n}\n\n.ov-chest-card.revealed {\n    border-color: rgba(129,140,248,0.35);\n    background-color: rgba(99,102,241,0.08);\n    box-shadow: 0px 4px 0px rgba(0,0,0,0.30),\n                0px 0px 16px rgba(129,140,248,0.10);\n}\n\n.ov-chest-card.revealed.rare {\n    border-color: rgba(251,191,36,0.50);\n    background-color: rgba(251,191,36,0.08);\n    box-shadow: 0px 4px 0px rgba(0,0,0,0.30),\n                0px 0px 24px rgba(251,191,36,0.12);\n}\n\n.ov-chest-card.pop-in {\n    animation: chest-card-pop 0.7s cubic-bezier(0.22,1,0.36,1) forwards;\n}\n\n.ov-chest-card-inner {\n    position: absolute;\n    top: 0; left: 0; right: 0; bottom: 0;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    gap: 8px;\n    pointer-events: none;\n}\n\n.ov-chest-emoji {\n    font-size: 48px;\n    animation: chest-card-idle 2.2s ease-in-out infinite alternate;\n}\n\n.ov-chest-card.waiting .ov-chest-emoji {\n    opacity: 0.25;\n    animation: none;\n}\n\n.ov-chest-tap {\n    font-size: 10px;\n    font-weight: 900;\n    color: white;\n    background-color: rgba(167,139,250,0.35);\n    border: 1px solid rgba(167,139,250,0.50);\n    border-radius: 6px;\n    padding: 3px 10px;\n    letter-spacing: 0.12em;\n}\n\n.ov-chest-tap.locked {\n    background-color: rgba(255,255,255,0.05);\n    border-color: rgba(255,255,255,0.08);\n    color: rgba(255,255,255,0.20);\n}\n\n.ov-chest-glow {\n    position: absolute;\n    top: 0; left: 0; right: 0; bottom: 0;\n    border-radius: 14px;\n    animation: chest-glow-pulse 1.2s ease-in-out infinite alternate;\n    pointer-events: none;\n}\n\n.ov-chest-card.ready {\n    border-color: rgba(167,139,250,0.60);\n    background-color: rgba(109,40,217,0.18);\n    cursor: none;\n    pointer-events: all;\n    animation: chest-card-ready 1.2s ease-in-out infinite alternate;\n}\n\n.ov-chest-reward-icon {\n    font-size: 40px;\n}\n\n.ov-chest-reward-lbl {\n    font-size: 11px;\n    font-weight: 800;\n    color: rgba(255,255,255,0.75);\n    padding: 0px 8px;\n}\n\n/* rare badge */\n.ov-chest-revealed-icon {\n    font-size: 40px;\n}\n\n.ov-chest-revealed-info {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 4px;\n}\n\n.ov-chest-revealed-lbl {\n    font-size: 13px;\n    font-weight: 900;\n    color: white;\n    padding: 0px 4px;\n}\n\n.ov-chest-rare-badge {\n    font-size: 9px;\n    font-weight: 900;\n    color: #fbbf24;\n    background-color: rgba(251,191,36,0.10);\n    border: 1px solid rgba(251,191,36,0.30);\n    border-radius: 20px;\n    padding: 2px 8px;\n    letter-spacing: 0.10em;\n}\n\n.ov-confetti-bit {\n    position: absolute;\n    top: -20px;\n    width: 8px;\n    height: 8px;\n    border-radius: 2px;\n    animation-name: confetti-fly;\n    animation-timing-function: ease-out;\n    animation-fill-mode: forwards;\n    pointer-events: none;\n    z-index: 10;\n}\n\n@keyframes confetti-fly {\n    0%   { opacity: 1; transform: translateY(0px) rotate(0deg) scale(1.2); }\n    30%  { opacity: 1; }\n    100% { opacity: 0; transform: translateY(-140px) rotate(520deg) scale(0.4); }\n}\n\n@keyframes chest-card-ready {\n    0%   { transform: translateY(0px) scale(1.0); }\n    100% { transform: translateY(-5px) scale(1.02); }\n}\n\n@keyframes chest-card-idle {\n    0%   { transform: translateY(0px) rotate(-1.5deg); }\n    100% { transform: translateY(-5px) rotate(1.5deg); }\n}\n\n@keyframes chest-card-pop {\n    0%   { transform: scale(1.0); }\n    20%  { transform: scale(0.85); }\n    55%  { transform: scale(1.25); }\n    75%  { transform: scale(0.96); }\n    100% { transform: scale(1.0); }\n}\n\n@keyframes chest-glow-pulse {\n    0%   { opacity: 0.4; }\n    100% { opacity: 1.0; }\n}\n\n@keyframes chest-shine-sweep {\n    0%   { transform: translateX(-100%); }\n    40%  { transform: translateX(250%); }\n    100% { transform: translateX(250%); }\n}\n\n@keyframes confetti-fly {\n    0%   { opacity: 1; transform: translateY(0px) rotate(0deg) scale(1.0); }\n    60%  { opacity: 1; }\n    100% { opacity: 0; transform: translateY(-90px) rotate(380deg) scale(0.6); }\n}\n\n/* Frags section */\n.ov-frag-section {\n    width: 100%;\n    display: flex;\n     flex-shrink: 0;\n    flex-direction: column;\n    align-items: center;\n    gap: 12px;\n    background-color: rgba(99,102,241,0.05);\n    border: 1px solid rgba(99,102,241,0.18);\n    border-radius: 18px;\n    padding: 20px 22px;\n    position: relative;\n    overflow: hidden;\n}\n\n/* Subtle shimmer across the top */\n.ov-frag-section::before {\n    content: \"\";\n    position: absolute;\n    top: 0; left: 0; right: 0;\n    height: 1px;\n    background-color: rgba(129,140,248,0.35);\n}\n\n.ov-frag-breakdown {\n    display: flex;\n    flex-direction: column;\n    gap: 5px;\n    width: 100%;\n}\n\n.ov-frag-line {\n    display: flex;\n    flex-direction: row;\n    justify-content: space-between;\n    align-items: center;\n    padding: 5px 10px;\n    background-color: rgba(255,255,255,0.025);\n    border-radius: 8px;\n    border-left: 2px solid rgba(129,140,248,0.20);\n}\n\n.ov-frag-line-lbl {\n    font-size: 12px;\n    font-weight: 600;\n    color: rgba(255,255,255,0.35);\n}\n\n.ov-frag-line-val {\n    font-size: 13px;\n    font-weight: 900;\n    color: #818cf8;\n}\n\n.ov-frag-label {\n    font-size: 10px;\n    font-weight: 900;\n    color: rgba(129,140,248,0.55);\n    letter-spacing: 0.22em;\n    text-transform: uppercase;\n}\n\n.ov-frag-val {\n    font-size: 52px;\n    font-weight: 900;\n    color: #818cf8;\n    letter-spacing: -0.02em;\n    line-height: 1;\n}\n\n.ov-frag-total {\n    font-size: 12px;\n    font-weight: 700;\n    color: rgba(129,140,248,0.50);\n    border-top: 1px solid rgba(129,140,248,0.10);\n    padding-top: 8px;\n    width: 100%;\n    text-align: center;\n}\n\n.ov-frag-hint {\n    font-size: 11px;\n    color: rgba(255,255,255,0.22);\n    font-weight: 600;\n    letter-spacing: 0.04em;\n}\n\n/**/\n\n.ov-score-section {\n    display: flex;\n    flex-shrink: 0;\n    flex-direction: column;\n    align-items: center;\n    gap: 6px;\n    animation: ov-score-appear 0.6s ease-out forwards;\n}\n\n@keyframes ov-score-appear {\n    0%   { opacity: 0; }\n    100% { opacity: 1; }\n}\n\n.ov-score-label { font-size: 12px; font-weight: 700; color: rgba(255,255,255,0.35); letter-spacing: 0.18em; text-transform: uppercase; }\n.ov-score       { font-size: 62px; font-weight: 900; color: #a78bfa; }\n.ov-score.gold  { color: #fbbf24; }\n.ov-newbest-badge {\n    font-size: 15px;\n    font-weight: 900;\n    color: #fbbf24;\n    background-color: rgba(251,191,36,0.12);\n    border: 1.5px solid rgba(251,191,36,0.40);\n    border-radius: 8px;\n    padding: 4px 14px;\n    letter-spacing: 0.12em;\n    animation: badge-slam 0.5s cubic-bezier(0.22,1,0.36,1) 0.6s both;\n}\n@keyframes badge-slam {\n    0%   { opacity: 0; transform: scale(0.3); }\n    60%  { transform: scale(1.15); }\n    100% { opacity: 1; transform: scale(1.0); }\n}\n.ov-btns {\n    display: flex;\n    flex-direction: row;\n    gap: 12px;\n    flex-shrink: 0; /* never let the buttons get squeezed */\n}\n\n/* \u2500\u2500 Play Again button sweep \u2500\u2500 */\n.ov-btn-wrap {\n    position: relative;\n    overflow: hidden;\n    background-color: #3aed3a;\n    border: 1.5px solid #8bfa9a;\n    border-radius: 12px;\n    color: white;\n    font-size: 30px;\n    font-weight: 800;\n    padding: 15px 40px;\n    cursor: none;\n    pointer-events: all;\n    animation: btn-breathe 2.2s ease-in-out infinite alternate;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n}\n\n@keyframes btn-breathe {\n    0%   { transform: scale(1.0); }\n    100% { transform: scale(1.05); }\n}\n\n.ov-btn-shine {\n    position: absolute;\n    top: 0; bottom: 0;\n    left: -80%;\n    width: 40%;\n    background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.25) 50%, transparent 100%);\n    animation: btn-shine-sweep 3.0s ease-in-out infinite;\n    pointer-events: none;\n}\n\n@keyframes btn-shine-sweep {\n    0%   { left: -80%; }\n    45%  { left: 140%; }\n    100% { left: 140%; }\n}\n\n@keyframes ov-in {\n    0%   { opacity: 0; transform: translateY(12px) scale(0.97); }\n    100% { opacity: 1; transform: translateY(0) scale(1.0); }\n}\n\n.ov-reveal {\n    animation: ov-in 0.4s ease-out both;\n}\n\n/* Main Menu Button */\n.ov-btn-menu {\n    background-color: rgba(255,255,255,0.04);\n    border: 1px solid rgba(255,255,255,0.10);\n    border-radius: 12px;\n    color: rgba(255,255,255,0.40);\n    font-size: 14px;\n    font-weight: 700;\n    padding: 15px 20px;\n    cursor: none;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n}\n.ov-btn-menu:hover {\n    background-color: rgba(255,255,255,0.08);\n    color: rgba(255,255,255,0.65);\n}\n\n/* -------------------- Modifier strip -----------------------\n* This shows the equipped modifiers as a vertical strip in the sidebar\n* Each item has its icon, name, and a tooltip on hover showing the full description. \n* The green tint matches the \"active\" color language used in the shop, \n* creating visual consistency between menu and game.\n\n* Players can now glance at the sidebar mid-run and see exactly what's buffing them\n* Combined with the boss multiplier suppression warning I added, \n* the player always knows what's affecting their score\n*/\n.mod-strip {\n    display: flex;\n    flex-direction: column;\n    gap: 6px;\n    margin-bottom: 12px;\n}\n\n.mod-strip-item {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 10px;\n    padding: 8px 12px;\n    background-color: rgba(52,211,153,0.06);\n    border: 1px solid rgba(52,211,153,0.20);\n    border-radius: 8px;\n    transition: background-color 0.2s ease;\n}\n\n.mod-strip-item:hover {\n    background-color: rgba(52,211,153,0.12);\n}\n\n.mod-strip-icon {\n    font-size: 18px;\n    flex-shrink: 0;\n}\n\n.mod-strip-name {\n    font-size: 11px;\n    font-weight: 700;\n    color: rgba(255,255,255,0.75);\n    letter-spacing: 0.04em;\n}\n\n/* \u2500\u2500 Game over reason \u2500\u2500 */\n.ov-reason {\n    animation: ov-section-in 0.5s cubic-bezier(0.22,1,0.36,1) 0.5s both;\n    font-size: 13px;\n    font-weight: 700;\n    color: rgba(255,255,255,0.55);\n    padding: 8px 16px;\n    background-color: rgba(255,255,255,0.04);\n    border: 1px solid rgba(255,255,255,0.08);\n    border-radius: 10px;\n    width: 100%;\n    justify-content: center;\n}\n\n/* ---------------- SCREEN SHAKE ---------------- */\n.hud-overlay {\n    position: absolute;\n    top: 0; left: 0; right: 0; bottom: 0;\n    pointer-events: none;\n    z-index: 5;\n}\n\n.hud-overlay.shake-small  { animation: shake-small  0.25s ease-out; }\n.hud-overlay.shake-medium { animation: shake-medium 0.35s ease-out; }\n.hud-overlay.shake-strong { animation: shake-strong 0.45s ease-out; }\n.hud-overlay.shake-insane { animation: shake-insane 0.55s ease-out; }\n\n.mini-hand.shake-small  { animation: shake-small  0.25s ease-out; }\n.mini-hand.shake-medium { animation: shake-medium 0.35s ease-out; }\n.mini-hand.shake-strong { animation: shake-strong 0.45s ease-out; }\n.mini-hand.shake-insane { animation: shake-insane 0.55s ease-out; }\n\n@keyframes shake-small {\n    0%,100% { transform: translate(0,0); }\n    25%     { transform: translate(-2px, 1px); }\n    50%     { transform: translate(2px, -1px); }\n    75%     { transform: translate(-1px, 2px); }\n}\n@keyframes shake-medium {\n    0%,100% { transform: translate(0,0); }\n    20%     { transform: translate(-4px, 2px) rotate(-0.3deg); }\n    40%     { transform: translate(4px,-3px) rotate(0.3deg); }\n    60%     { transform: translate(-3px, 3px) rotate(-0.2deg); }\n    80%     { transform: translate(3px,-2px) rotate(0.2deg); }\n}\n@keyframes shake-strong {\n    0%,100% { transform: translate(0,0); }\n    15%     { transform: translate(-6px, 3px) rotate(-0.5deg); }\n    30%     { transform: translate(6px,-4px) rotate(0.5deg); }\n    45%     { transform: translate(-5px, 5px) rotate(-0.4deg); }\n    60%     { transform: translate(5px,-4px) rotate(0.4deg); }\n    75%     { transform: translate(-3px, 2px); }\n    90%     { transform: translate(2px,-2px); }\n}\n@keyframes shake-insane {\n    0%,100% { transform: translate(0,0); }\n    10%     { transform: translate(-9px, 5px) rotate(-0.7deg); }\n    20%     { transform: translate(9px,-6px) rotate(0.7deg); }\n    30%     { transform: translate(-8px, 7px) rotate(-0.6deg); }\n    40%     { transform: translate(8px,-6px) rotate(0.6deg); }\n    50%     { transform: translate(-6px, 5px) rotate(-0.4deg); }\n    60%     { transform: translate(6px,-5px) rotate(0.4deg); }\n    70%     { transform: translate(-4px, 3px); }\n    80%     { transform: translate(3px,-3px); }\n    90%     { transform: translate(-1px, 1px); }\n}\n\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* \u2500\u2500\u2500\u2500\u2500\u2500 Consumable strip \u2014 horizontal bar below challenge bar \u2500\u2500\u2500\u2500\u2500 */\n/* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.consumable-strip {\n    position: absolute;\n    bottom: 120px;         /* sits above the bomb wheel */\n    left: 0;\n    right: 150px;     /* leave room for sidebar */\n    display: flex;\n    flex-direction: row;\n    justify-content: center;\n    gap: 8px;\n    pointer-events: all;\n    z-index: 20;\n}\n\n.consumable-slot {\n    position: relative;\n    left: 70px;\n    width: 54px;\n    height: 54px;\n    border-radius: 10px;\n    border: 1.5px solid rgba(255,255,255,0.10);\n    background-color: rgba(255,255,255,0.04);\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    cursor: none;\n    position: relative;\n}\n\n.consumable-slot.available {\n    border-color: rgba(167,139,250,0.50);\n    background-color: rgba(109,40,217,0.15);\n    cursor: none;\n}\n\n.consumable-slot.available:hover {\n    background-color: rgba(109,40,217,0.30);\n    border-color: rgba(167,139,250,0.80);\n}\n\n.consumable-slot.empty {\n    opacity: 0.25;\n    cursor: none;\n}\n\n.consumable-icon {\n    font-size: 22px;\n    pointer-events: none;\n}\n\n/* Count badge \u2014 bottom right corner of slot */\n.consumable-count {\n    position: absolute;\n    bottom: 2px;\n    right: 4px;\n    font-size: 10px;\n    font-weight: 900;\n    color: rgba(167,139,250,0.95);\n    pointer-events: none;\n}\n\n/* \u2500\u2500 Screen Flash \u2500\u2500 */\n.screen-flash {\n    position: absolute;\n    top: 0; left: 0;\n    width: 2200px;\n    height: 1400px;\n    pointer-events: none;\n    z-index: 100;\n}\n\n.flash-text {\n    position: absolute;\n    top: 485px;        /* roughly vertical center of 1080p */\n    left: 1055px;       /* roughly horizontal center minus half text width */\n    font-size: 72px;\n    font-weight: 900;\n    color: rgba(255,255,255,0.90);\n    pointer-events: none;\n    animation: flash-text-pop 1.2s ease-out forwards;\n        text-shadow:\n        -3px -3px 0 #000,\n         3px -3px 0 #000,\n        -3px  3px 0 #000,\n         3px  3px 0 #000,\n        -4px  0   0 #000,\n         4px  0   0 #000,\n         0   -4px  0 #000,\n         0    4px  0 #000;\n\tz-index: 10;\n}\n\n@keyframes flash-text-pop {\n    0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.6); }\n    12%  { opacity: 1; transform: translate(-50%, -50%) scale(1.05); }\n    70%  { opacity: 1; transform: translate(-50%, -50%) scale(1.0); }\n    100% { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }\n}\n\n@keyframes flash-fade {\n    0%   { opacity: 0; }\n    15%  { opacity: 1; }\n    100% { opacity: 0; }\n}\n\n@keyframes flash-boss-spawn {\n    0%   { opacity: 0; }\n    15%  { opacity: 1; }\n    100% { opacity: 0; }\n}\n\n@keyframes flash-wave {\n    0%   { opacity: 0; }\n    10%  { opacity: 1; }\n    60%  { opacity: 1; }\n    100% { opacity: 0; }\n}\n\n.screen-flash.flash-boss-spawn {\n    background-color: rgba(220, 30, 30, 0.50);\n    animation: flash-boss-spawn 2.5s ease-out forwards;\n}\n\n.screen-flash.flash-boss-death {\n    background-color: rgba(251,191,36,0.55);\n    animation: flash-fade 0.6s ease-out forwards;\n}\n\n.screen-flash.flash-wave-start {\n    background-color: rgba(138, 92, 246, 0.082);  /* was 0.18 \u2014 lighter */\n    animation: flash-wave .9s ease-out forwards;  /* was 2.5s \u2014 faster */\n}\n\n.screen-flash.flash-new-best {\n    background-color: rgba(52,211,153,0.45);\n    animation: flash-fade 1.0s ease-out forwards;\n}\n\n.screen-flash.flash-game-over {\n    background-color: rgba(180,20,20,0.60);\n    animation: flash-fade 0.8s ease-out forwards;\n}\n\n/* ------------------------------------------- */\n/* -------------- Pet Companion -------------- */\n/* ------------------------------------------- */\n\n.hud-pet-wrap {\n    position: absolute;\n    bottom: 55px;\n    left: 15px;\n    width: 220px;\n    height: 870px;\n    display: flex;\n    flex-direction: column;\n    align-items: flex-start;\n    padding: 14px 14px 0px 14px;\n    z-index: 6;\n}\n\n.hud-pet {\n    font-size: 110px;\n    animation: hud-pet-idle 2.5s ease-in-out infinite alternate;\n    opacity: 0.8;\n    margin-bottom: 10px;\n    margin-left: 0px;\n}\n.hud-pet image {\n    width: 110px;\n    height: 110px;\n}\n\n.hud-pet-egg-icon\n{\n    width: 110px;\n    height: 110px;\n}\n\n/* Top section \u2014 modifiers + curse */\n.hud-pet-top {\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    width: 100%;\n}\n\n/* Bottom section \u2014 pet + hearts, pinned to bottom */\n.hud-pet-bottom {\n    margin-top: auto;\n    display: flex;\n    flex-direction: column;\n    align-items: flex-start;\n    width: 100%;\n    font-size: 64px; \n\n}\n\n@keyframes pet-bob {\n    from { transform: translateY(0px); }\n    to   { transform: translateY(-8px); }\n}\n\n.pet-bob {\n    animation: pet-bob 1.4s ease-in-out infinite alternate;\n}\n\n.hud-pet-egg {\n    width: 120px;\n    height: 120px;\n}\n\n.hud-pet-bottom image {\n    width: 134px;\n    height: 134px;\n    margin-left: -10px;\n}\n\n.pet-sbl {\n    font-size: 10px;\n    font-weight: 800;\n    letter-spacing: 0.2em;\n    text-transform: uppercase;\n    color: rgba(166,154,219,0.5);\n}\n\n.hud-pet-card {\n    position: absolute;\n    top: 0; left: 0; right: 0; bottom: 0;\n    background-color: rgba(18,8,42,0.92);\n    border: 1px solid rgba(167,139,250,0.18);\n    border-radius: 14px 14px 0px 0px;\n    z-index: -1;\n}\n\n@keyframes hud-pet-idle {\n    0%   { transform: translateY(0px) scale(1.0); }\n    100% { transform: translateY(-6px) scale(1.05); }\n}\n\n/* -------------------------------------------------- */\n/* -------------- Hearts/Lives Sidebar -------------- */\n/* -------------------------------------------------- */\n\n.hud-pet-lives {\n    position: absolute;\n    right: 0px;\n    left: 150px;\n    bottom: 10px;\n    display: flex;\n    flex-direction: column;\n    gap: 4px;\n}\n\n.hud-pet-heart {\n    font-size: 40px;\n}\n\n.hud-pet-heart.empty {\n    opacity: 0.20;\n}\n\n/* -------------------------------------------------- */\n/* -------------- BACKGROUND (grid-like) ------------ */\n/* -------------------------------------------------- */\n\n.game-bg-grid {\n    position: absolute;\n    top: 90px; left: 0; right: 0; bottom: 0;\n    pointer-events: none;\n    z-index: 0;\n    overflow: hidden;\n}\n\n.game-bg-cell {\n    position: absolute;\n    width: 232px;\n    height: 232px;\n    border-radius: 10px;\n    border: 2px solid rgba(66, 54, 75, 0.04);\n    z-index: 0;\n}\n\n.game-bg-cell.gbg-idle  { /* inline style from WaveGridColor \u2014 already very dark hex */ }\n.game-bg-cell.gbg-expl  { background-color: rgba(10,4,24,0.95); border-color: rgba(LoadPetLbLoadPetLb251,146,60,0.22); }\n.game-bg-cell.gbg-expl2 { background-color: rgba(10,4,24,0.95); border-color: rgba(251,191,36,0.22); }\n.game-bg-cell.gbg-gold  { background-color: rgba(10,4,24,0.95); border-color: rgba(251,191,36,0.16); }\n.game-bg-cell.gbg-red   { background-color: rgba(10,4,24,0.95); border-color: rgba(248,113,113,0.16); }\n.game-bg-cell.gbg-chain { background-color: rgba(10,4,24,0.95); border-color: rgba(196,181,253,0.14); }\n\n/* SIDEBAR BUTTON (info) */\n.sidebar-toggle {\n    position: absolute;\n    top: 155px;\n    right: 12px;\n    width: 44px;\n    height: 44px;\n    border-radius: 50px;\n    background-color: rgba(180,30,30,0.85);\n    border: 2px solid rgba(255,100,100,0.60);\n    color: white;\n    font-size: 20px;\n    font-weight: 900;\n    pointer-events: all;\n    z-index: 20;\n    cursor: none;\n    box-shadow: 2px 2px 0px rgba(0,0,0,0.60);\n    display: flex;\n    align-items: center;\n    justify-content: center;\n}\n\n.sidebar-toggle:hover {\n    background-color: rgba(220,40,40,0.95);\n    border-color: rgba(255,140,140,0.80);\n}\n\n/**/\n.pet-mod-strip {\n    display: flex;\n    flex-direction: row;\n    gap: 6px;\n    margin-top: 8px;\n}\n\n.pet-mod-item {\n    width: 32px;\n    height: 32px;\n    border-radius: 8px;\n    background-color: rgba(52,211,153,0.10);\n    border: 1px solid rgba(52,211,153,0.25);\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    font-size: 16px;\n}\n\n.pet-curse-warning {\n    font-size: 13px;\n    font-weight: 800;\n    color: rgba(248,113,113,0.85);\n    margin-top: 6px;\n    padding: 4px 8px;\n    background-color: rgba(239,68,68,0.10);\n    border: 1px solid rgba(248,113,113,0.20);\n    border-radius: 6px;\n}\n\n/* Min chest requirement (displayed on left side bar)*/\n.hud-min-chests {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 6px;\n    padding: 4px 23px;\n    background-color: rgba(0,0,0,0.35);\n    border-radius: 6px;\n    margin-top: 4px;\n    justify-content: center;\n}\n\n.hud-min-chests-lbl {\n    font-size: 18px;\n    font-weight: 700;\n    color: rgba(255,255,255,0.55);\n}\n\n.hud-min-chests-val {\n    font-size: 18px;\n    font-weight: 900;\n    color: rgba(251,191,36,0.90);\n}\n\n/* MUSIC DEBUG */\n.mm-dev-music-panel {\n    position: absolute;\n    bottom: 20px;\n    right: 20px;\n    display: flex;\n    flex-direction: column;\n    gap: 4px;\n    padding: 8px;\n    background-color: rgba(4,1,13,0.90);\n    border: 1px dashed rgba(244,114,182,0.30);\n    border-radius: 6px;\n    z-index: 99;\n    pointer-events: all;\n}\n\n.mm-dev-music-label {\n    font-size: 9px;\n    font-weight: 900;\n    color: rgba(244,114,182,0.60);\n    letter-spacing: 0.20em;\n    text-transform: uppercase;\n    margin-bottom: 2px;\n}\n\n.mm-dev-btn {\n    background-color: rgba(244,114,182,0.15);\n    border: 1px solid rgba(244,114,182,0.30);\n    border-radius: 4px;\n    color: rgba(244,114,182,0.85);\n    font-size: 10px;\n    font-weight: 700;\n    padding: 4px 8px;\n    cursor: none;\n    pointer-events: all;\n    letter-spacing: 0.05em;\n}\n\n.mm-dev-btn:hover {\n    background-color: rgba(244,114,182,0.25);\n}\n\n.mm-dev-btn-active {\n    background-color: rgba(52,211,153,0.20);\n    border-color: rgba(52,211,153,0.50);\n    color: rgba(52,211,153,0.90);\n}\n\n/* =============== New SURVIVAL mode =============== */\n.survival-timer {\n    position: absolute;\n    top: 100px;\n    left: 50%;\n    transform: translateX(-50%);\n    width: 160px;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: center;\n    gap: 8px;\n    padding: 8px 16px;\n    background-color: rgba(10,4,28,0.80);\n    border: 2px solid rgba(239,68,68,0.35);\n    border-radius: 10px;\n    box-shadow: 3px 3px 0px rgba(0,0,0,0.55);\n    z-index: 10;\n}\n\n.survival-timer-icon {\n    font-size: 16px;\n}\n\n.survival-timer-val {\n    font-size: 20px;\n    font-weight: 900;\n    color: rgba(251,191,36,0.90);\n    letter-spacing: 0.04em;\n    font-variant-numeric: tabular-nums;\n}\n\n.survival-spawn-track {\n    position: absolute;\n    top: 145px; /* just below the timer pill */\n    left: 50%;\n    transform: translateX(-50%);\n    width: 160px;\n    height: 6px;\n    background-color: rgba(255,255,255,0.08);\n    border-radius: 3px;\n    overflow: hidden;\n    z-index: 10;\n}\n\n.survival-spawn-fill {\n    height: 100%;\n    background-color: rgba(251,191,36,0.85);\n    transition: width 0.1s linear;\n}\n\n/* CAT */\n.mm-pet-hero-emoji image {\n    width: 170px;   /* bump this up to whatever size you want */\n    height: 170px;\n    image-rendering: pixelated; /* keep it crisp if it's pixel art */\n    margin-left: -10px;\n}\n\n.mm-pet-hero-emoji {\n    position: relative;\n}\n.cat-anchor { position: relative; }\n\n\n\n.cat-speech-bubble {\n    position: absolute;\n    top: -50px;\n    left: 50%;\n    transform: translateX(-50%);\n    background-color: rgba(255,255,255,0.95);\n    color: #1a1030;\n    font-size: 14px;\n    font-weight: 800;\n    padding: 8px 14px;\n    border-radius: 12px;\n    white-space: nowrap;\n    box-shadow: 2px 2px 0px rgba(0,0,0,0.4);\n    z-index: 10;\n    animation: bubble-pop 0.2s ease-out both;\n}\n\n.cat-speech-bubble::after {\n    content: \"\";\n    position: absolute;\n    bottom: -6px;\n    left: 50%;\n    width: 12px;\n    height: 12px;\n    background-color: rgba(255,255,255,0.95);\n    transform: translateX(-50%) rotate(45deg);\n}\n\n@keyframes bubble-pop {\n    0%   { opacity: 0; transform: translateX(-50%) scale(0.7); }\n    100% { opacity: 1; transform: translateX(-50%) scale(1.0); }\n}\n\n/* Back button */\n.back-toggle {\n    top: 211px; /* 155 + 44 + 12 gap */\n    background-color: rgba(60,60,180,0.85);\n    border-color: rgba(140,140,255,0.60);\n}\n\n.back-toggle:hover {\n    background-color: rgba(80,80,220,0.95);\n    border-color: rgba(160,160,255,0.80);\n}\n\n.back-confirm-overlay {\n    position: absolute;\n    top: 0; left: 0; right: 0; bottom: 0;\n    background-color: rgba(4,1,13,0.85);\n    z-index: 90;\n    pointer-events: all;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n}\n\n.back-confirm-popup {\n    background-color: rgba(20,8,50,0.98);\n    border: 2px solid rgba(167,139,250,0.55);\n    border-radius: 18px;\n    padding: 32px 40px;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    gap: 16px;\n    max-width: 380px;\n    text-align: center;\n    box-shadow: 6px 6px 0px rgba(20,0,60,0.80);\n}\n\n.back-confirm-title { font-size: 22px; font-weight: 900; color: white; }\n.back-confirm-desc { font-size: 13px; color: rgba(255,255,255,0.55); line-height: 1.5; }\n.back-confirm-btns { display: flex; flex-direction: row; gap: 12px; margin-top: 8px; }\n\n.back-confirm-btn {\n    border-radius: 10px;\n    font-size: 14px;\n    font-weight: 800;\n    padding: 12px 20px;\n    cursor: none;\n    pointer-events: all;\n    border: 2px solid transparent;\n}\n\n.back-confirm-btn.cancel { background-color: rgba(52,211,153,0.15); border-color: rgba(52,211,153,0.45); color: #6ee7b7; }\n.back-confirm-btn.confirm { background-color: rgba(239,68,68,0.15); border-color: rgba(239,68,68,0.45); color: #fca5a5; }\n\n\n/* Survival hype track */\n.survival-strip {\n    position: absolute;\n    top: 96px;\n    left: 0;\n    right: 0;\n    height: 44px;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    justify-content: center;\n    gap: 10px;\n    z-index: 10;\n    pointer-events: none;\n}\n\n.survival-timer {\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 6px;\n    padding: 5px 12px;\n    background-color: rgba(10,4,28,0.85);\n    border: 2px solid rgba(239,68,68,0.35);\n    border-radius: 8px;\n    flex-shrink: 0;\n}\n.survival-timer-icon { font-size: 14px; }\n.survival-timer-val {\n    font-size: 18px;\n    font-weight: 900;\n    color: rgba(251,191,36,0.9);\n}\n\n.survival-hype-track {\n    position: relative;\n    width: 130px;\n    height: 20px;\n    background-color: rgba(255,255,255,0.08);\n    border: 1px solid rgba(251,146,60,0.35);\n    border-radius: 6px;\n    overflow: hidden;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    flex-shrink: 0;\n}\n.survival-hype-fill {\n    position: absolute;\n    top: 0; left: 0; bottom: 0;\n    background-color: rgba(251,146,60,0.75);\n    transition: width 0.12s linear;\n}\n.survival-hype-track.maxed {\n    border-color: rgba(251,191,36,0.95);\n    animation: hype-pulse 0.5s ease-in-out infinite alternate;\n}\n.survival-hype-track.maxed .survival-hype-fill { background-color: rgba(251,191,36,0.9); }\n.survival-hype-lbl {\n    font-size: 9px;\n    font-weight: 900;\n    letter-spacing: 0.12em;\n    color: rgba(255,255,255,0.9);\n    z-index: 1;\n}\n@keyframes hype-pulse {\n    from { border-color: rgba(251,191,36,0.5); }\n    to   { border-color: rgba(251,191,36,1.0); }\n}\n\n.survival-goal {\n    position: relative;\n    display: flex;\n    flex-direction: row;\n    align-items: center;\n    gap: 8px;\n    padding: 5px 12px;\n    background-color: rgba(10,4,28,0.85);\n    border: 1px solid rgba(167,139,250,0.35);\n    border-radius: 8px;\n    flex-shrink: 0;\n}\n.survival-goal-lbl  { font-size: 11px; font-weight: 800; color: rgba(255,255,255,0.85); }\n.survival-goal-prog { font-size: 12px; font-weight: 900; color: #a78bfa; }\n.survival-goal-timer {\n    position: absolute;\n    bottom: 0; left: 0; right: 0;\n    height: 2px;\n    background-color: rgba(255,255,255,0.06);\n}\n.survival-goal-timer-fill {\n    height: 100%;\n    background-color: rgba(167,139,250,0.7);\n    transition: width 0.2s linear;\n}\n\n.survival-spawn-track {\n    width: 90px;\n    height: 6px;\n    background-color: rgba(255,255,255,0.08);\n    border-radius: 3px;\n    overflow: hidden;\n    flex-shrink: 0;\n}\n.survival-spawn-fill {\n    height: 100%;\n    background-color: rgba(251,191,36,0.85);\n    transition: width 0.1s linear;\n}"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "styles/base/_splitcontainer.scss",
            "FileName": "_splitcontainer.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "\r\n.splitcontainer\r\n{\r\n\tflex-grow: 1;\r\n\r\n\t> .split-left, > .split-right\r\n\t{\r\n\t\tflex-grow: 1;\r\n\t}\r\n\r\n\t> .split-left\r\n\t{\r\n\t\theight: 100%;\r\n\t}\r\n\r\n\t> .splitter\r\n\t{\r\n\t\tflex-grow: 1;\r\n\t\twidth: 8px;\r\n\t\tcursor: ew-resize;\r\n\r\n\t\t&:hover\r\n\t\t{\r\n\t\t}\r\n\t}\r\n\r\n\t&.dragging > .splitter\r\n\t{\r\n\t}\r\n\r\n\t&.vertical\r\n\t{\r\n\t\tflex-direction: column;\r\n\r\n\t\t> .splitter\r\n\t\t{\r\n\t\t\theight: 8px;\r\n\t\t\twidth: 100%;\r\n\t\t\tcursor: ns-resize;\r\n\t\t}\r\n\t}\r\n}"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "styles/form/_coloreditor.scss",
            "FileName": "_coloreditor.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "\r\n$primary: red !default;\r\n$primary-alt: white !default;\r\n\r\n$switch-padding: 6px !default;\r\n\r\n.coloreditor\r\n{\r\n\tflex-direction: column;\r\n\tmin-width: 250px;\r\n\tmax-width: 250px;\r\n\r\n\t> .canvas\r\n\t{\r\n\t\tborder-top: 1px solid rgba( white, 0.5 );\r\n\t\tflex-grow: 1;\r\n\t\tflex-direction: column;\r\n\t\tpadding: 10px 15px;\r\n\r\n\r\n\t\t> textentry.textentry\r\n\t\t{\r\n\t\t\tflex-grow: 0;\r\n\t\t\tflex-shrink: 0;\r\n\t\t\twidth: auto;\r\n\t\t}\r\n\t}\r\n\r\n\t.slider\r\n\t{\r\n\t\tpadding: 6.5px;\r\n\t}\r\n\r\n\t.slider .track\r\n\t{\r\n\t\theight: 14px;\r\n\t\tborder-radius: 14px;\r\n\t\tleft: 0px;\r\n\t\tright: 0px;\r\n\t\ttransition: all 0.1s ease-out;\r\n\r\n\t\t.inner\r\n\t\t{\r\n\t\t\tdisplay: none;\r\n\t\t}\r\n\t}\r\n\r\n\t.slider:hover, .slider:active\r\n\t{\r\n\t\tpadding: 7px;\r\n\r\n\t\t.track\r\n\t\t{\r\n\t\t\theight: 20px;\r\n\t\t\tborder-radius: 6px;\r\n\t\t}\r\n\r\n\t\t.thumb\r\n\t\t{\r\n\t\t\twidth: 10px;\r\n\t\t\theight: 16px;\r\n\t\t\tmargin-left: 2px;\r\n\t\t}\r\n\t}\r\n\r\n\t.slider .thumb\r\n\t{\r\n\t\twidth: 10px;\r\n\t\theight: 10px;\r\n\t\tbox-shadow: 0px 0px 0px transparent;\r\n\t\tbackground-color: white;\r\n\t\tbackdrop-filter: invert( 1 );\r\n\t\tmargin-left: 1px;\r\n\t\tpointer-events: none;\r\n\t}\r\n\r\n\t.alpha_slider .track\r\n\t{\r\n\t\tbackground-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );\r\n\t}\r\n\r\n\t.hue .track\r\n\t{\r\n\t\tbackground: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);\r\n\r\n\t\t.inner\r\n\t\t{\r\n\t\t\tdisplay: none;\r\n\t\t}\r\n\t}\r\n\r\n\t.satval\r\n\t{\r\n\t\tbackground: linear-gradient(to right, white, transparent);\r\n\t\theight: 200px;\r\n\t\tposition: relative;\r\n\r\n\t\t&:active\r\n\t\t{\r\n\t\t\tz-index: 10;\r\n\t\t\tcursor: none;\r\n\t\t}\r\n\r\n\t\t.value_gradient\r\n\t\t{\r\n\t\t\tbackground: linear-gradient(to bottom, black, white);\r\n\t\t\tposition: absolute;\r\n\t\t\ttop: 0;\r\n\t\t\tleft: 0;\r\n\t\t\tright: 0;\r\n\t\t\tbottom: 0;\r\n\t\t\tpointer-events: none;\r\n\t\t\tmix-blend-mode: multiply;\r\n\t\t}\r\n\r\n\t\t.thumb\r\n\t\t{\r\n\t\t\tz-index: 10;\r\n\t\t}\r\n\t}\r\n\r\n\t.presets\r\n\t{\r\n\t\tflex-wrap: wrap;\r\n\t\talign-content: center;\r\n\t\tjustify-content: center;\r\n\t\tpadding: 15px;\r\n\t\tborder-top: 1px solid rgba( white, 0.5 );\r\n\r\n\t\t.preset\r\n\t\t{\r\n\t\t\tbackground-color: white;\r\n\t\t\twidth: 16px;\r\n\t\t\theight: 16px;\r\n\t\t\tmargin: 2px;\r\n\t\t\tborder-radius: 20px;\r\n\t\t\tcursor: pointer;\r\n\t\t\t\r\n\t\t\t&:hover\r\n\t\t\t{\r\n\t\t\t\tbox-shadow: 0px 0px 1px 2px white;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\n\r\n.coloreditorrgba\r\n{\r\n\tflex-direction: column;\r\n\r\n\t.slider\r\n\t{\r\n\t\tpadding-right: 16px;\r\n\t}\r\n\r\n\t.slider .track\r\n\t{\r\n\t\tbackground-color: transparent;\r\n\t\theight: 14px;\r\n\t\tborder-radius: 14px;\r\n\t\tleft: 0px;\r\n\t\tright: 0px;\r\n\r\n\t\t.inner\r\n\t\t{\r\n\t\t\tdisplay: none;\r\n\t\t}\r\n\t}\r\n\r\n\t.slider .thumb\r\n\t{\r\n\t\twidth: 12px;\r\n\t\theight: 12px;\r\n\t\tbox-shadow: 0px 0px 0px transparent;\r\n\t\tbackground-color: white;\r\n\t\tmargin-left: 2px;\r\n\t\tbackdrop-filter: invert( 1 );\r\n\t}\r\n\r\n\t.red_slider .track\r\n\t{\r\n\t\tbackground-image: linear-gradient( to right, rgba( #e63439, 0.1 ), rgba( #e63439, 0.9 ) );\r\n\t}\r\n\r\n\t.green_slider .track\r\n\t{\r\n\t\tbackground-image: linear-gradient( to right, rgba( #54be35, 0.1 ), rgba( #54be35, 0.9 ) );\r\n\t}\r\n\r\n\t.blue_slider .track\r\n\t{\r\n\t\tbackground-image: linear-gradient( to right, rgba( #3472e6, 0.1 ), rgba( #3472e6, 0.9 ) );\r\n\t}\r\n\r\n\t.alpha_slider .track\r\n\t{\r\n\t\tbackground-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );\r\n\t}\r\n}\r\n\r\n\r\n.coloreditorhsva\r\n{\r\n\tflex-direction: column;\r\n\r\n\t.slider\r\n\t{\r\n\t\tpadding-right: 16px;\r\n\t}\r\n\r\n\t.slider .track\r\n\t{\r\n\t\tbackground-color: transparent;\r\n\t\theight: 14px;\r\n\t\tborder-radius: 14px;\r\n\t\tleft: 0px;\r\n\t\tright: 0px;\r\n\r\n\t\t.inner\r\n\t\t{\r\n\t\t\tdisplay: none;\r\n\t\t}\r\n\t}\r\n\r\n\t.slider .thumb\r\n\t{\r\n\t\twidth: 12px;\r\n\t\theight: 12px;\r\n\t\tbox-shadow: 0px 0px 0px transparent;\r\n\t\tbackground-color: white;\r\n\t\tmargin-left: 2px;\r\n\t\tbackdrop-filter: invert( 1 );\r\n\t}\r\n\t\t\r\n\t.hue_slider .track\r\n\t{\r\n\t\tbackground-image: linear-gradient( to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00 );\r\n\t}\r\n\r\n\t.alpha_slider .track\r\n\t{\r\n\t\tbackground-image: linear-gradient( to right, rgba( #ccc, 0.05 ), rgba( #ccc, 0.9 ) );\r\n\t}\r\n}"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "styles/form/_colorproperty.scss",
            "FileName": "_colorproperty.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "\r\n\r\n.colorproperty\r\n{\r\n\talign-items: center;\r\n\tposition: relative;\r\n\r\n\t.colorsquare\r\n\t{\r\n\t\twidth: 20px;\r\n\t\theight: 20px;\r\n\t\tborder-radius: 4px;\r\n\t\tmargin-right: 8px;\r\n\t\tposition: absolute;\r\n\t\tleft: 7px;\r\n\t\tz-index: 1;\r\n\t\tcursor: pointer;\r\n\t}\r\n\r\n\t> .textentry:not( .a.b.c )\r\n\t{\r\n\t\tpadding-left: 36px;\r\n\t}\r\n}"
        },
        {
            "Ident": "playnplay.chain_reaction",
            "Path": "ui/controls/color/coloralphacontrol.cs.scss",
            "FileName": "coloralphacontrol.cs.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 343132,
            "Code": "ColorAlphaControl\r\n{\r\n\tgap: 0.5rem;\r\n\tflex-grow: 1;\r\n\tpointer-events: all;\r\n\tbackground: linear-gradient( to right, black, white );\r\n\tborder-radius: 4px;\r\n\tpadding: 2px;\r\n\theight: 12px;\r\n\tposition: relative;\r\n\tcursor: pointer;\r\n\tborder: 1px solid #333;\r\n\r\n\t&:hover\r\n\t{\r\n\t\tborder: 1px solid #08f;\r\n\t}\r\n\r\n\t&:active\r\n\t{\r\n\t\tborder: 1px solid #fff;\r\n\t}\r\n\r\n\t.handle\r\n\t{\r\n\t\ttop: -5px;\r\n\t\tbottom: -5px;\r\n\t\taspect-ratio: 1;\r\n\t\tborder-radius: 100px;\r\n\t\tborder: 2px solid #444;\r\n\t\tposition: absolute;\r\n\t\tbackground-color: white;\r\n\t\tbox-shadow: 2px 2px 16px #000a;\r\n\t\ttransform: translateX( -50% );\r\n\t\tpointer-events: none;\r\n\t}\r\n}\r\n"
        }
    ]
}