π s&box Package Code Search
Search C# source code, UI razor templates, shaders, and configs across s&box packages.
π Raw Facepunch API Link: https://public.facepunch.com/sbox/code/search/1/?ident=playnplay.chain_reaction&take=20
Showing code results for query:
*
(92 total matches found)
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")]
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);
}
}
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 => "π",
_ => "π",
};
}
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%;
}
}
Game
game
VectorControl
{
gap: 2px;
flex-grow: 1;
}
VectorControl NumberEntry
{
flex-basis: 50%;
}
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;
}
}
}
}
Game
game
ColorPickerControl
{
flex-direction: column;
flex-shrink: 0;
gap: 0.5rem;
margin: 1rem;
}
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); }
}
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);
}
Game
game
using Sandbox;
public enum GameMode
{
Wave,
Survival,
Idle, // Reactor mode β the self-running chain reaction economy
}
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.",
};
}
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; }
}
}
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;
}
}
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;
}
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;
}
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;
}
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;
}
}
}
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 ) );
}
}
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;
}
}
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"
}
]
}