{"TotalCount":58,"Files":[{"Ident":"fpkreastudios.desertpump","Path":".obj/__compiler_extra.cs","FileName":"__compiler_extra.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"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( \u0022AddonTitle\u0022, \u0022Desert Pump\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonIdent\u0022, \u0022desertpump\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022OrgIdent\u0022, \u0022fpkreastudios\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022Ident\u0022, \u0022fpkreastudios.desertpump\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineVersion\u0022, \u002228\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineMinorVersion\u0022, \u00221\u0022 )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \u0022.NETCoreApp,Version=v9.0\u0022, FrameworkDisplayName = \u0022.NET 9.0\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022CompileTime\u0022, \u00222026-08-20T18:13:47.6067714Z\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\u00220.0.110.0\u0022)]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\u00220.0.110.0\u0022)]"},{"Ident":"fpkreastudios.desertpump","Path":"Game/DesertPumpGame.cs","FileName":"DesertPumpGame.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":"namespace DesertPump;\r\n\r\n/// \u003Csummary\u003E\r\n/// The whole game. Holds the money, the water and the current pump, and is the only\r\n/// thing allowed to change them - the HUD just calls into here and renders the result.\r\n/// \u003C/summary\u003E\r\n[Title( \u0022Desert Pump Game\u0022 )]\r\n[Category( \u0022Desert Pump\u0022 )]\r\n[Icon( \u0022water_drop\u0022 )]\r\npublic sealed partial class DesertPumpGame : Component\r\n{\r\n\t/// \u003Csummary\u003EThe active game, so the HUD doesn\u0027t need a wired-up reference.\u003C/summary\u003E\r\n\tpublic static DesertPumpGame Current { get; private set; }\r\n\r\n\t[Property, Group( \u0022Save\u0022 )]\r\n\tpublic bool SaveEnabled { get; set; } = true;\r\n\r\n\t[Property, Group( \u0022Save\u0022 )]\r\n\tpublic string SaveFile { get; set; } = \u0022desertpump.json\u0022;\r\n\r\n\t/// \u003Csummary\u003ESeconds between autosaves. Progress also saves on shutdown.\u003C/summary\u003E\r\n\t[Property, Group( \u0022Save\u0022 ), Range( 2f, 60f )]\r\n\tpublic float SaveInterval { get; set; } = 5f;\r\n\r\n\t/// \u003Csummary\u003ELongest stretch of time away that still pays out idle water.\u003C/summary\u003E\r\n\t[Property, Group( \u0022Save\u0022 ), Range( 0f, 24f )]\r\n\tpublic float MaxIdleHours { get; set; } = 4f;\r\n\r\n\t[Property, Group( \u0022Audio\u0022 )] public SoundEvent PumpSound { get; set; }\r\n\t[Property, Group( \u0022Audio\u0022 )] public SoundEvent SellSound { get; set; }\r\n\t[Property, Group( \u0022Audio\u0022 )] public SoundEvent UpgradeSound { get; set; }\r\n\t[Property, Group( \u0022Audio\u0022 )] public SoundEvent DeniedSound { get; set; }\r\n\r\n\t/// \u003Csummary\u003EPlayed on a surge. Falls back to the upgrade sting when it\u0027s left empty.\u003C/summary\u003E\r\n\t[Property, Group( \u0022Audio\u0022 )] public SoundEvent SurgeSound { get; set; }\r\n\r\n\tpublic double Coins { get; private set; }\r\n\tpublic double Water { get; private set; }\r\n\r\n\t/// \u003Csummary\u003EIndex into \u003Csee cref=\u0022PumpModel.All\u0022/\u003E.\u003C/summary\u003E\r\n\tpublic int PumpLevel { get; private set; }\r\n\r\n\t/// \u003Csummary\u003EShop upgrade levels, indexed by \u003Csee cref=\u0022UpgradeKind\u0022/\u003E.\u003C/summary\u003E\r\n\treadonly int[] upgradeLevels = new int[3];\r\n\r\n\tpublic bool Muted =\u003E PumpSettings.Current.Muted;\r\n\r\n\tpublic long TotalPumps { get; private set; }\r\n\tpublic double TotalEarned { get; private set; }\r\n\tpublic double TotalLitresSold { get; private set; }\r\n\r\n\t/// \u003Csummary\u003ELitres the pump produced while the game was closed, for the welcome-back note.\u003C/summary\u003E\r\n\tpublic double IdleCatchUp { get; private set; }\r\n\r\n\tpublic PumpModel CurrentPump =\u003E PumpModel.Get( PumpLevel );\r\n\tpublic PumpTier Tier =\u003E CurrentPump.Tier;\r\n\tpublic PumpModel NextPump =\u003E PumpLevel \u003C PumpModel.MaxIndex ? PumpModel.Get( PumpLevel \u002B 1 ) : null;\r\n\tpublic bool IsMaxed =\u003E NextPump is null;\r\n\r\n\t// ---- effective stats: the tier\u0027s base numbers times whatever the shop has sold you\r\n\r\n\t/// \u003Csummary\u003ETank size including the Water Tank upgrade.\u003C/summary\u003E\r\n\tpublic double TankCapacity =\u003E CurrentPump.Tank * MultiplierOf( UpgradeKind.Storage );\r\n\r\n\t/// \u003Csummary\u003ELitres per manual pump, including Pump Power.\u003C/summary\u003E\r\n\tpublic double LitresPerClick =\u003E CurrentPump.PerClick * MultiplierOf( UpgradeKind.Power );\r\n\r\n\t/// \u003Csummary\u003ECoins per litre, including Market Contacts.\u003C/summary\u003E\r\n\tpublic double PricePerLitre =\u003E CurrentPump.PricePerLitre * MultiplierOf( UpgradeKind.Value );\r\n\r\n\tpublic bool TankFull =\u003E Water \u003E= TankCapacity - 0.0001;\r\n\tpublic float TankFraction =\u003E TankCapacity \u003C= 0 ? 0f : (float)Math.Clamp( Water / TankCapacity, 0d, 1d );\r\n\r\n\t/// \u003Csummary\u003EWhat the tank is worth right now.\u003C/summary\u003E\r\n\tpublic double SaleValue =\u003E Water * PricePerLitre;\r\n\r\n\tpublic bool CanSell =\u003E Water \u003E 0.0001;\r\n\tpublic bool CanUpgrade =\u003E NextPump is not null \u0026\u0026 Coins \u003E= NextPump.Cost;\r\n\r\n\tpublic int LevelOf( UpgradeKind kind ) =\u003E upgradeLevels[(int)kind];\r\n\r\n\tpublic float MultiplierOf( UpgradeKind kind ) =\u003E UpgradeTrack.Get( kind ).MultiplierAt( LevelOf( kind ) );\r\n\r\n\tpublic bool IsMaxLevel( UpgradeKind kind ) =\u003E LevelOf( kind ) \u003E= UpgradeTrack.Get( kind ).MaxLevel;\r\n\r\n\t/// \u003Csummary\u003ECost of the next level, or infinity once the track is maxed.\u003C/summary\u003E\r\n\tpublic double CostOf( UpgradeKind kind )\r\n\t{\r\n\t\treturn IsMaxLevel( kind )\r\n\t\t\t? double.PositiveInfinity\r\n\t\t\t: UpgradeTrack.Get( kind ).CostAt( LevelOf( kind ) );\r\n\t}\r\n\r\n\tpublic bool CanBuy( UpgradeKind kind ) =\u003E !IsMaxLevel( kind ) \u0026\u0026 Coins \u003E= CostOf( kind );\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Fired on an ordinary manual pump, with the litres gained. A pump that came up a\r\n\t/// surge fires \u003Csee cref=\u0022Surged\u0022/\u003E instead - exactly one of the two per click.\r\n\t/// \u003C/summary\u003E\r\n\tpublic Action\u003Cdouble\u003E Pumped { get; set; }\r\n\r\n\t/// \u003Csummary\u003EFired on a sale, with the coins gained.\u003C/summary\u003E\r\n\tpublic Action\u003Cdouble\u003E Sold { get; set; }\r\n\r\n\t/// \u003Csummary\u003EFired after buying a new pump, with the tier we moved to.\u003C/summary\u003E\r\n\tpublic Action\u003CPumpModel\u003E Upgraded { get; set; }\r\n\r\n\t/// \u003Csummary\u003EFired when an action was refused - tank full, broke, nothing to sell.\u003C/summary\u003E\r\n\tpublic Action\u003Cstring\u003E Refused { get; set; }\r\n\r\n\t/// \u003Csummary\u003EFired after buying a shop upgrade.\u003C/summary\u003E\r\n\tpublic Action\u003CUpgradeTrack\u003E Purchased { get; set; }\r\n\r\n\tTimeSince timeSinceSave;\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// True once \u003Csee cref=\u0022Load\u0022/\u003E has run, which only happens in play mode. The copy of\r\n\t/// this component living in the editor\u0027s scene never wakes, so it sits on default\r\n\t/// zeroes - and without this guard its shutdown save would write those zeroes straight\r\n\t/// over a real run. Never write state we haven\u0027t read.\r\n\t/// \u003C/summary\u003E\r\n\tbool loaded;\r\n\r\n\tprotected override void OnAwake()\r\n\t{\r\n\t\tCurrent = this;\r\n\t\tLoad();\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Quitting tears the scene down, and which of these fires first depends on how you\r\n\t/// left - closing the window, stopping play mode, or loading another scene. Saving\r\n\t/// from both, plus after every earn and spend, means there\u0027s no way out that loses\r\n\t/// a run.\r\n\t/// \u003C/summary\u003E\r\n\tprotected override void OnDisabled()\r\n\t{\r\n\t\tSave();\r\n\t}\r\n\r\n\tprotected override void OnDestroy()\r\n\t{\r\n\t\tSave();\r\n\r\n\t\tif ( Current == this )\r\n\t\t\tCurrent = null;\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\t// A code hotload wipes statics without re-running OnAwake, which would leave\r\n\t\t// Current null and the HUD showing \u0022no game in the scene\u0022 for the rest of the\r\n\t\t// session. Cheaper to re-claim it than to debug that every time.\r\n\t\tif ( Current != this ) Current = this;\r\n\r\n\t\t// This is a mouse game - without this the cursor gets locked to the view in\r\n\t\t// play mode and none of the buttons can be clicked.\r\n\t\tMouse.Visibility = MouseVisibility.Visible;\r\n\r\n\t\t// Space bar is a second pump button so you\u0027re not glued to the mouse.\r\n\t\tif ( Input.Pressed( \u0022Jump\u0022 ) )\r\n\t\t{\r\n\t\t\tPump();\r\n\t\t}\r\n\r\n\t\t// The pump and everyone you\u0027ve hired, together.\r\n\t\tvar passive = TotalWaterPerSecond * Time.Delta;\r\n\t\tif ( passive \u003E 0f )\r\n\t\t{\r\n\t\t\tAddWater( passive );\r\n\t\t}\r\n\r\n\t\tTickPressure();\r\n\t\tTickGusher();\r\n\t\tTickTree();\r\n\r\n\t\tif ( PumpSettings.Current.AutoSell \u0026\u0026 TankFull )\r\n\t\t{\r\n\t\t\tSellAll();\r\n\t\t}\r\n\r\n\t\tif ( SaveEnabled \u0026\u0026 timeSinceSave \u003E SaveInterval )\r\n\t\t{\r\n\t\t\tSave();\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// One manual pump. Returns the litres actually gained - zero means the tank was already full.\r\n\t/// \u003C/summary\u003E\r\n\t/// \u003Cremarks\u003E\r\n\t/// Pressure and surges only exist on this path. Idle water, workers and the tree all\r\n\t/// go through \u003Csee cref=\u0022AddWater\u0022/\u003E untouched, so hands-off income is exactly what\r\n\t/// it always was.\r\n\t/// \u003C/remarks\u003E\r\n\tpublic double Pump()\r\n\t{\r\n\t\tif ( TankFull )\r\n\t\t{\r\n\t\t\tRefuse( \u0022Tank is full - sell your water\u0022 );\r\n\t\t\treturn 0;\r\n\t\t}\r\n\r\n\t\tvar surge = RollSurge();\r\n\t\tvar litres = LitresPerClick * PressureBonus * (surge ? SurgeMultiplier : 1f);\r\n\r\n\t\tvar gained = AddWater( litres );\r\n\r\n\t\t// Pressure builds even on the click that overflowed the tank - the rhythm is the\r\n\t\t// player\u0027s, and dropping it because the tank happened to be full feels like a bug.\r\n\t\tBumpPressure();\r\n\t\tTotalPumps\u002B\u002B;\r\n\r\n\t\tif ( surge )\r\n\t\t{\r\n\t\t\tSurgeCount\u002B\u002B;\r\n\t\t\tPlay( SurgeSound ?? UpgradeSound );\r\n\t\t\tSurged?.Invoke( gained );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tPlay( PumpSound );\r\n\t\t\tPumped?.Invoke( gained );\r\n\t\t}\r\n\r\n\t\treturn gained;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Empty the tank into coins. Returns what we earned.\r\n\t/// \u003C/summary\u003E\r\n\tpublic double SellAll()\r\n\t{\r\n\t\tif ( !CanSell )\r\n\t\t{\r\n\t\t\tRefuse( \u0022No water to sell\u0022 );\r\n\t\t\treturn 0;\r\n\t\t}\r\n\r\n\t\tvar litres = Water;\r\n\t\tvar earned = SaleValue;\r\n\r\n\t\tWater = 0;\r\n\t\tCoins \u002B= earned;\r\n\t\tTotalEarned \u002B= earned;\r\n\t\tTotalLitresSold \u002B= litres;\r\n\t\tIdleCatchUp = 0;\r\n\r\n\t\tPlay( SellSound );\r\n\t\tSold?.Invoke( earned );\r\n\t\tSave();\r\n\r\n\t\treturn earned;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Buy the next pump in the list. Water carries over, clamped to the new tank.\r\n\t/// \u003C/summary\u003E\r\n\tpublic bool BuyNextPump()\r\n\t{\r\n\t\tvar next = NextPump;\r\n\r\n\t\tif ( next is null )\r\n\t\t{\r\n\t\t\tRefuse( \u0022You own every pump in the desert\u0022 );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tif ( Coins \u003C next.Cost )\r\n\t\t{\r\n\t\t\tRefuse( $\u0022Need {Numbers.Short( next.Cost - Coins )} more coins\u0022 );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tCoins -= next.Cost;\r\n\t\tPumpLevel = next.Index;\r\n\t\tWater = Math.Min( Water, TankCapacity );\r\n\r\n\t\tPlay( UpgradeSound );\r\n\t\tUpgraded?.Invoke( next );\r\n\t\tSave();\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EHand over coins from outside the sell loop - rewards, console commands.\u003C/summary\u003E\r\n\tpublic void AddCoins( double amount )\r\n\t{\r\n\t\t// A NaN or infinity in here poisons the save and every number on screen with\r\n\t\t// no way back, so refuse it at the door rather than storing it.\r\n\t\tif ( amount \u003C= 0 || double.IsNaN( amount ) || double.IsInfinity( amount ) )\r\n\t\t\treturn;\r\n\r\n\t\tCoins \u002B= amount;\r\n\t\tTotalEarned \u002B= amount;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Buy one level of a shop upgrade. Returns false and complains if you can\u0027t afford it.\r\n\t/// \u003C/summary\u003E\r\n\tpublic bool BuyUpgrade( UpgradeKind kind )\r\n\t{\r\n\t\tvar track = UpgradeTrack.Get( kind );\r\n\r\n\t\tif ( IsMaxLevel( kind ) )\r\n\t\t{\r\n\t\t\tRefuse( $\u0022{track.Name} is fully upgraded\u0022 );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar cost = CostOf( kind );\r\n\t\tif ( Coins \u003C cost )\r\n\t\t{\r\n\t\t\tRefuse( $\u0022Need {Numbers.Short( cost - Coins )} more coins\u0022 );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tCoins -= cost;\r\n\t\tupgradeLevels[(int)kind]\u002B\u002B;\r\n\r\n\t\t// A smaller tank than the water in it can\u0027t happen, but a bigger one is free room.\r\n\t\tWater = Math.Min( Water, TankCapacity );\r\n\r\n\t\tPlay( UpgradeSound );\r\n\t\tPurchased?.Invoke( track );\r\n\t\tSave();\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic void ToggleMute()\r\n\t{\r\n\t\tvar settings = PumpSettings.Current;\r\n\r\n\t\tsettings.Muted = !settings.Muted;\r\n\t\tsettings.Save();\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EWipe the save and start over from the hand pump.\u003C/summary\u003E\r\n\tpublic void ResetProgress()\r\n\t{\r\n\t\tCoins = 0;\r\n\t\tWater = 0;\r\n\t\tPumpLevel = 0;\r\n\t\tArray.Clear( upgradeLevels );\r\n\t\townedBackgrounds.Clear();\r\n\t\townedBackgrounds.Add( BackgroundStyle.DefaultId );\r\n\t\tSelectedBackground = BackgroundStyle.DefaultId;\r\n\t\tlastDailyDay = 0;\r\n\t\tDailyStreak = 0;\r\n\t\tArray.Clear( workerLevels );\r\n\t\tTreeHeight = 0;\r\n\t\tTreeWater = 0;\r\n\t\ttreeGrowsAt = 0;\r\n\t\tTotalPumps = 0;\r\n\t\tTotalEarned = 0;\r\n\t\tTotalLitresSold = 0;\r\n\t\tIdleCatchUp = 0;\r\n\t\tPressure = 0;\r\n\t\tSurgeCount = 0;\r\n\r\n\t\tSave();\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EAdds water up to the tank limit and returns how much actually fit.\u003C/summary\u003E\r\n\tdouble AddWater( double amount )\r\n\t{\r\n\t\tvar before = Water;\r\n\t\tWater = Math.Min( TankCapacity, Water \u002B amount );\r\n\r\n\t\treturn Water - before;\r\n\t}\r\n\r\n\tvoid Refuse( string reason )\r\n\t{\r\n\t\tPlay( DeniedSound );\r\n\t\tRefused?.Invoke( reason );\r\n\t}\r\n\r\n\tvoid Play( SoundEvent sound )\r\n\t{\r\n\t\tvar volume = PumpSettings.Current.EffectiveSfx;\r\n\r\n\t\tif ( sound is null || volume \u003C= 0f )\r\n\t\t\treturn;\r\n\r\n\t\tvar handle = Sound.Play( sound, (Sandbox.Audio.Mixer)null );\r\n\r\n\t\tif ( handle.IsValid() )\r\n\t\t{\r\n\t\t\thandle.Volume = volume;\r\n\t\t}\r\n\t}\r\n\r\n\tvoid Load()\r\n\t{\r\n\t\ttimeSinceSave = 0;\r\n\r\n\t\tif ( !SaveEnabled )\r\n\t\t\treturn;\r\n\r\n\t\t// From here on we own the file - a missing save just means a fresh run.\r\n\t\tloaded = true;\r\n\r\n\t\tvar save = ReadSave( SaveFile );\r\n\r\n\t\t// Nothing came back but there is a file on disk, so it is unreadable rather\r\n\t\t// than absent - a write cut short by a crash, most likely. The backup below is\r\n\t\t// one session behind, which beats handing the player a fresh run and no\r\n\t\t// explanation. This is the half of that feature that was missing: the backup\r\n\t\t// was being written every load and never once read.\r\n\t\tif ( save is null \u0026\u0026 FileSystem.Data.FileExists( SaveFile ) )\r\n\t\t{\r\n\t\t\tsave = ReadSave( SaveFile \u002B \u0022.bak\u0022 );\r\n\r\n\t\t\tLog.Warning( save is null\r\n\t\t\t\t? $\u0022{SaveFile} is unreadable and so is its backup - starting a fresh run.\u0022\r\n\t\t\t\t: $\u0022{SaveFile} is unreadable - restored the backup from the last session.\u0022 );\r\n\t\t}\r\n\r\n\t\tif ( save is null )\r\n\t\t\treturn;\r\n\r\n\t\t// Stash the run as we found it. One session of history is enough to rescue a\r\n\t\t// player whose save gets eaten by a bug, and it costs one small write per load.\r\n\t\tFileSystem.Data.WriteJson( SaveFile \u002B \u0022.bak\u0022, save );\r\n\r\n\t\tCoins = Math.Max( 0, save.Coins );\r\n\t\tPumpLevel = Math.Clamp( save.PumpLevel, 0, PumpModel.MaxIndex );\r\n\r\n\t\townedBackgrounds.Clear();\r\n\t\townedBackgrounds.Add( BackgroundStyle.DefaultId );\r\n\t\tforeach ( var id in save.OwnedBackgrounds ?? new List\u003Cstring\u003E() )\r\n\t\t{\r\n\t\t\townedBackgrounds.Add( id );\r\n\t\t}\r\n\r\n\t\tSelectedBackground = OwnsBackground( save.SelectedBackground )\r\n\t\t\t? save.SelectedBackground\r\n\t\t\t: BackgroundStyle.DefaultId;\r\n\r\n\t\tlastDailyDay = save.LastDailyDay;\r\n\t\tArray.Clear( workerLevels );\r\n\t\tvar levels = save.WorkerLevels ?? new List\u003Cint\u003E();\r\n\t\tfor ( var i = 0; i \u003C Math.Min( levels.Count, workerLevels.Length ); i\u002B\u002B )\r\n\t\t{\r\n\t\t\tworkerLevels[i] = Math.Clamp( levels[i], 0, WorkerType.MaxLevel );\r\n\t\t}\r\n\r\n\t\tTreeHeight = Math.Max( 0, save.TreeHeight );\r\n\t\tTreeWater = Math.Max( 0, save.TreeWater );\r\n\t\ttreeGrowsAt = save.TreeGrowsAt;\r\n\t\tDailyStreak = save.DailyStreak;\r\n\r\n\t\tupgradeLevels[(int)UpgradeKind.Storage] = ClampLevel( save.StorageLevel, UpgradeKind.Storage );\r\n\t\tupgradeLevels[(int)UpgradeKind.Power] = ClampLevel( save.PowerLevel, UpgradeKind.Power );\r\n\t\tupgradeLevels[(int)UpgradeKind.Value] = ClampLevel( save.ValueLevel, UpgradeKind.Value );\r\n\r\n\t\t// Levels have to be in before the tank is clamped, or a full tank gets trimmed\r\n\t\t// to the un-upgraded size on every load.\r\n\t\tWater = Math.Clamp( save.Water, 0, TankCapacity );\r\n\t\tTotalPumps = save.TotalPumps;\r\n\t\tTotalEarned = save.TotalEarned;\r\n\t\tTotalLitresSold = save.TotalLitresSold;\r\n\r\n\t\tPayIdleTime( save.SavedAt );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Read one save file, or null if it isn\u0027t there and null if it\u0027s rubbish.\r\n\t/// \u003C/summary\u003E\r\n\t/// \u003Cremarks\u003E\r\n\t/// ReadJsonOrDefault already returns the default on a missing or malformed file, so\r\n\t/// the catch is only for what it doesn\u0027t cover - a locked or unreadable file. Either\r\n\t/// way a broken save must never take the game down with it: that turns \u0022you lost\r\n\t/// your progress\u0022 into \u0022the game no longer starts\u0022.\r\n\t/// \u003C/remarks\u003E\r\n\tPumpSave ReadSave( string path )\r\n\t{\r\n\t\ttry\r\n\t\t{\r\n\t\t\treturn FileSystem.Data.ReadJsonOrDefault\u003CPumpSave\u003E( path, null );\r\n\t\t}\r\n\t\tcatch ( Exception e )\r\n\t\t{\r\n\t\t\tLog.Warning( $\u0022Couldn\u0027t read {path}: {e.Message}\u0022 );\r\n\t\t\treturn null;\r\n\t\t}\r\n\t}\r\n\r\n\tstatic int ClampLevel( int level, UpgradeKind kind ) =\u003E\r\n\t\tMath.Clamp( level, 0, UpgradeTrack.Get( kind ).MaxLevel );\r\n\r\n\t/// \u003Csummary\u003ERuns the passive pump for the time we were away, capped so it can\u0027t be farmed.\u003C/summary\u003E\r\n\tvoid PayIdleTime( long savedAt )\r\n\t{\r\n\t\tif ( savedAt \u003C= 0 || TotalWaterPerSecond \u003C= 0 )\r\n\t\t\treturn;\r\n\r\n\t\tvar away = DateTimeOffset.UtcNow.ToUnixTimeSeconds() - savedAt;\r\n\t\tif ( away \u003C= 0 )\r\n\t\t\treturn;\r\n\r\n\t\tvar seconds = Math.Min( away, (long)(MaxIdleHours * 3600f) );\r\n\t\tIdleCatchUp = AddWater( TotalWaterPerSecond * seconds );\r\n\t}\r\n\r\n\tvoid Save()\r\n\t{\r\n\t\ttimeSinceSave = 0;\r\n\r\n\t\tif ( !SaveEnabled )\r\n\t\t\treturn;\r\n\r\n\t\t// Never overwrite a real run with state we never loaded. See the field comment.\r\n\t\tif ( !loaded )\r\n\t\t\treturn;\r\n\r\n\t\tFileSystem.Data.WriteJson( SaveFile, new PumpSave\r\n\t\t{\r\n\t\t\tCoins = Coins,\r\n\t\t\tWater = Water,\r\n\t\t\tPumpLevel = PumpLevel,\r\n\t\t\tOwnedBackgrounds = ownedBackgrounds.ToList(),\r\n\t\t\tSelectedBackground = SelectedBackground,\r\n\t\t\tLastDailyDay = lastDailyDay,\r\n\t\t\tWorkerLevels = workerLevels.ToList(),\r\n\t\t\tTreeHeight = TreeHeight,\r\n\t\t\tTreeWater = TreeWater,\r\n\t\t\tTreeGrowsAt = treeGrowsAt,\r\n\t\t\tDailyStreak = DailyStreak,\r\n\t\t\tStorageLevel = LevelOf( UpgradeKind.Storage ),\r\n\t\t\tPowerLevel = LevelOf( UpgradeKind.Power ),\r\n\t\t\tValueLevel = LevelOf( UpgradeKind.Value ),\r\n\t\t\tMuted = Muted,\r\n\t\t\tTotalPumps = TotalPumps,\r\n\t\t\tTotalEarned = TotalEarned,\r\n\t\t\tTotalLitresSold = TotalLitresSold,\r\n\t\t\tSavedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds()\r\n\t\t} );\r\n\t}\r\n}\r\n"},{"Ident":"fpkreastudios.desertpump","Path":"Game/PumpTier.cs","FileName":"PumpTier.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":"namespace DesertPump;\n\n/// \u003Csummary\u003E\n/// One of the twelve tiers. A tier is nine pumps that share a setting - you finish\n/// the desert before you ever see a glacier.\n/// \u003C/summary\u003E\npublic sealed class PumpTier\n{\n\t/// \u003Csummary\u003E1 to 12, the way the player sees it.\u003C/summary\u003E\n\tpublic int Number { get; init; }\n\n\tpublic string Name { get; init; }\n\tpublic string Tagline { get; init; }\n\n\t/// \u003Csummary\u003EDrives the placeholder pump art for every pump in this tier.\u003C/summary\u003E\n\tpublic Color Tint { get; init; }\n\n\t/// \u003Csummary\u003EThe nine pump names, cheapest first.\u003C/summary\u003E\n\tpublic string[] PumpNames { get; init; }\n\n\tpublic int FirstPumpIndex =\u003E (Number - 1) * PumpsPerTier;\n\tpublic int LastPumpIndex =\u003E FirstPumpIndex \u002B PumpsPerTier - 1;\n\n\tpublic const int PumpsPerTier = 9;\n\n\t/// \u003Csummary\u003ERe-read on hotload so edits apply without restarting - see PumpModel.All.\u003C/summary\u003E\n\t[SkipHotload]\n\tpublic static readonly PumpTier[] All = new PumpTier[]\n\t{\n\t\tnew()\n\t\t{\n\t\t\tNumber = 1,\n\t\t\tName = \u0022Desert Wells\u0022,\n\t\t\tTagline = \u0022Sand, rust, and whatever you can pull up by hand.\u0022,\n\t\t\tTint = new Color( 0.72f, 0.50f, 0.24f ),\n\t\t\t// These nine are all ways of getting water out of a hole in the ground.\n\t\t\t// The old set were leftovers from when there were nine tiers rather than\n\t\t\t// twelve, so tier 1 - the one whose whole premise is hand-pulled water -\n\t\t\t// contained a Steam Pressure Pump, a Diesel Derrick and a Solar Filtration\n\t\t\t// Rig, each of which is the name of a tier the player has not reached yet.\n\t\t\tPumpNames = new[]\n\t\t\t{\n\t\t\t\t\u0022Rusty Hand Pump\u0022, \u0022Iron Well Pump\u0022, \u0022Bucket Windlass\u0022,\n\t\t\t\t\u0022Windmill Well\u0022, \u0022Cattle Trough Bore\u0022, \u0022Stone Well House\u0022,\n\t\t\t\t\u0022Desert Water Tower\u0022, \u0022Twin Bore Rig\u0022, \u0022The Great Standpipe\u0022\n\t\t\t}\n\t\t},\n\t\tnew()\n\t\t{\n\t\t\tNumber = 2,\n\t\t\tName = \u0022The Steamworks\u0022,\n\t\t\tTagline = \u0022Brass, pressure gauges, and a great deal of hissing.\u0022,\n\t\t\tTint = new Color( 0.66f, 0.42f, 0.20f ),\n\t\t\tPumpNames = new[]\n\t\t\t{\n\t\t\t\t\u0022Brass Piston Well\u0022, \u0022Boiler Draw\u0022, \u0022Twin Cylinder Rig\u0022,\n\t\t\t\t\u0022Pressure Cathedral\u0022, \u0022Governor Engine\u0022, \u0022Flywheel Colossus\u0022,\n\t\t\t\t\u0022Condenser Array\u0022, \u0022Turbine Hall\u0022, \u0022The Great Manifold\u0022\n\t\t\t}\n\t\t},\n\t\tnew()\n\t\t{\n\t\t\tNumber = 3,\n\t\t\tName = \u0022Oil Country\u0022,\n\t\t\tTagline = \u0022If it worked for crude, it\u0027ll work for water.\u0022,\n\t\t\tTint = new Color( 0.34f, 0.34f, 0.30f ),\n\t\t\tPumpNames = new[]\n\t\t\t{\n\t\t\t\t\u0022Nodding Donkey\u0022, \u0022Wildcat Rig\u0022, \u0022Slant Drill\u0022,\n\t\t\t\t\u0022Offshore Jacket\u0022, \u0022Deep Casing Pump\u0022, \u0022Fracture Head\u0022,\n\t\t\t\t\u0022Pipeline Junction\u0022, \u0022Refinery Tap\u0022, \u0022The Black Column\u0022\n\t\t\t}\n\t\t},\n\t\tnew()\n\t\t{\n\t\t\tNumber = 4,\n\t\t\tName = \u0022Solar Frontier\u0022,\n\t\t\tTagline = \u0022Free light, endless panels, no moving parts.\u0022,\n\t\t\tTint = new Color( 0.28f, 0.46f, 0.78f ),\n\t\t\tPumpNames = new[]\n\t\t\t{\n\t\t\t\t\u0022Panel Trickle\u0022, \u0022Mirror Trough\u0022, \u0022Heliostat Field\u0022,\n\t\t\t\t\u0022Molten Salt Loop\u0022, \u0022Photovoltaic Mesa\u0022, \u0022Tower of Light\u0022,\n\t\t\t\t\u0022Orbital Reflector\u0022, \u0022Fusion Preheater\u0022, \u0022The Sun Engine\u0022\n\t\t\t}\n\t\t},\n\t\tnew()\n\t\t{\n\t\t\tNumber = 5,\n\t\t\tName = \u0022The Lost Oasis\u0022,\n\t\t\tTagline = \u0022Someone was pumping here long before you.\u0022,\n\t\t\tTint = new Color( 0.86f, 0.70f, 0.26f ),\n\t\t\tPumpNames = new[]\n\t\t\t{\n\t\t\t\t\u0022Sunken Step Well\u0022, \u0022Palm Court Spring\u0022, \u0022Mosaic Cistern\u0022,\n\t\t\t\t\u0022Colonnade Fountain\u0022, \u0022Serpent Aqueduct\u0022, \u0022Sun Disc Basin\u0022,\n\t\t\t\t\u0022Hall of Rains\u0022, \u0022Temple of the Flood\u0022, \u0022The First Spring\u0022\n\t\t\t}\n\t\t},\n\t\tnew()\n\t\t{\n\t\t\tNumber = 6,\n\t\t\tName = \u0022Deep Aquifer\u0022,\n\t\t\tTagline = \u0022Down where the rock has been holding its breath.\u0022,\n\t\t\tTint = new Color( 0.30f, 0.52f, 0.55f ),\n\t\t\tPumpNames = new[]\n\t\t\t{\n\t\t\t\t\u0022Bore Shaft\u0022, \u0022Karst Siphon\u0022, \u0022Cavern Lake Pump\u0022,\n\t\t\t\t\u0022Sinkhole Intake\u0022, \u0022Mantle Straw\u0022, \u0022Pressure Fault\u0022,\n\t\t\t\t\u0022Abyssal Column\u0022, \u0022Tectonic Draw\u0022, \u0022The Sleeping Sea\u0022\n\t\t\t}\n\t\t},\n\t\tnew()\n\t\t{\n\t\t\tNumber = 7,\n\t\t\tName = \u0022Storm Harvest\u0022,\n\t\t\tTagline = \u0022Stop digging. Start taking it out of the sky.\u0022,\n\t\t\tTint = new Color( 0.42f, 0.46f, 0.62f ),\n\t\t\tPumpNames = new[]\n\t\t\t{\n\t\t\t\t\u0022Rain Catcher\u0022, \u0022Cloud Seeder\u0022, \u0022Squall Funnel\u0022,\n\t\t\t\t\u0022Lightning Condenser\u0022, \u0022Monsoon Gate\u0022, \u0022Cyclone Tap\u0022,\n\t\t\t\t\u0022Hurricane Yoke\u0022, \u0022Jet Stream Weir\u0022, \u0022The Storm Crown\u0022\n\t\t\t}\n\t\t},\n\t\tnew()\n\t\t{\n\t\t\tNumber = 8,\n\t\t\tName = \u0022Glacier Melt\u0022,\n\t\t\tTagline = \u0022Ten thousand years of snow, cashed in.\u0022,\n\t\t\tTint = new Color( 0.58f, 0.78f, 0.86f ),\n\t\t\tPumpNames = new[]\n\t\t\t{\n\t\t\t\t\u0022Meltwater Channel\u0022, \u0022Crevasse Pump\u0022, \u0022Ice Core Drill\u0022,\n\t\t\t\t\u0022Moraine Sluice\u0022, \u0022Calving Front Rig\u0022, \u0022Ice Shelf Siphon\u0022,\n\t\t\t\t\u0022Polar Reservoir\u0022, \u0022Continental Thaw\u0022, \u0022The Long Winter Tap\u0022\n\t\t\t}\n\t\t},\n\t\tnew()\n\t\t{\n\t\t\tNumber = 9,\n\t\t\tName = \u0022Ocean Reclaim\u0022,\n\t\t\tTagline = \u0022Salt is just an engineering problem.\u0022,\n\t\t\tTint = new Color( 0.20f, 0.52f, 0.62f ),\n\t\t\tPumpNames = new[]\n\t\t\t{\n\t\t\t\t\u0022Beach Well\u0022, \u0022Reverse Osmosis Bank\u0022, \u0022Desalination Pier\u0022,\n\t\t\t\t\u0022Tidal Intake\u0022, \u0022Trench Pump\u0022, \u0022Current Harness\u0022,\n\t\t\t\t\u0022Abyssal Plain Array\u0022, \u0022Continental Shelf Works\u0022, \u0022The Ocean Engine\u0022\n\t\t\t}\n\t\t},\n\t\tnew()\n\t\t{\n\t\t\tNumber = 10,\n\t\t\tName = \u0022Sky Condensers\u0022,\n\t\t\tTagline = \u0022The whole atmosphere, wrung out like a cloth.\u0022,\n\t\t\tTint = new Color( 0.66f, 0.72f, 0.86f ),\n\t\t\tPumpNames = new[]\n\t\t\t{\n\t\t\t\t\u0022Fog Net\u0022, \u0022Dew Spire\u0022, \u0022Stratosphere Coil\u0022,\n\t\t\t\t\u0022Cloud Anchor\u0022, \u0022Mesosphere Rig\u0022, \u0022Aurora Collector\u0022,\n\t\t\t\t\u0022Orbital Ring Tap\u0022, \u0022Atmospheric Siphon\u0022, \u0022The Great Condenser\u0022\n\t\t\t}\n\t\t},\n\t\tnew()\n\t\t{\n\t\t\tNumber = 11,\n\t\t\tName = \u0022Void Springs\u0022,\n\t\t\tTagline = \u0022The water is coming from somewhere. Don\u0027t ask where.\u0022,\n\t\t\tTint = new Color( 0.52f, 0.36f, 0.78f ),\n\t\t\tPumpNames = new[]\n\t\t\t{\n\t\t\t\t\u0022Rift Trickle\u0022, \u0022Phase Well\u0022, \u0022Folded Space Pump\u0022,\n\t\t\t\t\u0022Null Aquifer\u0022, \u0022Entropy Siphon\u0022, \u0022Singularity Draw\u0022,\n\t\t\t\t\u0022Event Horizon Tap\u0022, \u0022Vacuum Spring\u0022, \u0022The Hollow Between\u0022\n\t\t\t}\n\t\t},\n\t\tnew()\n\t\t{\n\t\t\tNumber = 12,\n\t\t\tName = \u0022Cosmic Wellspring\u0022,\n\t\t\tTagline = \u0022Comets are mostly ice, and there are rather a lot of them.\u0022,\n\t\t\tTint = new Color( 0.30f, 0.80f, 0.92f ),\n\t\t\tPumpNames = new[]\n\t\t\t{\n\t\t\t\t\u0022Comet Harpoon\u0022, \u0022Ring System Skimmer\u0022, \u0022Ice Moon Bore\u0022,\n\t\t\t\t\u0022Nebula Trawler\u0022, \u0022Rogue World Tap\u0022, \u0022Stellar Nursery Pump\u0022,\n\t\t\t\t\u0022Galactic Aqueduct\u0022, \u0022Dark Flow Intake\u0022, \u0022The Wellspring\u0022\n\t\t\t}\n\t\t},\n\t};\n\n\tpublic static int Count =\u003E All.Length;\n\n\t/// \u003Csummary\u003ETotal pumps across every tier.\u003C/summary\u003E\n\tpublic static int TotalPumps =\u003E Count * PumpsPerTier;\n\n\t/// \u003Csummary\u003ETier for a 0-based pump index.\u003C/summary\u003E\n\tpublic static PumpTier ForPump( int pumpIndex ) =\u003E\n\t\tAll[Math.Clamp( pumpIndex / PumpsPerTier, 0, Count - 1 )];\n\n\t/// \u003Csummary\u003ETier by its 1-12 number.\u003C/summary\u003E\n\tpublic static PumpTier Get( int number ) =\u003E All[Math.Clamp( number - 1, 0, Count - 1 )];\n}\n"},{"Ident":"fpkreastudios.desertpump","Path":"styles/base/_navigator.scss","FileName":"_navigator.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":".navigator-body\r\n{\r\n\t\u0026.hidden\r\n\t{\r\n\t\tdisplay: none;\r\n\t}\r\n}\r\n"},{"Ident":"fpkreastudios.desertpump","Path":"ui/components/packageflairbar.razor.scss","FileName":"packageflairbar.razor.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":"PackageFlairBar\r\n{\r\n\tposition: absolute;\r\n\ttop: 6px;\r\n\tleft: 6px;\r\n\tz-index: 2;\r\n\tflex-direction: row;\r\n\tgap: 5px;\r\n\tpointer-events: none;\r\n\r\n\t.flair\r\n\t{\r\n\t\tposition: relative;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t\twidth: 22px;\r\n\t\theight: 22px;\r\n\t\tpointer-events: all;\r\n\t\tborder-radius: 4px;\r\n\t\tcolor: white;\r\n\t\tborder: 1px solid rgba( white, 0.1 );\r\n\t\toutline: 1px solid #0006;\r\n\t\topacity: 0.7;\r\n\r\n\t\t\u0026:hover\r\n\t\t{\r\n\t\t\topacity: 1;\r\n\t\t}\r\n\r\n\t\ticon\r\n\t\t{\r\n\t\t\tfont-size: 15px;\r\n\t\t}\r\n\t}\r\n}\r\n"},{"Ident":"fpkreastudios.desertpump","Path":"Game/PumpSettings.cs","FileName":"PumpSettings.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":"namespace DesertPump;\n\n/// \u003Csummary\u003E\n/// Player preferences. Kept in their own file so wiping a run never costs you your\n/// volume levels, and so they survive a reset.\n/// \u003C/summary\u003E\npublic sealed class PumpSettings\n{\n\tpublic const string FileName = \u0022settings.json\u0022;\n\n\tpublic float MasterVolume { get; set; } = 0.8f;\n\tpublic float MusicVolume { get; set; } = 0.55f;\n\tpublic float SfxVolume { get; set; } = 1f;\n\n\t/// \u003Csummary\u003ESilences everything, music included. The speaker button toggles this.\u003C/summary\u003E\n\tpublic bool Muted { get; set; }\n\n\t/// \u003Csummary\u003EThe little \u0022\u002B12L\u0022 numbers that fly off the pump.\u003C/summary\u003E\n\tpublic bool ShowFloatingNumbers { get; set; } = true;\n\n\t/// \u003Csummary\u003ESell the tank automatically the moment it fills. Off by default.\u003C/summary\u003E\n\tpublic bool AutoSell { get; set; }\n\n\t/// \u003Csummary\u003EFinal multiplier for one-shot effects.\u003C/summary\u003E\n\t[System.Text.Json.Serialization.JsonIgnore]\n\tpublic float EffectiveSfx =\u003E Muted ? 0f : MasterVolume * SfxVolume;\n\n\t/// \u003Csummary\u003EFinal multiplier for the background track.\u003C/summary\u003E\n\t[System.Text.Json.Serialization.JsonIgnore]\n\tpublic float EffectiveMusic =\u003E Muted ? 0f : MasterVolume * MusicVolume;\n\n\tstatic PumpSettings loaded;\n\n\t/// \u003Csummary\u003E\n\t/// The live settings. Lazily read so a code hotload - which clears statics - just\n\t/// picks them back up off disk instead of reverting to defaults.\n\t/// \u003C/summary\u003E\n\tpublic static PumpSettings Current\n\t{\n\t\tget\n\t\t{\n\t\t\tloaded ??= FileSystem.Data.ReadJsonOrDefault\u003CPumpSettings\u003E( FileName, null ) ?? new PumpSettings();\n\t\t\treturn loaded;\n\t\t}\n\t}\n\n\tpublic void Save()\n\t{\n\t\tMasterVolume = Math.Clamp( MasterVolume, 0f, 1f );\n\t\tMusicVolume = Math.Clamp( MusicVolume, 0f, 1f );\n\t\tSfxVolume = Math.Clamp( SfxVolume, 0f, 1f );\n\n\t\tFileSystem.Data.WriteJson( FileName, this );\n\t}\n}\n"},{"Ident":"fpkreastudios.desertpump","Path":"styles/base.scss","FileName":"base.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":"@import \u0022base/_splitcontainer.scss\u0022;\r\n@import \u0022base/_navigator.scss\u0022;\r\n\r\nbutton\r\n{\r\n\tcursor: pointer;\r\n}\r\n\r\nIconPanel\r\n{\r\n\tfont-family: Material Icons;\r\n}\r\n\r\n.is-half\r\n{\r\n\twidth: 50%;\r\n}\r\n\r\n.is-third\r\n{\r\n\twidth: 33%;\r\n}\r\n\r\n.is-quarter\r\n{\r\n\twidth: 25%;\r\n}\r\n\r\nbutton.has-subtitle\r\n{\r\n\tposition: relative;\r\n\tflex-direction: column;\r\n\tjustify-content: flex-start;\r\n\talign-items: flex-start;\r\n\tpadding-left: 40px; // icon space\r\n\r\n\t.iconpanel\r\n\t{\r\n\t\tposition: absolute;\r\n\t\tleft: 5px;\r\n\t\ttop: 0;\r\n\t\tbottom: 0;\r\n\t\talign-items: center;\r\n\t}\r\n\r\n\t.button-label\r\n\t{\r\n\t\tfont-weight: bold;\r\n\t}\r\n\r\n\t.button-subtitle\r\n\t{\r\n\t\tfont-size: 12px;\r\n\t\topacity: 0.5;\r\n\t\tmix-blend-mode: lighten;\r\n\t}\r\n}"},{"Ident":"fpkreastudios.desertpump","Path":"ui/controls/color/coloralphacontrol.cs.scss","FileName":"coloralphacontrol.cs.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"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\u0026:hover\r\n\t{\r\n\t\tborder: 1px solid #08f;\r\n\t}\r\n\r\n\t\u0026: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"},{"Ident":"fpkreastudios.desertpump","Path":"ui/controls/color/colorpickercontrol.cs.scss","FileName":"colorpickercontrol.cs.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"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":"fpkreastudios.desertpump","Path":"ui/components/packagelist.razor.scss","FileName":"packagelist.razor.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":".package-list\r\n{\r\n    flex-shrink: 1;\r\n    flex-wrap: wrap;\r\n    flex-grow: 1;\r\n\r\n    h1\r\n    {\r\n        width: 100%;\r\n        margin-top: 50px;\r\n        font-size: 40px;\r\n    }\r\n\r\n    PackageCard\r\n    {\r\n        \u0026:hover\r\n        {\r\n            sound-in: \u0022ui.button.over\u0022;\r\n        }\r\n    }\r\n\r\n    VirtualGrid\r\n    {\r\n        width: 100%;\r\n        height: 100%;\r\n\r\n        .cell\r\n        {\r\n            \r\n        }\r\n    }\r\n}"},{"Ident":"fpkreastudios.desertpump","Path":"ui/treepanel.razor.scss","FileName":"treepanel.razor.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":"$gold: #f5c542;\r\n$panel: #1b2130;\r\n\r\n.treepanel\r\n{\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tpointer-events: all;\r\n\r\n\t// Same reason as the shop - without this the HUD paints through the sheet.\r\n\tz-index: 100;\r\n\tfont-family: Poppins;\r\n\tcolor: white;\r\n\r\n\t.scrim\r\n\t{\r\n\t\tposition: absolute;\r\n\t\ttop: 0;\r\n\t\tleft: 0;\r\n\t\twidth: 100%;\r\n\t\theight: 100%;\r\n\t\tbackground-color: rgba( 8, 10, 16, 0.65 );\r\n\t\tpointer-events: all;\r\n\t}\r\n\r\n\t.sheet\r\n\t{\r\n\t\twidth: 560px;\r\n\t\tmax-width: 92%;\r\n\t\tmax-height: 90%;\r\n\t\tflex-direction: column;\r\n\t\tbackground-color: $panel;\r\n\t\tborder-radius: 24px;\r\n\t\tpadding: 22px;\r\n\t\tborder-bottom: 6px solid rgba( 0, 0, 0, 0.4 );\r\n\t\tpointer-events: all;\r\n\t}\r\n\r\n\t.head\r\n\t{\r\n\t\talign-items: center;\r\n\t\tmargin-bottom: 14px;\r\n\r\n\t\t.title\r\n\t\t{\r\n\t\t\tflex-grow: 1;\r\n\t\t\tfont-size: 24px;\r\n\t\t\tfont-weight: 800;\r\n\t\t\tletter-spacing: 2px;\r\n\t\t\talign-items: center;\r\n\r\n\t\t\ti\r\n\t\t\t{\r\n\t\t\t\tfont-family: Material Icons;\r\n\t\t\t\tfont-size: 28px;\r\n\t\t\t\tmargin-right: 10px;\r\n\t\t\t\tcolor: $gold;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t.closebtn\r\n\t\t{\r\n\t\t\twidth: 44px;\r\n\t\t\theight: 44px;\r\n\t\t\tborder-radius: 12px;\r\n\t\t\tbackground-color: rgba( 255, 255, 255, 0.08 );\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\tcursor: pointer;\r\n\t\t\tpointer-events: all;\r\n\r\n\t\t\ti { font-family: Material Icons; font-size: 24px; }\r\n\r\n\t\t\t\u0026:hover { background-color: rgba( 255, 255, 255, 0.18 ); }\r\n\t\t}\r\n\t}\r\n\r\n\t.note\r\n\t{\r\n\t\tfont-size: 13px;\r\n\t\topacity: 0.7;\r\n\t\tbackground-color: rgba( 255, 255, 255, 0.05 );\r\n\t\tborder-radius: 12px;\r\n\t\tpadding: 10px 14px;\r\n\t\tmargin-bottom: 12px;\r\n\t}\r\n\r\n\t.rows\r\n\t{\r\n\t\tflex-direction: column;\r\n\t\toverflow-y: scroll;\r\n\t}\r\n\r\n\t.lbrow\r\n\t{\r\n\t\talign-items: center;\r\n\t\tbackground-color: rgba( 255, 255, 255, 0.05 );\r\n\t\tborder-radius: 14px;\r\n\t\tpadding: 12px 16px;\r\n\t\tmargin-bottom: 8px;\r\n\t\tborder: 2px solid rgba( 0, 0, 0, 0 );\r\n\r\n\t\t.rank\r\n\t\t{\r\n\t\t\twidth: 44px;\r\n\t\t\tfont-size: 20px;\r\n\t\t\tfont-weight: 800;\r\n\t\t\topacity: 0.7;\r\n\t\t}\r\n\r\n\t\t.who\r\n\t\t{\r\n\t\t\tflex-grow: 1;\r\n\t\t\tfont-size: 17px;\r\n\t\t}\r\n\r\n\t\t.score\r\n\t\t{\r\n\t\t\tfont-size: 20px;\r\n\t\t\tfont-weight: 700;\r\n\t\t\talign-items: center;\r\n\r\n\t\t\t.num { font-size: 20px; font-weight: 700; }\r\n\r\n\t\t\t.unit\r\n\t\t\t{\r\n\t\t\t\tfont-size: 12px;\r\n\t\t\t\topacity: 0.6;\r\n\t\t\t\tmargin-left: 6px;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t\u0026.gold .rank { color: $gold; opacity: 1; }\r\n\t\t\u0026.silver .rank { color: #cfd6e4; opacity: 1; }\r\n\t\t\u0026.bronze .rank { color: #d09a5c; opacity: 1; }\r\n\r\n\t\t\u0026.me\r\n\t\t{\r\n\t\t\tborder-color: #7fd06f;\r\n\t\t\tbackground-color: rgba( 127, 208, 111, 0.12 );\r\n\t\t}\r\n\t}\r\n\r\n\t.refresh\r\n\t{\r\n\t\talign-self: center;\r\n\t\tmargin-top: 8px;\r\n\t\tpadding: 10px 28px;\r\n\t\tborder-radius: 12px;\r\n\t\tbackground-color: rgba( 255, 255, 255, 0.08 );\r\n\t\tfont-size: 14px;\r\n\t\tfont-weight: 700;\r\n\t\tletter-spacing: 1px;\r\n\t\talign-items: center;\r\n\t\tcursor: pointer;\r\n\t\tpointer-events: all;\r\n\r\n\t\ti\r\n\t\t{\r\n\t\t\tfont-family: Material Icons;\r\n\t\t\tfont-size: 18px;\r\n\t\t\tmargin-right: 8px;\r\n\t\t}\r\n\r\n\t\t\u0026:hover { background-color: rgba( 255, 255, 255, 0.16 ); }\r\n\t}\r\n}\r\n"},{"Ident":"fpkreastudios.desertpump","Path":"Game/DesertPumpGame.Workers.cs","FileName":"DesertPumpGame.Workers.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":"namespace DesertPump;\n\n/// \u003Csummary\u003E\n/// The hired help. Workers add litres per second on top of the pump, and unlike the\n/// pump they keep working while you\u0027re doing something else entirely.\n/// \u003C/summary\u003E\npublic sealed partial class DesertPumpGame\n{\n\treadonly int[] workerLevels = new int[WorkerType.Count];\n\n\tpublic int WorkerLevel( WorkerType worker ) =\u003E workerLevels[worker.Index];\n\n\tpublic bool IsHired( WorkerType worker ) =\u003E WorkerLevel( worker ) \u003E 0;\n\n\tpublic bool IsWorkerMaxed( WorkerType worker ) =\u003E WorkerLevel( worker ) \u003E= WorkerType.MaxLevel;\n\n\t/// \u003Csummary\u003ECost of the next level, or infinity when they\u0027ve nothing left to learn.\u003C/summary\u003E\n\tpublic double WorkerCost( WorkerType worker ) =\u003E IsWorkerMaxed( worker )\n\t\t? double.PositiveInfinity\n\t\t: worker.CostAt( WorkerLevel( worker ) );\n\n\tpublic bool CanHire( WorkerType worker ) =\u003E !IsWorkerMaxed( worker ) \u0026\u0026 Coins \u003E= WorkerCost( worker );\n\n\t/// \u003Csummary\u003EWhat one worker is currently producing.\u003C/summary\u003E\n\tpublic double WorkerOutput( WorkerType worker ) =\u003E worker.OutputAt( WorkerLevel( worker ) );\n\n\t/// \u003Csummary\u003EEverything the crew makes between them, litres per second.\u003C/summary\u003E\n\tpublic double WorkerWaterPerSecond\n\t{\n\t\tget\n\t\t{\n\t\t\tdouble total = 0;\n\n\t\t\tforeach ( var worker in WorkerType.All )\n\t\t\t{\n\t\t\t\ttotal \u002B= WorkerOutput( worker );\n\t\t\t}\n\n\t\t\treturn total;\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003EHow many of the fifteen are on the payroll.\u003C/summary\u003E\n\tpublic int HiredCount\n\t{\n\t\tget\n\t\t{\n\t\t\tvar count = 0;\n\n\t\t\tforeach ( var worker in WorkerType.All )\n\t\t\t{\n\t\t\t\tif ( IsHired( worker ) ) count\u002B\u002B;\n\t\t\t}\n\n\t\t\treturn count;\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003EThe pump and the crew together - what the tank actually fills at.\u003C/summary\u003E\n\tpublic double TotalWaterPerSecond =\u003E CurrentPump.PerSecond \u002B WorkerWaterPerSecond;\n\n\t/// \u003Csummary\u003EFired after hiring or promoting someone.\u003C/summary\u003E\n\tpublic Action\u003CWorkerType\u003E Hired { get; set; }\n\n\t/// \u003Csummary\u003EHire a worker, or promote one you already have.\u003C/summary\u003E\n\tpublic bool BuyWorker( WorkerType worker )\n\t{\n\t\tif ( IsWorkerMaxed( worker ) )\n\t\t{\n\t\t\tRefuse( $\u0022{worker.Name} can\u0027t be promoted any further\u0022 );\n\t\t\treturn false;\n\t\t}\n\n\t\tvar cost = WorkerCost( worker );\n\t\tif ( Coins \u003C cost )\n\t\t{\n\t\t\tRefuse( $\u0022Need {Numbers.Short( cost - Coins )} more coins\u0022 );\n\t\t\treturn false;\n\t\t}\n\n\t\tCoins -= cost;\n\t\tworkerLevels[worker.Index]\u002B\u002B;\n\n\t\tPlay( UpgradeSound );\n\t\tHired?.Invoke( worker );\n\t\tSave();\n\n\t\treturn true;\n\t}\n}\n"},{"Ident":"fpkreastudios.desertpump","Path":"UI/TreePanel.razor","FileName":"TreePanel.razor","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":"@using System\r\n@using Sandbox\r\n@using Sandbox.UI\r\n@namespace DesertPump\r\n@inherits Panel\r\n\r\n\u003Croot class=\u0022treepanel\u0022\u003E\r\n\r\n\t\u003Cdiv class=\u0022scrim\u0022 onclick=\u0022@Close\u0022\u003E\u003C/div\u003E\r\n\r\n\t\u003Cdiv class=\u0022sheet\u0022\u003E\r\n\r\n\t\t\u003Cdiv class=\u0022head\u0022\u003E\r\n\t\t\t\u003Cdiv class=\u0022title\u0022\u003E\u003Ci\u003Eemoji_events\u003C/i\u003E TALLEST TREES\u003C/div\u003E\r\n\t\t\t\u003Cdiv class=\u0022closebtn\u0022 onclick=\u0022@Close\u0022\u003E\u003Ci\u003Eclose\u003C/i\u003E\u003C/div\u003E\r\n\t\t\u003C/div\u003E\r\n\r\n\t\t@if ( Leaderboard.Fetching )\r\n\t\t{\r\n\t\t\t\u003Cdiv class=\u0022note\u0022\u003ELoading the board...\u003C/div\u003E\r\n\t\t}\r\n\t\telse if ( !string.IsNullOrEmpty( Leaderboard.Status ) )\r\n\t\t{\r\n\t\t\t\u003Cdiv class=\u0022note\u0022\u003E@Leaderboard.Status\u003C/div\u003E\r\n\t\t}\r\n\r\n\t\t\u003Cdiv class=\u0022rows\u0022\u003E\r\n\t\t\t@foreach ( var row in Leaderboard.Entries )\r\n\t\t\t{\r\n\t\t\t\t\u003Cdiv class=\u0022lbrow @( row.IsMe ? \u0022me\u0022 : \u0022\u0022 ) @RankClass( row.Rank )\u0022\u003E\r\n\t\t\t\t\t\u003Cdiv class=\u0022rank\u0022\u003E@row.Rank\u003C/div\u003E\r\n\t\t\t\t\t\u003Cdiv class=\u0022who\u0022\u003E@row.Name\u003C/div\u003E\r\n\t\t\t\t\t\u003Cdiv class=\u0022score\u0022\u003E\u003Cspan class=\u0022num\u0022\u003E@row.Height\u003C/span\u003E\u003Cspan class=\u0022unit\u0022\u003Estages\u003C/span\u003E\u003C/div\u003E\r\n\t\t\t\t\u003C/div\u003E\r\n\t\t\t}\r\n\t\t\u003C/div\u003E\r\n\r\n\t\t\u003Cdiv class=\u0022refresh\u0022 onclick=\u0022@Reload\u0022\u003E\r\n\t\t\t\u003Ci\u003Erefresh\u003C/i\u003E REFRESH\r\n\t\t\u003C/div\u003E\r\n\r\n\t\u003C/div\u003E\r\n\r\n\u003C/root\u003E\r\n\r\n@code\r\n{\r\n\tpublic Action OnClose { get; set; }\r\n\r\n\tstatic DesertPumpGame Game =\u003E DesertPumpGame.Current;\r\n\r\n\tprotected override void OnAfterTreeRender( bool firstTime )\r\n\t{\r\n\t\tbase.OnAfterTreeRender( firstTime );\r\n\r\n\t\tif ( firstTime ) Reload();\r\n\t}\r\n\r\n\tvoid Reload()\r\n\t{\r\n\t\tvar name = Connection.Local?.DisplayName;\r\n\t\tif ( string.IsNullOrWhiteSpace( name ) ) name = \u0022You\u0022;\r\n\r\n\t\t_ = Leaderboard.Refresh( Game?.TreeHeight ?? 0, name );\r\n\t\tStateHasChanged();\r\n\t}\r\n\r\n\tstatic string RankClass( int rank ) =\u003E rank switch\r\n\t{\r\n\t\t1 =\u003E \u0022gold\u0022,\r\n\t\t2 =\u003E \u0022silver\u0022,\r\n\t\t3 =\u003E \u0022bronze\u0022,\r\n\t\t_ =\u003E string.Empty\r\n\t};\r\n\r\n\tvoid Close() =\u003E OnClose?.Invoke();\r\n\r\n\tprotected override int BuildHash() =\u003E HashCode.Combine(\r\n\t\tLeaderboard.Entries.Count, Leaderboard.Fetching, Leaderboard.Status, Game?.TreeHeight ?? 0 );\r\n}\r\n"},{"Ident":"fpkreastudios.desertpump","Path":"UI/GameHud.razor","FileName":"GameHud.razor","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":"@using System\n@using System.Globalization\n@using Sandbox\n@using Sandbox.UI\n@namespace DesertPump\n@inherits PanelComponent\n\n\u003Cdiv class=\u0022hud @SceneClass @( ShakeClass )\u0022\u003E\n\n\t\u003Cdiv class=\u0022sky\u0022\u003E\u003C/div\u003E\n\t\u003Cdiv class=\u0022sun\u0022\u003E\n\t\t\u003Cdiv class=\u0022sun-rays\u0022\u003E\u003C/div\u003E\n\t\u003C/div\u003E\n\t\u003Cdiv class=\u0022stars\u0022\u003E\u003C/div\u003E\n\n\t\u003Cdiv class=\u0022clouds\u0022\u003E\n\t\t\u003Cdiv class=\u0022cloud c1\u0022\u003E\u003C/div\u003E\n\t\t\u003Cdiv class=\u0022cloud c2\u0022\u003E\u003C/div\u003E\n\t\t\u003Cdiv class=\u0022cloud c3\u0022\u003E\u003C/div\u003E\n\t\u003C/div\u003E\n\n\t\u003Cdiv class=\u0022floor\u0022\u003E\u003C/div\u003E\n\t\u003Cdiv class=\u0022dune far\u0022\u003E\u003C/div\u003E\n\t\u003Cdiv class=\u0022dune near\u0022\u003E\u003C/div\u003E\n\t\u003Cdiv class=\u0022shimmer\u0022\u003E\u003C/div\u003E\n\t\u003Cdiv class=\u0022scenery\u0022\u003E\u003C/div\u003E\n\n\t\u003Cdiv class=\u0022motes\u0022\u003E\n\t\t\u003Cdiv class=\u0022mote m1\u0022\u003E\u003C/div\u003E\n\t\t\u003Cdiv class=\u0022mote m2\u0022\u003E\u003C/div\u003E\n\t\t\u003Cdiv class=\u0022mote m3\u0022\u003E\u003C/div\u003E\n\t\t\u003Cdiv class=\u0022mote m4\u0022\u003E\u003C/div\u003E\n\t\t\u003Cdiv class=\u0022mote m5\u0022\u003E\u003C/div\u003E\n\t\t\u003Cdiv class=\u0022mote m6\u0022\u003E\u003C/div\u003E\n\t\t\u003Cdiv class=\u0022mote m7\u0022\u003E\u003C/div\u003E\n\t\t\u003Cdiv class=\u0022mote m8\u0022\u003E\u003C/div\u003E\n\t\u003C/div\u003E\n\n\t@if ( State is null )\n\t{\n\t\t\u003Cdiv class=\u0022missing\u0022\u003E\n\t\t\t\u003Cdiv class=\u0022missing-title\u0022\u003ENo Desert Pump Game in the scene\u003C/div\u003E\n\t\t\t\u003Cdiv class=\u0022missing-body\u0022\u003EAdd a \u0022Desert Pump Game\u0022 component to any GameObject.\u003C/div\u003E\n\t\t\u003C/div\u003E\n\t}\n\telse\n\t{\n\t\t\u003Cdiv class=\u0022topbar\u0022\u003E\n\n\t\t\t\u003Cdiv class=\u0022stat coins @( CoinPopping ? \u0022pop\u0022 : \u0022\u0022 )\u0022\u003E\n\t\t\t\t\u003Cdiv class=\u0022coin\u0022\u003E\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022value\u0022\u003E@Numbers.Short( State.Coins )\u003C/div\u003E\n\t\t\t\t@if ( CoinPopping )\n\t\t\t\t{\n\t\t\t\t\t\u003Cdiv class=\u0022coinflash\u0022\u003E\u003C/div\u003E\n\t\t\t\t}\n\t\t\t\u003C/div\u003E\n\n\t\t\t\u003Cdiv class=\u0022stat tankstat @( State.TankFull ? \u0022full\u0022 : \u0022\u0022 )\u0022\u003E\n\t\t\t\t\u003Cdiv class=\u0022tank\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022tank-fill\u0022 style=\u0022@FillStyle()\u0022\u003E\n\t\t\t\t\t\t\u003Cdiv class=\u0022tank-wave\u0022\u003E\u003C/div\u003E\n\t\t\t\t\t\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022tank-shine\u0022\u003E\u003C/div\u003E\n\t\t\t\t\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022tank-text\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022value\u0022\u003E@Numbers.Short( State.Water )\u003Cspan class=\u0022unit\u0022\u003E / @Numbers.Short( State.TankCapacity )L\u003C/span\u003E\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022sub\u0022\u003E@RateText()\u003C/div\u003E\n\t\t\t\t\u003C/div\u003E\n\t\t\t\u003C/div\u003E\n\n\t\t\t\u003Cdiv class=\u0022spacer\u0022\u003E\u003C/div\u003E\n\n\t\t\t\u003Cdiv class=\u0022iconbutton shopbtn @( AnyUpgradeAffordable ? \u0022highlight\u0022 : \u0022\u0022 )\u0022 onclick=\u0022@OpenShop\u0022\u003E\n\t\t\t\t\u003Ci\u003Estorefront\u003C/i\u003E\n\t\t\t\t@if ( AnyUpgradeAffordable )\n\t\t\t\t{\n\t\t\t\t\t\u003Cdiv class=\u0022dot\u0022\u003E\u003C/div\u003E\n\t\t\t\t}\n\t\t\t\u003C/div\u003E\n\n\t\t\t\u003Cdiv class=\u0022iconbutton @( State.Muted ? \u0022muted\u0022 : \u0022\u0022 )\u0022 onclick=\u0022@ToggleMute\u0022\u003E\n\t\t\t\t\u003Ci\u003E@( State.Muted ? \u0022volume_off\u0022 : \u0022volume_up\u0022 )\u003C/i\u003E\n\t\t\t\u003C/div\u003E\n\n\t\t\t\u003Cdiv class=\u0022iconbutton\u0022 onclick=\u0022@OpenSettings\u0022\u003E\n\t\t\t\t\u003Ci\u003Esettings\u003C/i\u003E\n\t\t\t\u003C/div\u003E\n\n\t\t\u003C/div\u003E\n\n\t\t\u003Cdiv class=\u0022stage\u0022\u003E\n\n\t\t\t\u003Cdiv class=\u0022pumpwrap\u0022\u003E\n\n\t\t\t\t\u003Cdiv class=\u0022pumparea @PumpAreaClass()\u0022 onclick=\u0022@DoPump\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022glow\u0022 style=\u0022@GlowStyle()\u0022\u003E\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022heatring\u0022\u003E\u003C/div\u003E\n\t\t\t\t\t\u003CPumpArt TierIndex=\u0022@State.PumpLevel\u0022 Artwork=\u0022@ArtworkFor( State.PumpLevel )\u0022\u003E\u003C/PumpArt\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022ground\u0022\u003E\u003C/div\u003E\n\t\t\t\t\u003C/div\u003E\n\n\t\t\t\t@if ( ShowClickRing )\n\t\t\t\t{\n\t\t\t\t\t\u003Cdiv class=\u0022clickring\u0022\u003E\u003C/div\u003E\n\t\t\t\t}\n\n\t\t\t\t@if ( ShowSurgeBurst )\n\t\t\t\t{\n\t\t\t\t\t\u003Cdiv class=\u0022surgering\u0022\u003E\u003C/div\u003E\n\t\t\t\t}\n\n\t\t\t\t\u003Cdiv class=\u0022fx\u0022\u003E\n\t\t\t\t\t@foreach ( var p in particles )\n\t\t\t\t\t{\n\t\t\t\t\t\t\u003Cdiv class=\u0022particle @p.Kind\u0022 style=\u0022@ParticleStyle( p )\u0022\u003E\u003C/div\u003E\n\t\t\t\t\t}\n\n\t\t\t\t\t@foreach ( var f in floaters )\n\t\t\t\t\t{\n\t\t\t\t\t\t\u003Cdiv class=\u0022floater @f.Kind\u0022 style=\u0022@FloaterStyle( f )\u0022\u003E@f.Text\u003C/div\u003E\n\t\t\t\t\t}\n\t\t\t\t\u003C/div\u003E\n\n\t\t\t\u003C/div\u003E\n\n\t\t\t\u003Cdiv class=\u0022pressure s@( State.PressureStage ) @( State.Pressure \u003E 0.02f ? \u0022\u0022 : \u0022cold\u0022 )\u0022\u003E\n\t\t\t\t\u003Cdiv class=\u0022pressure-bar\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022pressure-fill\u0022 style=\u0022@PressureFillStyle()\u0022\u003E\u003C/div\u003E\n\t\t\t\t\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022pressure-label\u0022\u003E\n\t\t\t\t\t\u003Cspan class=\u0022pressure-name\u0022\u003EPRESSURE\u003C/span\u003E\n\t\t\t\t\t\u003Cspan class=\u0022pressure-value\u0022\u003E@State.PressureBonusText\u003C/span\u003E\n\t\t\t\t\u003C/div\u003E\n\t\t\t\u003C/div\u003E\n\n\t\t\t\u003Cdiv class=\u0022nameplate\u0022\u003E\n\t\t\t\t\u003Cdiv class=\u0022tierno\u0022\u003E@State.CurrentPump.Position \u0026middot; @State.Tier.Name.ToUpper()\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022name\u0022\u003E@State.CurrentPump.Name\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022tagline\u0022\u003E@State.Tier.Tagline\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022tierbar\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022tierfill\u0022 style=\u0022@TierFillStyle()\u0022\u003E\u003C/div\u003E\n\t\t\t\t\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022perclick\u0022\u003E\u002B@Numbers.Short( State.LitresPerClick )L per pump - @Numbers.Short( State.PricePerLitre ) @Numbers.Plural( State.PricePerLitre, \u0022coin\u0022, \u0022coins\u0022 ) per litre\u003C/div\u003E\n\t\t\t\u003C/div\u003E\n\n\t\t\t@if ( ShowToast )\n\t\t\t{\n\t\t\t\t\u003Cdiv class=\u0022toast\u0022\u003E@toast\u003C/div\u003E\n\t\t\t}\n\n\t\t\t@if ( ShowIdleNote )\n\t\t\t{\n\t\t\t\t\u003Cdiv class=\u0022idlenote\u0022\u003E\u003Ci\u003Eschedule\u003C/i\u003E The pump made @Numbers.Short( idleNotice )L while you were away\u003C/div\u003E\n\t\t\t}\n\n\t\t\t\u003Cdiv class=\u0022treearea\u0022\u003E\n\n\t\t\t\t\u003Cdiv class=\u0022tree @( State.TreeIsGrowing ? \u0022growing\u0022 : \u0022\u0022 )\u0022 onclick=\u0022@DoWaterTree\u0022\n\t\t\t\t\t style=\u0022background-image: url( \u0027/ui/tree/tree_@(State.TreeArtStage).svg\u0027 );\u0022\u003E\n\t\t\t\t\u003C/div\u003E\n\n\t\t\t\t\u003Cdiv class=\u0022treeinfo\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022treeheight\u0022\u003E\u003Ci\u003Epark\u003C/i\u003E @State.TreeHeight @( State.TreeHeight == 1 ? \u0022stage\u0022 : \u0022stages\u0022 )\u003C/div\u003E\n\n\t\t\t\t\t@if ( State.TreeIsGrowing )\n\t\t\t\t\t{\n\t\t\t\t\t\t\u003Cdiv class=\u0022treestatus growing\u0022\u003EGrowing - @State.TreeTimeLeft\u003C/div\u003E\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\t\u003Cdiv class=\u0022treebar\u0022\u003E\n\t\t\t\t\t\t\t\u003Cdiv class=\u0022treefill\u0022 style=\u0022@TreeFillStyle()\u0022\u003E\u003C/div\u003E\n\t\t\t\t\t\t\u003C/div\u003E\n\t\t\t\t\t\t\u003Cdiv class=\u0022treestatus\u0022\u003E@Numbers.Short( State.TreeWater )/@Numbers.Short( State.TreeWaterNeeded )L\u003C/div\u003E\n\t\t\t\t\t}\n\n\t\t\t\t\t\u003Cdiv class=\u0022treebtn @( State.CanWaterTree ? \u0022\u0022 : \u0022disabled\u0022 )\u0022 onclick=\u0022@DoWaterTree\u0022\u003E\n\t\t\t\t\t\t\u003Ci\u003Ewater_drop\u003C/i\u003E WATER\n\t\t\t\t\t\u003C/div\u003E\n\n\t\t\t\t\t\u003Cdiv class=\u0022treebtn board\u0022 onclick=\u0022@OpenTree\u0022\u003E\n\t\t\t\t\t\t\u003Ci\u003Eleaderboard\u003C/i\u003E BOARD\n\t\t\t\t\t\u003C/div\u003E\n\t\t\t\t\u003C/div\u003E\n\n\t\t\t\u003C/div\u003E\n\n\t\t\t@* Input.Pressed is edge triggered, so SPACE is one pump per press - holding it\n\t\t\t   does nothing and telling the player otherwise reads as the game being broken. *@\n\t\t\t\u003Cdiv class=\u0022hint\u0022\u003EClick the pump or tap SPACE - keep a rhythm going to build pressure\u003C/div\u003E\n\n\t\t\t@* Last in the stage on purpose. It spawns at a random spot whose range\n\t\t\t   overlaps the tree, and drawn any earlier the tree paints over it and eats\n\t\t\t   the click - so the gusher silently waters the tree and then expires. It is\n\t\t\t   a rare, timed reward; nothing in here should ever be allowed on top of it. *@\n\t\t\t@if ( State.GusherActive )\n\t\t\t{\n\t\t\t\t\u003Cdiv class=\u0022gusher\u0022 style=\u0022@GusherStyle()\u0022 onclick=\u0022@ClaimGusher\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022gusher-drop\u0022\u003E\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022gusher-ring\u0022\u003E\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022gusher-ring two\u0022\u003E\u003C/div\u003E\n\t\t\t\t\u003C/div\u003E\n\t\t\t}\n\n\t\t\u003C/div\u003E\n\n\t\t@if ( State.CanClaimDaily )\n\t\t{\n\t\t\t\u003Cdiv class=\u0022daily\u0022 onclick=\u0022@ClaimDaily\u0022\u003E\n\t\t\t\t\u003Cdiv class=\u0022daily-icon\u0022\u003E\u003Ci\u003Eredeem\u003C/i\u003E\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022daily-text\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022daily-title\u0022\u003EDAILY BONUS READY\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022daily-sub\u0022\u003E@Numbers.Short( State.DailyReward ) coins \u0026middot; day @State.ProjectedDailyStreak streak\u003C/div\u003E\n\t\t\t\t\u003C/div\u003E\n\t\t\t\u003C/div\u003E\n\t\t}\n\n\t\t\u003Cdiv class=\u0022actions\u0022\u003E\n\n\t\t\t\u003Cdiv class=\u0022bigbutton sell @( State.CanSell ? \u0022\u0022 : \u0022disabled\u0022 )\u0022 onclick=\u0022@DoSell\u0022\u003E\n\t\t\t\t\u003Cdiv class=\u0022btn-icon\u0022\u003E\u003Ci\u003Elocal_drink\u003C/i\u003E\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022btn-text\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022btn-title\u0022\u003ESELL WATER\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022btn-sub\u0022\u003E\u002B@Numbers.Short( State.SaleValue ) coins\u003C/div\u003E\n\t\t\t\t\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022shine\u0022\u003E\u003C/div\u003E\n\t\t\t\u003C/div\u003E\n\n\t\t\t@if ( State.IsMaxed )\n\t\t\t{\n\t\t\t\t\u003Cdiv class=\u0022bigbutton buy maxed\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022btn-icon\u0022\u003E\u003Ci\u003Eemoji_events\u003C/i\u003E\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022btn-text\u0022\u003E\n\t\t\t\t\t\t\u003Cdiv class=\u0022btn-title\u0022\u003EALL PUMPS OWNED\u003C/div\u003E\n\t\t\t\t\t\t\u003Cdiv class=\u0022btn-sub\u0022\u003Ethe desert is yours\u003C/div\u003E\n\t\t\t\t\t\u003C/div\u003E\n\t\t\t\t\u003C/div\u003E\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t\u003Cdiv class=\u0022bigbutton buy @( State.CanUpgrade ? \u0022ready\u0022 : \u0022disabled\u0022 )\u0022 onclick=\u0022@DoBuy\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022btn-icon\u0022\u003E\u003Ci\u003Eupgrade\u003C/i\u003E\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022btn-text\u0022\u003E\n\t\t\t\t\t\t\u003Cdiv class=\u0022btn-title\u0022\u003EBUY @State.NextPump.Name.ToUpper()\u003C/div\u003E\n\t\t\t\t\t\t\u003Cdiv class=\u0022btn-sub\u0022\u003E@Numbers.Short( State.NextPump.Cost ) coins\u003C/div\u003E\n\t\t\t\t\t\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022shine\u0022\u003E\u003C/div\u003E\n\t\t\t\t\u003C/div\u003E\n\t\t\t}\n\n\t\t\u003C/div\u003E\n\n\t\t\u003Cdiv class=\u0022collection\u0022\u003E\n\t\t\t@foreach ( var pump in VisiblePumps )\n\t\t\t{\n\t\t\t\t\u003Cdiv class=\u0022slot @SlotClass( pump )\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022slotart\u0022\u003E\n\t\t\t\t\t\t\u003CPumpArt TierIndex=\u0022@pump.Index\u0022\n\t\t\t\t\t\t\t\t Silhouette=\u0022@( pump.Index \u003E State.PumpLevel )\u0022\n\t\t\t\t\t\t\t\t Artwork=\u0022@ArtworkFor( pump.Index )\u0022\u003E\u003C/PumpArt\u003E\n\t\t\t\t\t\t@if ( pump.Index \u003E State.PumpLevel )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\u003Cdiv class=\u0022question\u0022\u003E?\u003C/div\u003E\n\t\t\t\t\t\t}\n\t\t\t\t\t\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022slotlabel\u0022\u003E@SlotLabel( pump )\u003C/div\u003E\n\t\t\t\t\t@if ( pump.Index \u003C= State.PumpLevel )\n\t\t\t\t\t{\n\t\t\t\t\t\t\u003Cdiv class=\u0022tick\u0022\u003E\u003Ci\u003Echeck\u003C/i\u003E\u003C/div\u003E\n\t\t\t\t\t}\n\t\t\t\t\u003C/div\u003E\n\t\t\t}\n\t\t\u003C/div\u003E\n\n\t\t@if ( ShowCelebration )\n\t\t{\n\t\t\t\u003Cdiv class=\u0022celebrate\u0022\u003E\n\t\t\t\t\u003Cdiv class=\u0022celebrate-ring\u0022\u003E\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022celebrate-ring two\u0022\u003E\u003C/div\u003E\n\t\t\t\t\u003Cdiv class=\u0022celebrate-card\u0022\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022celebrate-kicker\u0022\u003ENEW PUMP INSTALLED\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022celebrate-name\u0022\u003E@celebrateName\u003C/div\u003E\n\t\t\t\t\t\u003Cdiv class=\u0022celebrate-sub\u0022\u003E@celebrateSub\u003C/div\u003E\n\t\t\t\t\u003C/div\u003E\n\t\t\t\u003C/div\u003E\n\t\t}\n\n\t\t@if ( ShowSurgeFlash )\n\t\t{\n\t\t\t\u003Cdiv class=\u0022surgeflash\u0022\u003E\u003C/div\u003E\n\t\t}\n\n\t\t@if ( showShop )\n\t\t{\n\t\t\t\u003CShopPanel OnClose=\u0022@CloseOverlays\u0022\u003E\u003C/ShopPanel\u003E\n\t\t}\n\n\t\t@if ( showSettings )\n\t\t{\n\t\t\t\u003CSettingsPanel OnClose=\u0022@CloseOverlays\u0022\u003E\u003C/SettingsPanel\u003E\n\t\t}\n\n\t\t@if ( showTree )\n\t\t{\n\t\t\t\u003CTreePanel OnClose=\u0022@CloseOverlays\u0022\u003E\u003C/TreePanel\u003E\n\t\t}\n\t}\n\n\u003C/div\u003E\n\n@code\n{\n\t/// \u003Csummary\u003EOptional - left empty, the HUD finds the game itself.\u003C/summary\u003E\n\t[Property] public DesertPumpGame Target { get; set; }\n\n\t/// \u003Csummary\u003E\n\t/// The real pump artwork, one texture per tier in the same order as\n\t/// \u003Csee cref=\u0022PumpTier.All\u0022/\u003E. Any slot left empty falls back to placeholder art.\n\t/// \u003C/summary\u003E\n\t[Property] public List\u003CTexture\u003E PumpArtwork { get; set; } = new();\n\n\tDesertPumpGame State =\u003E Target.IsValid() ? Target : DesertPumpGame.Current;\n\n\tconst float FloaterLife = 1.1f;\n\tconst float SurgeFloaterLife = 1.5f;\n\tconst float ToastLife = 2.2f;\n\tconst float IdleNoticeLife = 8f;\n\tconst float PumpAnimLife = 0.16f;\n\tconst float ClickRingLife = 0.42f;\n\tconst float SurgeFlashLife = 0.4f;\n\tconst float SurgeBurstLife = 0.6f;\n\tconst float CoinPopLife = 0.36f;\n\tconst float CelebrationLife = 2.6f;\n\tconst float ShakeLife = 0.32f;\n\n\t/// \u003Csummary\u003EPixels per second squared pulling the burst particles back down.\u003C/summary\u003E\n\tconst float Gravity = 1150f;\n\n\t/// \u003Csummary\u003EMust match the width of .floater in the stylesheet - see FloaterStyle.\u003C/summary\u003E\n\tconst float FloaterWidth = 300f;\n\n\t/// \u003Csummary\u003EAbove this the whole effects layer stops spawning - a fast clicker can outrun it.\u003C/summary\u003E\n\tconst int MaxParticles = 40;\n\n\tclass Floater\n\t{\n\t\tpublic string Text;\n\t\tpublic string Kind;\n\t\tpublic float X;\n\t\tpublic float Drift;\n\t\tpublic float Life;\n\t\tpublic RealTimeSince Age;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// One piece of the burst. Positions are integrated in C# rather than handed to a CSS\n\t/// keyframe because each one needs its own arc - see \u003Csee cref=\u0022ParticleStyle\u0022/\u003E.\n\t/// \u003C/summary\u003E\n\tclass Particle\n\t{\n\t\tpublic string Kind;\n\t\tpublic float X;\n\t\tpublic float Y;\n\t\tpublic float Vx;\n\t\tpublic float Vy;\n\t\tpublic float Size;\n\t\tpublic float Spin;\n\t\tpublic float Life;\n\t\tpublic RealTimeSince Age;\n\t}\n\n\treadonly List\u003CFloater\u003E floaters = new();\n\treadonly List\u003CParticle\u003E particles = new();\n\n\tstring toast;\n\tRealTimeSince toastAge;\n\n\tdouble idleNotice;\n\tRealTimeSince idleAge;\n\n\t// Every one-shot effect below is gated on \u0022has it been less than N seconds since\n\t// this fired\u0022. Left at the default timestamp of zero that reads as \u0022it fired at\n\t// engine time zero\u0022 - fine in the editor, which has been up for hours, but in a\n\t// fresh build RealTime.Now starts near zero too and the first frame would come up\n\t// mid surge-flash. Start them all firmly in the past instead.\n\tconst float LongAgo = 9999f;\n\n\tRealTimeSince timeSincePump = LongAgo;\n\tRealTimeSince clickRingAge = LongAgo;\n\tRealTimeSince surgeAge = LongAgo;\n\tRealTimeSince coinPopAge = LongAgo;\n\tRealTimeSince celebrateAge = LongAgo;\n\tRealTimeSince shakeAge = LongAgo;\n\n\tstring celebrateName;\n\tstring celebrateSub;\n\n\t/// \u003Csummary\u003ESet when a shake is wanted, and which of the two flavours.\u003C/summary\u003E\n\tstring shakeKind;\n\n\t/// \u003Csummary\u003EBumped every frame so the particles get rebuilt while they\u0027re moving.\u003C/summary\u003E\n\tint animTick;\n\n\tbool hooked;\n\n\tbool showShop;\n\tbool showSettings;\n\tbool showTree;\n\n\t/// \u003Csummary\u003EPuts a nudge on the shop button when something in there is affordable.\u003C/summary\u003E\n\tbool AnyUpgradeAffordable =\u003E\n\t\tState is not null \u0026\u0026 UpgradeTrack.All.Any( x =\u003E State.CanBuy( x.Kind ) );\n\n\t/// \u003Csummary\u003E\n\t/// The strip along the bottom shows the tier you\u0027re in, not all 108 pumps - nine\n\t/// slots you can actually finish is a far better carrot than a hundred you can\u0027t.\n\t/// \u003C/summary\u003E\n\tIEnumerable\u003CPumpModel\u003E VisiblePumps\n\t{\n\t\tget\n\t\t{\n\t\t\tif ( State is null ) return Enumerable.Empty\u003CPumpModel\u003E();\n\n\t\t\tvar first = State.Tier.FirstPumpIndex;\n\t\t\treturn PumpModel.All.Skip( first ).Take( PumpTier.PumpsPerTier );\n\t\t}\n\t}\n\n\tstring SceneClass =\u003E $\u0022scene-{State?.SelectedBackground ?? BackgroundStyle.DefaultId}\u0022;\n\n\t// ---- one-shot effect gates ---------------------------------------------\n\n\t/// \u003Csummary\u003E\n\t/// The settings toggle that governs all the per-action juice - numbers, droplets,\n\t/// coins, rings, flashes and the shake. The new-pump card is deliberately left out\n\t/// of it: that one is a milestone rather than noise, and a player who turned the\n\t/// effects down still wants to be told they bought something.\n\t/// \u003C/summary\u003E\n\tstatic bool EffectsOn =\u003E PumpSettings.Current.ShowFloatingNumbers;\n\n\tbool ShowClickRing =\u003E EffectsOn \u0026\u0026 clickRingAge \u003C ClickRingLife;\n\tbool ShowSurgeFlash =\u003E EffectsOn \u0026\u0026 surgeAge \u003C SurgeFlashLife;\n\tbool ShowSurgeBurst =\u003E EffectsOn \u0026\u0026 surgeAge \u003C SurgeBurstLife;\n\tbool CoinPopping =\u003E EffectsOn \u0026\u0026 coinPopAge \u003C CoinPopLife;\n\tbool ShowCelebration =\u003E celebrateName is not null \u0026\u0026 celebrateAge \u003C CelebrationLife;\n\n\t/// \u003Csummary\u003E\n\t/// The shake is a class on the root rather than an inline transform, so the whole\n\t/// keyframe runs on the GPU and the C# side only has to say when it starts.\n\t/// \u003C/summary\u003E\n\tstring ShakeClass =\u003E shakeAge \u003C ShakeLife ? $\u0022shake-{shakeKind}\u0022 : \u0022\u0022;\n\n\tvoid OpenShop()\n\t{\n\t\tshowShop = !showShop;\n\t\tshowSettings = false;\n\t}\n\n\tvoid OpenSettings()\n\t{\n\t\tshowSettings = !showSettings;\n\t\tshowShop = false;\n\t}\n\n\tvoid OpenTree()\n\t{\n\t\tshowTree = !showTree;\n\t\tshowShop = false;\n\t\tshowSettings = false;\n\t}\n\n\t/// \u003Csummary\u003EOpen a panel from the console, so UI can be inspected without clicking.\u003C/summary\u003E\n\tpublic void ShowPanel( string which )\n\t{\n\t\tshowShop = which is \u0022shop\u0022 or \u0022bg\u0022 or \u0022workers\u0022;\n\t\tshowSettings = which == \u0022settings\u0022;\n\t\tshowTree = which == \u0022tree\u0022;\n\n\t\tStateHasChanged();\n\t}\n\n\tvoid CloseOverlays()\n\t{\n\t\tshowShop = false;\n\t\tshowSettings = false;\n\t\tshowTree = false;\n\t}\n\n\tstring TreeFillStyle() =\u003E $\u0022width: {N( State.TreeFraction * 100f )}%;\u0022;\n\n\tvoid DoWaterTree()\n\t{\n\t\tvar poured = State?.WaterTree() ?? 0;\n\t\tif ( poured \u003C= 0 ) return;\n\n\t\tSpawn( $\u0022-{Numbers.Short( poured )}L\u0022, \u0022water\u0022 );\n\n\t\ttoast = State.TreeIsGrowing\n\t\t\t? $\u0022Tree is growing - back in {State.TreeTimeLeft}\u0022\n\t\t\t: $\u0022{Numbers.Short( State.TreeWaterRemaining )}L to go\u0022;\n\t\ttoastAge = 0;\n\t}\n\n\tvoid OnTreeGrew( int height )\n\t{\n\t\ttoast = $\u0022Your tree grew to {height} {( height == 1 ? \u0022stage\u0022 : \u0022stages\u0022 )}\u0022;\n\t\ttoastAge = 0;\n\t}\n\n\tbool ShowToast =\u003E !string.IsNullOrEmpty( toast ) \u0026\u0026 toastAge \u003C ToastLife;\n\tbool ShowIdleNote =\u003E idleNotice \u003E 0 \u0026\u0026 idleAge \u003C IdleNoticeLife;\n\n\t/// \u003Csummary\u003E\n\t/// The panel a PanelComponent builds for itself defaults to pointer-events: none,\n\t/// and no stylesheet rule can reach it - it has no class and a generated type name.\n\t/// Left alone it makes every button underneath it dead to the mouse.\n\t/// \u003C/summary\u003E\n\tprotected override void OnTreeFirstBuilt()\n\t{\n\t\tbase.OnTreeFirstBuilt();\n\n\t\tPanel.Style.PointerEvents = PointerEvents.All;\n\t}\n\n\tprotected override void OnStart()\n\t{\n\t\tHook();\n\n\t\tif ( State is not null \u0026\u0026 State.IdleCatchUp \u003E 0 )\n\t\t{\n\t\t\tidleNotice = State.IdleCatchUp;\n\t\t\tidleAge = 0;\n\t\t}\n\t}\n\n\tprotected override void OnDestroy()\n\t{\n\t\tUnhook();\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\t// The game component can come alive after us, so keep trying until it\u0027s there.\n\t\tif ( !hooked ) Hook();\n\n\t\tfloaters.RemoveAll( x =\u003E x.Age \u003E x.Life );\n\t\tparticles.RemoveAll( x =\u003E x.Age \u003E x.Life );\n\n\t\tif ( floaters.Count \u003E 0 || particles.Count \u003E 0 || timeSincePump \u003C PumpAnimLife )\n\t\t{\n\t\t\tanimTick = (int)(RealTime.Now * 30f);\n\t\t}\n\t}\n\n\tprotected override int BuildHash()\n\t{\n\t\tvar state = State;\n\t\tif ( state is null ) return 0;\n\n\t\t// The counts have to be in here, not just animTick. animTick only advances while\n\t\t// something is animating, so on the frame the last floater is removed it freezes\n\t\t// - and if nothing else in the hash moved, the tree never rebuilds and that\n\t\t// floater stays on screen forever, stuck at whatever opacity it last drew at.\n\t\t// At pump 1 there is no idle income to accidentally cover for it.\n\t\t// ShowSurgeFlash belongs here for the same reason the counts do - it is a panel\n\t\t// that has to come off the screen, and nothing else in the hash is guaranteed to\n\t\t// have moved on the frame it expires. Combine takes eight, so the last two share\n\t\t// a nested one rather than being OR\u0027d together, which would hide a swap.\n\t\tvar effects = HashCode.Combine(\n\t\t\tfloaters.Count, particles.Count, animTick, ShakeClass,\n\t\t\tShowClickRing, ShowSurgeBurst, ShowSurgeFlash,\n\t\t\tHashCode.Combine( CoinPopping, ShowCelebration ) );\n\n\t\tvar overlays = HashCode.Combine(\n\t\t\tshowShop, showSettings, showTree, ShowToast, ShowIdleNote,\n\t\t\tAnyUpgradeAffordable, state.SelectedBackground, state.CanClaimDaily );\n\n\t\tvar world = HashCode.Combine(\n\t\t\tstate.GusherActive, state.TreeHeight, Numbers.Bucket( state.TreeWater ),\n\t\t\t(int)state.TreeSecondsLeft );\n\n\t\t// Numbers go through Numbers.Bucket rather than a cast. Coins reach 2.5e51 by\n\t\t// the last pump, and a double that big cast to long lands on the same value\n\t\t// every time - so the hash would stop moving and the wallet would freeze on\n\t\t// screen from tier 9 onward. See the remarks on Bucket.\n\t\treturn HashCode.Combine(\n\t\t\tNumbers.Bucket( state.Coins ),\n\t\t\tNumbers.Bucket( state.Water ),\n\t\t\tstate.PumpLevel,\n\t\t\tstate.Muted,\n\t\t\t// Twenty bands rather than the raw float - the gauge still reads as smooth\n\t\t\t// and the tree isn\u0027t rebuilt on every frame that pressure bleeds off.\n\t\t\t(int)(state.Pressure * 20f),\n\t\t\teffects,\n\t\t\toverlays,\n\t\t\tworld );\n\t}\n\n\tvoid Hook()\n\t{\n\t\tvar state = State;\n\t\tif ( state is null || hooked ) return;\n\n\t\tstate.Pumped \u002B= OnPumped;\n\t\tstate.Surged \u002B= OnSurged;\n\t\tstate.Sold \u002B= OnSold;\n\t\tstate.Upgraded \u002B= OnUpgraded;\n\t\tstate.Refused \u002B= OnRefused;\n\t\tstate.Purchased \u002B= OnPurchased;\n\t\tstate.TreeGrew \u002B= OnTreeGrew;\n\n\t\thooked = true;\n\t}\n\n\tvoid Unhook()\n\t{\n\t\tvar state = State;\n\t\tif ( state is null || !hooked ) return;\n\n\t\tstate.Pumped -= OnPumped;\n\t\tstate.Surged -= OnSurged;\n\t\tstate.Sold -= OnSold;\n\t\tstate.Upgraded -= OnUpgraded;\n\t\tstate.Refused -= OnRefused;\n\t\tstate.Purchased -= OnPurchased;\n\t\tstate.TreeGrew -= OnTreeGrew;\n\n\t\thooked = false;\n\t}\n\n\tvoid DoPump() =\u003E State?.Pump();\n\tvoid DoSell() =\u003E State?.SellAll();\n\tvoid DoBuy() =\u003E State?.BuyNextPump();\n\tvoid ToggleMute() =\u003E State?.ToggleMute();\n\n\tvoid OnPumped( double litres )\n\t{\n\t\ttimeSincePump = 0;\n\t\tclickRingAge = 0;\n\n\t\tSpawn( $\u0022\u002B{Numbers.Short( litres )}L\u0022, \u0022water\u0022 );\n\n\t\t// More water on screen the harder you\u0027re going, so the gauge isn\u0027t the only\n\t\t// thing telling you pressure is up.\n\t\tvar count = 4 \u002B (int)(State.Pressure * 5f);\n\t\tSprayDroplets( count );\n\t}\n\n\tvoid OnSurged( double litres )\n\t{\n\t\ttimeSincePump = 0;\n\t\tclickRingAge = 0;\n\t\tsurgeAge = 0;\n\n\t\tShake( \u0022hard\u0022 );\n\t\tSpawn( $\u0022SURGE  \u002B{Numbers.Short( litres )}L\u0022, \u0022surge\u0022 );\n\t\tSprayDroplets( 14 );\n\t\tSpawnSparks( 12 );\n\t}\n\n\tvoid OnSold( double coins )\n\t{\n\t\tSpawn( $\u0022\u002B{Numbers.Short( coins )}\u0022, \u0022coin\u0022 );\n\t\tSpawnCoins( 10 );\n\n\t\tcoinPopAge = 0;\n\t\ttoast = null;\n\t}\n\n\tvoid OnUpgraded( PumpModel pump )\n\t{\n\t\tcelebrateName = pump.Name;\n\t\tcelebrateSub = pump.Rank == 1\n\t\t\t? $\u0022{pump.Tier.Name} - {pump.Tier.Tagline}\u0022\n\t\t\t: $\u0022{Numbers.Short( pump.PerClick )}L per pump  -  {Numbers.Short( pump.Tank )}L tank\u0022;\n\t\tcelebrateAge = 0;\n\n\t\tShake( \u0022soft\u0022 );\n\t\tSpawnSparks( 18 );\n\n\t\ttoast = null;\n\t\tidleNotice = 0;\n\t}\n\n\tvoid OnRefused( string reason )\n\t{\n\t\ttoast = reason;\n\t\ttoastAge = 0;\n\t}\n\n\tvoid OnPurchased( UpgradeTrack track )\n\t{\n\t\ttoast = $\u0022{track.Name} upgraded\u0022;\n\t\ttoastAge = 0;\n\t}\n\n\tvoid Shake( string kind )\n\t{\n\t\tif ( !PumpSettings.Current.ShowFloatingNumbers )\n\t\t\treturn;\n\n\t\tshakeKind = kind;\n\t\tshakeAge = 0;\n\t}\n\n\tvoid Spawn( string text, string kind )\n\t{\n\t\tif ( !PumpSettings.Current.ShowFloatingNumbers )\n\t\t\treturn;\n\n\t\t// Keep the list short - a fast clicker can outrun the fade otherwise.\n\t\tif ( floaters.Count \u003E 12 ) floaters.RemoveAt( 0 );\n\n\t\tfloaters.Add( new Floater\n\t\t{\n\t\t\tText = text,\n\t\t\tKind = kind,\n\t\t\tX = kind == \u0022surge\u0022 ? Rand( -30f, 30f ) : Rand( -70f, 70f ),\n\t\t\tDrift = Rand( -18f, 18f ),\n\t\t\tLife = kind == \u0022surge\u0022 ? SurgeFloaterLife : FloaterLife,\n\t\t\tAge = 0\n\t\t} );\n\t}\n\n\t/// \u003Csummary\u003EWater thrown up out of the pump head.\u003C/summary\u003E\n\tvoid SprayDroplets( int count )\n\t{\n\t\tfor ( var i = 0; i \u003C count; i\u002B\u002B )\n\t\t{\n\t\t\tAdd( new Particle\n\t\t\t{\n\t\t\t\tKind = \u0022drop\u0022,\n\t\t\t\tX = Rand( -26f, 26f ),\n\t\t\t\tY = Rand( -96f, -60f ),\n\t\t\t\tVx = Rand( -280f, 280f ),\n\t\t\t\tVy = Rand( -440f, -230f ),\n\t\t\t\tSize = Rand( 7f, 16f ),\n\t\t\t\tSpin = 0f,\n\t\t\t\tLife = Rand( 0.55f, 0.85f ),\n\t\t\t\tAge = 0\n\t\t\t} );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003ECoins tossed out on a sale.\u003C/summary\u003E\n\tvoid SpawnCoins( int count )\n\t{\n\t\tfor ( var i = 0; i \u003C count; i\u002B\u002B )\n\t\t{\n\t\t\tAdd( new Particle\n\t\t\t{\n\t\t\t\tKind = \u0022coinbit\u0022,\n\t\t\t\tX = Rand( -40f, 40f ),\n\t\t\t\tY = Rand( -30f, 20f ),\n\t\t\t\tVx = Rand( -240f, 240f ),\n\t\t\t\tVy = Rand( -520f, -320f ),\n\t\t\t\tSize = Rand( 14f, 24f ),\n\t\t\t\tSpin = Rand( -420f, 420f ),\n\t\t\t\tLife = Rand( 0.75f, 1.05f ),\n\t\t\t\tAge = 0\n\t\t\t} );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003EThe flat radial burst a surge or a new pump throws off.\u003C/summary\u003E\n\tvoid SpawnSparks( int count )\n\t{\n\t\tfor ( var i = 0; i \u003C count; i\u002B\u002B )\n\t\t{\n\t\t\tvar angle = (i / (float)count) * MathF.PI * 2f \u002B Rand( -0.2f, 0.2f );\n\t\t\tvar speed = Rand( 380f, 620f );\n\n\t\t\tAdd( new Particle\n\t\t\t{\n\t\t\t\tKind = \u0022spark\u0022,\n\t\t\t\tX = 0f,\n\t\t\t\tY = -40f,\n\t\t\t\tVx = MathF.Cos( angle ) * speed,\n\t\t\t\tVy = MathF.Sin( angle ) * speed,\n\t\t\t\tSize = Rand( 8f, 15f ),\n\t\t\t\tSpin = Rand( -240f, 240f ),\n\t\t\t\tLife = Rand( 0.45f, 0.7f ),\n\t\t\t\tAge = 0\n\t\t\t} );\n\t\t}\n\t}\n\n\tvoid Add( Particle particle )\n\t{\n\t\tif ( !PumpSettings.Current.ShowFloatingNumbers )\n\t\t\treturn;\n\n\t\t// Drop the oldest rather than refusing the new one, so a burst always reads as\n\t\t// a burst even when the screen is already busy.\n\t\tif ( particles.Count \u003E= MaxParticles )\n\t\t\tparticles.RemoveAt( 0 );\n\n\t\tparticles.Add( particle );\n\t}\n\n\tTexture ArtworkFor( int index )\n\t{\n\t\tif ( PumpArtwork is null ) return null;\n\t\tif ( index \u003C 0 || index \u003E= PumpArtwork.Count ) return null;\n\n\t\treturn PumpArtwork[index];\n\t}\n\n\tstring RateText()\n\t{\n\t\tvar perSecond = State.TotalWaterPerSecond;\n\n\t\treturn perSecond \u003E 0\n\t\t\t? $\u0022\u002B{Numbers.Rate( perSecond )}L/s while idle\u0022\n\t\t\t: \u0022hand powered\u0022;\n\t}\n\n\tstring PumpAreaClass()\n\t{\n\t\tvar classes = timeSincePump \u003C PumpAnimLife ? \u0022pumping\u0022 : \u0022\u0022;\n\t\tif ( State.TankFull ) classes \u002B= \u0022 full\u0022;\n\t\tif ( State.PressureHot ) classes \u002B= \u0022 hot\u0022;\n\n\t\treturn classes;\n\t}\n\n\t/// \u003Csummary\u003EThe pump\u0027s halo brightens and widens with pressure.\u003C/summary\u003E\n\tstring GlowStyle()\n\t{\n\t\tvar p = State.Pressure;\n\t\tvar size = 78f \u002B p * 14f;\n\t\tvar alpha = 0.14f \u002B p * 0.22f;\n\n\t\treturn $\u0022width: {N( size )}%; height: {N( size )}%; \u0022 \u002B\n\t\t\t$\u0022background-color: rgba( 255, 255, 255, {N( alpha )} );\u0022;\n\t}\n\n\tstring PressureFillStyle() =\u003E $\u0022width: {N( State.Pressure * 100f )}%;\u0022;\n\n\tstring SlotClass( PumpModel tier )\n\t{\n\t\tif ( tier.Index \u003C= State.PumpLevel ) return \u0022owned\u0022;\n\n\t\treturn tier.Index == State.PumpLevel \u002B 1 ? \u0022locked next\u0022 : \u0022locked\u0022;\n\t}\n\n\tstring SlotLabel( PumpModel tier )\n\t{\n\t\treturn tier.Index \u003C= State.PumpLevel\n\t\t\t? tier.Name\n\t\t\t: $\u0022{Numbers.Short( tier.Cost )} {Numbers.Plural( tier.Cost, \u0022coin\u0022, \u0022coins\u0022 )}\u0022;\n\t}\n\n\tstring FillStyle() =\u003E $\u0022height: {N( State.TankFraction * 100f )}%;\u0022;\n\n\tstring TierFillStyle() =\u003E $\u0022width: {N( State.TierProgress * 100f )}%;\u0022;\n\n\tstring GusherStyle() =\u003E\n\t\t$\u0022left: {N( State.GusherX * 100f )}%; top: {N( State.GusherY * 100f )}%;\u0022;\n\n\tvoid ClaimGusher()\n\t{\n\t\tvar won = State?.ClaimGusher() ?? 0;\n\t\tif ( won \u003C= 0 ) return;\n\n\t\tSpawn( $\u0022\u002B{Numbers.Short( won )}\u0022, \u0022coin\u0022 );\n\t\tSpawnCoins( 14 );\n\t\tcoinPopAge = 0;\n\n\t\ttoast = \u0022Gusher! Free coins\u0022;\n\t\ttoastAge = 0;\n\t}\n\n\tvoid ClaimDaily()\n\t{\n\t\tvar won = State?.ClaimDaily() ?? 0;\n\t\tif ( won \u003C= 0 ) return;\n\n\t\tSpawn( $\u0022\u002B{Numbers.Short( won )}\u0022, \u0022coin\u0022 );\n\t\tSpawnCoins( 16 );\n\t\tcoinPopAge = 0;\n\n\t\ttoast = $\u0022Day {State.DailyStreak} streak - come back tomorrow\u0022;\n\t\ttoastAge = 0;\n\t}\n\n\tstring FloaterStyle( Floater f )\n\t{\n\t\tvar t = Math.Clamp( (float)f.Age / f.Life, 0f, 1f );\n\t\tvar rise = 70f \u002B t * 110f;\n\t\tvar fade = 1f - t * t;\n\n\t\t// Surge numbers punch out to full size and settle, so they read as an event\n\t\t// rather than as one more of the numbers already streaming past.\n\t\tvar scale = f.Kind == \u0022surge\u0022\n\t\t\t? 1f \u002B (1f - MathF.Min( t * 5f, 1f )) * 0.5f\n\t\t\t: 1f;\n\n\t\t// Centred on the anchor by hand: the panel is a fixed 300px wide with centred\n\t\t// text, so half its width comes back off the left. Text has no width to measure.\n\t\treturn $\u0022left: {N( f.X \u002B f.Drift * t - FloaterWidth * 0.5f )}px; top: {N( -rise )}px; \u0022 \u002B\n\t\t\t$\u0022opacity: {N( fade )}; transform: scale( {N( scale )} );\u0022;\n\t}\n\n\tstring ParticleStyle( Particle p )\n\t{\n\t\tvar t = (float)p.Age;\n\t\tvar k = Math.Clamp( t / p.Life, 0f, 1f );\n\n\t\t// Sparks fly flat - they\u0027re a burst of light, not something thrown in the air.\n\t\tvar gravity = p.Kind == \u0022spark\u0022 ? 0f : Gravity;\n\n\t\tvar x = p.X \u002B p.Vx * t;\n\t\tvar y = p.Y \u002B p.Vy * t \u002B 0.5f * gravity * t * t;\n\n\t\tvar fade = 1f - k * k;\n\t\tvar scale = p.Kind == \u0022spark\u0022 ? 1f - k * 0.7f : 1f;\n\n\t\treturn $\u0022left: {N( x - p.Size * 0.5f )}px; top: {N( y - p.Size * 0.5f )}px; \u0022 \u002B\n\t\t\t$\u0022width: {N( p.Size )}px; height: {N( p.Size )}px; opacity: {N( fade )}; \u0022 \u002B\n\t\t\t$\u0022transform: rotate( {N( p.Spin * t )}deg ) scale( {N( scale )} );\u0022;\n\t}\n\n\t/// \u003Csummary\u003EStyle strings need dots for decimals, whatever the machine\u0027s locale says.\u003C/summary\u003E\n\tstatic string N( float value ) =\u003E value.ToString( \u00220.##\u0022, CultureInfo.InvariantCulture );\n\n\tstatic float Rand( float min, float max ) =\u003E min \u002B (float)System.Random.Shared.NextDouble() * (max - min);\n}\n"},{"Ident":"fpkreastudios.desertpump","Path":"UI/PumpArt.razor","FileName":"PumpArt.razor","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":"@using System\r\n@using Sandbox\r\n@using Sandbox.UI\r\n@namespace DesertPump\r\n@inherits Panel\r\n\r\n\u003Croot class=\u0022pumpart @( Silhouette ? \u0022silhouette\u0022 : \u0022\u0022 )\u0022\u003E\r\n\r\n\t@if ( Artwork is not null )\r\n\t{\r\n\t\t\u003CImage Texture=\u0022@Artwork\u0022 class=\u0022photo\u0022\u003E\u003C/Image\u003E\r\n\t}\r\n\telse\r\n\t{\r\n\t\t\u003Cdiv class=\u0022drawing\u0022 style=\u0022background-image: url( \u0027@ArtPath\u0027 );\u0022\u003E\u003C/div\u003E\r\n\t}\r\n\r\n\u003C/root\u003E\r\n\r\n@code\r\n{\r\n\t/// \u003Csummary\u003EIndex into \u003Csee cref=\u0022PumpModel.All\u0022/\u003E.\u003C/summary\u003E\r\n\tpublic int TierIndex { get; set; }\r\n\r\n\t/// \u003Csummary\u003EDraw it as a black cut-out, the way locked pumps show in the collection.\u003C/summary\u003E\r\n\tpublic bool Silhouette { get; set; }\r\n\r\n\t/// \u003Csummary\u003EOverrides the drawn artwork entirely, if you\u0027d rather use your own.\u003C/summary\u003E\r\n\tpublic Texture Artwork { get; set; }\r\n\r\n\tPumpModel Pump =\u003E PumpModel.Get( TierIndex );\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// One illustration per pump - all 108 of them, and no two the same machine. The\r\n\t/// tier owns the palette and what the thing stands on; everything else about a pump\r\n\t/// is drawn for the name it carries. See tools/make_pumps.py.\r\n\t/// \u003C/summary\u003E\r\n\tstring ArtPath =\u003E $\u0022/ui/pumps/pump_{Pump.Index \u002B 1:000}.svg\u0022;\r\n\r\n\t// There used to be a 0.92-1.0 scale ramp by rank here, from back when the nine\r\n\t// pumps in a tier were one drawing and the ramp was doing the work of selling a\r\n\t// purchase. Every drawing includes the ground it stands on, so scaling the image\r\n\t// scaled the ground with it and the desert changed size when you bought a pump.\r\n\t// The drawings differ on their own now, so the ramp is gone and the horizon holds.\r\n\r\n\tprotected override int BuildHash() =\u003E HashCode.Combine( TierIndex, Silhouette, Artwork );\r\n}\r\n"},{"Ident":"fpkreastudios.desertpump","Path":"styles/form/_dropdown.scss","FileName":"_dropdown.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"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\u003E .dropdown_indicator\r\n\t{\r\n\t\tposition: absolute;\r\n\t\tright: 8px;\r\n\t}\r\n\r\n\t\u0026.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\u003E option\r\n\t{\r\n\t\tdisplay: none;\r\n\t}\r\n}"},{"Ident":"fpkreastudios.desertpump","Path":"ui/controlsheet/controlsheetgroupheader.cs.scss","FileName":"controlsheetgroupheader.cs.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":"ControlSheetGroupHeader\r\n{\r\n\tfont-size: 1.33rem;\r\n\tcolor: red;\r\n\tgap: 2px;\r\n\talign-items: center;\r\n\r\n\t\u0026.hidden\r\n\t{\r\n\t\tdisplay: none;\r\n\t}\r\n\r\n\t\u003E .title\r\n\t{\r\n\t\tfont-weight: 600;\r\n\t}\r\n\r\n\t\u0026.has-toggle\r\n\t{\r\n\t\tcursor: pointer;\r\n\t\topacity: 0.8;\r\n\r\n\t\t\u0026:before\r\n\t\t{\r\n\t\t\tcontent: \u0027 \u0027;\r\n\t\t\twidth: 22px;\r\n\t\t\theight: 22px;\r\n\t\t\tbackground-color: #000a;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\ttext-align: center;\r\n\t\t\tborder-radius: 5px;\r\n\t\t\tborder: 1px solid #555;\r\n\t\t}\r\n\r\n\t\t\u0026:hover\r\n\t\t{\r\n\t\t\topacity: 1;\r\n\r\n\t\t\t\u0026:before\r\n\t\t\t{\r\n\t\t\t\tborder-color: #888;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t\u0026.checked\r\n\t\t{\r\n\t\t\t\u003E .title\r\n\t\t\t{\r\n\t\t\t\tcolor: white;\r\n\t\t\t}\r\n\r\n\t\t\t\u0026:before\r\n\t\t\t{\r\n\t\t\t\tcontent: \u0027\u2713\u0027;\r\n\t\t\t\tfont-weight: bold;\r\n\t\t\t\tcolor: #08f;\r\n\t\t\t\tborder-color: #08f;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n"},{"Ident":"fpkreastudios.desertpump","Path":"ui/dropdown.cs.scss","FileName":"dropdown.cs.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":".dropdown\r\n{\r\n\tgap: 2px;\r\n\tflex-grow: 1;\r\n\tcursor: pointer;\r\n\tjustify-content: flex-end;\r\n\talign-items: center;\r\n\tpadding: 0px 12px;\r\n\r\n\t.button-right-column\r\n\t{\r\n\t\tflex-grow: 1;\r\n\t}\r\n}\r\n"},{"Ident":"fpkreastudios.desertpump","Path":"ui/gamehud.razor.scss","FileName":"gamehud.razor.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":"$sand: #e8bd7a;\r\n$sand-dark: #cf9a4c;\r\n$panel: rgba( 18, 22, 32, 0.72 );\r\n$gold: #f5c542;\r\n$water: #4fb0e8;\r\n\r\n.hud\r\n{\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tflex-direction: column;\r\n\tfont-family: Poppins;\r\n\tcolor: white;\r\n\toverflow: hidden;\r\n\r\n\t// s\u0026box does not inherit pointer-events down the tree and the screen root starts\r\n\t// as \u0022none\u0022, so every clickable element sets \u0022all\u0022 for itself. Setting it here\r\n\t// only would leave the whole UI dead to the mouse.\r\n\tpointer-events: all;\r\n}\r\n\r\n// ---------------------------------------------------------------- background\r\n\r\n// Backdrop layers never take the mouse - they sit under the whole screen and would\r\n// otherwise swallow clicks meant for the pump.\r\n.sky,\r\n.sun,\r\n.dune,\r\n.floor,\r\n.clouds,\r\n.motes,\r\n.shimmer,\r\n.fx\r\n{\r\n\tpointer-events: none;\r\n}\r\n\r\n// The rows that hold clickable things, in case the engine wants the whole ancestor\r\n// chain live rather than inheriting from .hud.\r\n.topbar,\r\n.stage,\r\n.actions,\r\n.collection\r\n{\r\n\tpointer-events: all;\r\n}\r\n\r\n.sky\r\n{\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tbackground-image: linear-gradient( to bottom, #4aa3d8 0%, #8fd0ea 38%, #f4d69c 70%, #e9b264 100% );\r\n}\r\n\r\n.sun\r\n{\r\n\tposition: absolute;\r\n\ttop: 70px;\r\n\tright: 160px;\r\n\twidth: 170px;\r\n\theight: 170px;\r\n\tborder-radius: 100%;\r\n\tbackground-color: #ffd84d;\r\n\tbox-shadow: 0px 0px 90px 30px rgba( 255, 216, 77, 0.35 );\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tanimation: sun-breathe 9s ease-in-out infinite;\r\n}\r\n\r\n// A slowly turning square behind the disc. One panel, and it reads as light bending\r\n// off something very bright - eight individual ray divs looked like a cartwheel.\r\n.sun-rays\r\n{\r\n\tposition: absolute;\r\n\twidth: 230%;\r\n\theight: 230%;\r\n\tborder-radius: 22%;\r\n\tbackground-color: rgba( 255, 240, 180, 0.10 );\r\n\tanimation: sun-turn 70s linear infinite;\r\n}\r\n\r\n@keyframes sun-turn\r\n{\r\n\t0% { transform: rotate( 0deg ); }\r\n\t100% { transform: rotate( 360deg ); }\r\n}\r\n\r\n// Scale, not blur radius. The disc and its shadow are drawn once and the transform\r\n// moves the result, where animating the shadow re-blurs a 170px quad every frame for\r\n// as long as the game is running - and the sun is on screen the whole time.\r\n@keyframes sun-breathe\r\n{\r\n\t0% { transform: scale( 1 ); }\r\n\t50% { transform: scale( 1.07 ); }\r\n\t100% { transform: scale( 1 ); }\r\n}\r\n\r\n// ---------------------------------------------------------------- clouds\r\n\r\n.clouds\r\n{\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\twidth: 100%;\r\n\theight: 55%;\r\n\toverflow: hidden;\r\n}\r\n\r\n// Soft ellipses with a wide shadow rather than drawn puffs - at this scale the shadow\r\n// is what sells it, and it costs one panel each.\r\n.cloud\r\n{\r\n\tposition: absolute;\r\n\tborder-radius: 50%;\r\n\tbackground-color: rgba( 255, 255, 255, 0.5 );\r\n\tbox-shadow: 0px 0px 50px 24px rgba( 255, 255, 255, 0.3 );\r\n\r\n\t\u0026.c1\r\n\t{\r\n\t\ttop: 12%;\r\n\t\twidth: 260px;\r\n\t\theight: 54px;\r\n\t\tanimation: drift-a 96s linear infinite;\r\n\t}\r\n\r\n\t\u0026.c2\r\n\t{\r\n\t\ttop: 30%;\r\n\t\twidth: 180px;\r\n\t\theight: 38px;\r\n\t\topacity: 0.7;\r\n\t\tanimation: drift-b 138s linear infinite;\r\n\t}\r\n\r\n\t\u0026.c3\r\n\t{\r\n\t\ttop: 5%;\r\n\t\twidth: 340px;\r\n\t\theight: 62px;\r\n\t\topacity: 0.45;\r\n\t\tanimation: drift-c 176s linear infinite;\r\n\t}\r\n}\r\n\r\n@keyframes drift-a\r\n{\r\n\t0% { left: -22%; }\r\n\t100% { left: 108%; }\r\n}\r\n\r\n@keyframes drift-b\r\n{\r\n\t0% { left: -16%; }\r\n\t100% { left: 104%; }\r\n}\r\n\r\n@keyframes drift-c\r\n{\r\n\t0% { left: -30%; }\r\n\t100% { left: 112%; }\r\n}\r\n\r\n// ---------------------------------------------------------------- dust\r\n\r\n.motes\r\n{\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\toverflow: hidden;\r\n}\r\n\r\n// Specks of sand on the wind. Purely atmosphere, and the one thing on screen that\r\n// keeps moving when the player has stopped.\r\n.mote\r\n{\r\n\tposition: absolute;\r\n\twidth: 5px;\r\n\theight: 5px;\r\n\tborder-radius: 100%;\r\n\tbackground-color: rgba( 255, 244, 214, 0.55 );\r\n\r\n\t\u0026.m1 { top: 42%; width: 4px; height: 4px; animation: mote-a 17s linear infinite; }\r\n\t\u0026.m2 { top: 58%; animation: mote-b 23s linear infinite; }\r\n\t\u0026.m3 { top: 71%; width: 3px; height: 3px; animation: mote-c 14s linear infinite; }\r\n\t\u0026.m4 { top: 36%; animation: mote-b 29s linear infinite; }\r\n\t\u0026.m5 { top: 64%; width: 6px; height: 6px; animation: mote-a 21s linear infinite; }\r\n\t\u0026.m6 { top: 79%; width: 4px; height: 4px; animation: mote-c 26s linear infinite; }\r\n\t\u0026.m7 { top: 50%; width: 3px; height: 3px; animation: mote-b 19s linear infinite; }\r\n\t\u0026.m8 { top: 86%; animation: mote-a 31s linear infinite; }\r\n}\r\n\r\n@keyframes mote-a\r\n{\r\n\t0% { left: -3%; opacity: 0; transform: translateY( 0px ); }\r\n\t14% { opacity: 0.8; }\r\n\t86% { opacity: 0.8; }\r\n\t100% { left: 103%; opacity: 0; transform: translateY( -46px ); }\r\n}\r\n\r\n@keyframes mote-b\r\n{\r\n\t0% { left: -3%; opacity: 0; transform: translateY( 0px ); }\r\n\t18% { opacity: 0.6; }\r\n\t82% { opacity: 0.6; }\r\n\t100% { left: 103%; opacity: 0; transform: translateY( 30px ); }\r\n}\r\n\r\n@keyframes mote-c\r\n{\r\n\t0% { left: -3%; opacity: 0; transform: translateY( 0px ); }\r\n\t20% { opacity: 0.9; }\r\n\t80% { opacity: 0.9; }\r\n\t100% { left: 103%; opacity: 0; transform: translateY( -18px ); }\r\n}\r\n\r\n// ---------------------------------------------------------------- heat haze\r\n\r\n// A band of light sitting on the horizon, breathing. Off by default and turned up by\r\n// the scenes where the air would actually be shaking.\r\n.shimmer\r\n{\r\n\tposition: absolute;\r\n\tbottom: 8%;\r\n\tleft: 0;\r\n\twidth: 100%;\r\n\theight: 22%;\r\n\topacity: 0;\r\n\tbackground-image: linear-gradient( to top, rgba( 255, 255, 255, 0.22 ) 0%, rgba( 255, 255, 255, 0 ) 100% );\r\n\tanimation: haze 7s ease-in-out infinite;\r\n}\r\n\r\n// Deliberately does not touch opacity - that is the switch each scene uses to turn the\r\n// haze on, and a keyframe writing it would override the switch and show it everywhere.\r\n@keyframes haze\r\n{\r\n\t0% { transform: scaleY( 1 ); }\r\n\t50% { transform: scaleY( 1.3 ); }\r\n\t100% { transform: scaleY( 1 ); }\r\n}\r\n\r\n// Wide flat ellipses - only the top curve shows, which reads as a dune ridge.\r\n.dune\r\n{\r\n\tposition: absolute;\r\n\tborder-radius: 50%;\r\n\r\n\t\u0026.far\r\n\t{\r\n\t\tleft: -34%;\r\n\t\twidth: 105%;\r\n\t\tbottom: 14%;\r\n\t\theight: 26%;\r\n\t\tbackground-color: $sand;\r\n\t}\r\n\r\n\t\u0026.near\r\n\t{\r\n\t\tleft: 28%;\r\n\t\twidth: 105%;\r\n\t\tbottom: 12%;\r\n\t\theight: 24%;\r\n\t\tbackground-color: $sand;\r\n\t}\r\n}\r\n\r\n// A very wide ellipse, so the horizon is a gentle curve. As a plain rectangle its\r\n// top edge cut a hard straight line across the sky wherever no dune covered it.\r\n.floor\r\n{\r\n\tposition: absolute;\r\n\tbottom: -34%;\r\n\tleft: -25%;\r\n\twidth: 150%;\r\n\theight: 70%;\r\n\tborder-radius: 50%;\r\n\tbackground-color: $sand-dark;\r\n}\r\n\r\n// ---------------------------------------------------------------- top bar\r\n\r\n.topbar\r\n{\r\n\theight: 96px;\r\n\talign-items: center;\r\n\tpadding-left: 28px;\r\n\tpadding-right: 28px;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.stat\r\n{\r\n\tbackground-color: $panel;\r\n\tborder-radius: 18px;\r\n\tpadding: 10px 18px;\r\n\talign-items: center;\r\n\tmargin-right: 16px;\r\n}\r\n\r\n.coins\r\n{\r\n\tposition: relative;\r\n\ttransition: transform 0.12s ease-out;\r\n\r\n\t.coin\r\n\t{\r\n\t\twidth: 32px;\r\n\t\theight: 32px;\r\n\t\tborder-radius: 100%;\r\n\t\tbackground-color: $gold;\r\n\t\tborder: 3px solid #a8791a;\r\n\t\tmargin-right: 10px;\r\n\t}\r\n\r\n\t.value\r\n\t{\r\n\t\tfont-size: 30px;\r\n\t\tfont-weight: 700;\r\n\t}\r\n\r\n\t// Mounted only for the moment after a sale, so it starts its animation fresh every\r\n\t// time. Toggling a class on a panel that is already there does not reliably restart\r\n\t// one, and a payout that doesn\u0027t visibly land is the whole point of this.\r\n\t.coinflash\r\n\t{\r\n\t\tposition: absolute;\r\n\t\ttop: 0;\r\n\t\tleft: 0;\r\n\t\twidth: 100%;\r\n\t\theight: 100%;\r\n\t\tborder-radius: 18px;\r\n\t\tborder: 2px solid $gold;\r\n\t\tanimation: coin-flash 0.36s ease-out;\r\n\t}\r\n\r\n\t\u0026.pop\r\n\t{\r\n\t\ttransform: scale( 1.06 );\r\n\t}\r\n}\r\n\r\n@keyframes coin-flash\r\n{\r\n\t0% { opacity: 0.95; transform: scale( 1 ); }\r\n\t100% { opacity: 0; transform: scale( 1.18 ); }\r\n}\r\n\r\n.tankstat\r\n{\r\n\t.tank\r\n\t{\r\n\t\twidth: 26px;\r\n\t\theight: 54px;\r\n\t\tborder-radius: 8px;\r\n\t\tbackground-color: rgba( 255, 255, 255, 0.16 );\r\n\t\tborder: 2px solid rgba( 255, 255, 255, 0.45 );\r\n\t\tmargin-right: 12px;\r\n\t\tflex-direction: column;\r\n\t\tjustify-content: flex-end;\r\n\t\toverflow: hidden;\r\n\t\tposition: relative;\r\n\t}\r\n\r\n\t.tank-fill\r\n\t{\r\n\t\twidth: 100%;\r\n\t\tbackground-color: $water;\r\n\t\ttransition: height 0.12s linear;\r\n\t\tposition: relative;\r\n\t}\r\n\r\n\t// A pale disc riding the surface, wider than the tank so only its curve shows. It\r\n\t// rocks side to side, which turns a rectangle sliding up into something with liquid in it.\r\n\t.tank-wave\r\n\t{\r\n\t\tposition: absolute;\r\n\t\ttop: -5px;\r\n\t\tleft: -30%;\r\n\t\twidth: 160%;\r\n\t\theight: 10px;\r\n\t\tborder-radius: 50%;\r\n\t\tbackground-color: #8fd8ff;\r\n\t\tanimation: tank-rock 1.9s ease-in-out infinite;\r\n\t}\r\n\r\n\t// Fixed glass highlight down the left, so the tank reads as a cylinder.\r\n\t.tank-shine\r\n\t{\r\n\t\tposition: absolute;\r\n\t\ttop: 8%;\r\n\t\tleft: 14%;\r\n\t\twidth: 5px;\r\n\t\theight: 60%;\r\n\t\tborder-radius: 999px;\r\n\t\tbackground-color: rgba( 255, 255, 255, 0.35 );\r\n\t}\r\n\r\n\t.tank-text\r\n\t{\r\n\t\tflex-direction: column;\r\n\t}\r\n\r\n\t.value\r\n\t{\r\n\t\tfont-size: 22px;\r\n\t\tfont-weight: 600;\r\n\t}\r\n\r\n\t.unit\r\n\t{\r\n\t\tfont-size: 15px;\r\n\t\topacity: 0.7;\r\n\t}\r\n\r\n\t.sub\r\n\t{\r\n\t\tfont-size: 13px;\r\n\t\topacity: 0.65;\r\n\t}\r\n\r\n\t\u0026.full .tank-fill\r\n\t{\r\n\t\tbackground-color: #ff8b4a;\r\n\t}\r\n\r\n\t\u0026.full .tank-wave\r\n\t{\r\n\t\tbackground-color: #ffb98a;\r\n\t}\r\n\r\n\t\u0026.full .sub\r\n\t{\r\n\t\tcolor: #ffb37a;\r\n\t\topacity: 1;\r\n\t}\r\n\r\n\t// A full tank is wasted pumping, so the whole readout nags rather than sitting there.\r\n\t\u0026.full\r\n\t{\r\n\t\tanimation: tank-nag 1.1s ease-in-out infinite;\r\n\t}\r\n}\r\n\r\n@keyframes tank-rock\r\n{\r\n\t0% { transform: translateX( -4px ) rotate( -2deg ); }\r\n\t50% { transform: translateX( 4px ) rotate( 2deg ); }\r\n\t100% { transform: translateX( -4px ) rotate( -2deg ); }\r\n}\r\n\r\n@keyframes tank-nag\r\n{\r\n\t0% { box-shadow: 0px 0px 0px 0px rgba( 255, 139, 74, 0.5 ); }\r\n\t50% { box-shadow: 0px 0px 18px 3px rgba( 255, 139, 74, 0.55 ); }\r\n\t100% { box-shadow: 0px 0px 0px 0px rgba( 255, 139, 74, 0.5 ); }\r\n}\r\n\r\n.spacer\r\n{\r\n\tflex-grow: 1;\r\n}\r\n\r\n.iconbutton\r\n{\r\n\twidth: 56px;\r\n\theight: 56px;\r\n\tborder-radius: 14px;\r\n\tbackground-color: $panel;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tcursor: pointer;\r\n\tpointer-events: all;\r\n\tposition: relative;\r\n\tmargin-left: 10px;\r\n\ttransition: transform 0.09s ease-out, background-color 0.12s linear;\r\n\r\n\ti\r\n\t{\r\n\t\tfont-family: Material Icons;\r\n\t\tfont-size: 30px;\r\n\t}\r\n\r\n\t\u0026:hover\r\n\t{\r\n\t\tbackground-color: rgba( 40, 48, 64, 0.9 );\r\n\t\ttransform: translateY( -2px );\r\n\t}\r\n\r\n\t\u0026:active\r\n\t{\r\n\t\ttransform: translateY( 1px );\r\n\t}\r\n\r\n\t\u0026.muted\r\n\t{\r\n\t\tbackground-color: rgba( 200, 110, 40, 0.85 );\r\n\t}\r\n\r\n\t// Something in the shop is affordable. A dot in the corner rather than a colour\r\n\t// change on the button, so it reads the way an unread badge does everywhere else.\r\n\t.dot\r\n\t{\r\n\t\tposition: absolute;\r\n\t\ttop: -4px;\r\n\t\tright: -4px;\r\n\t\twidth: 16px;\r\n\t\theight: 16px;\r\n\t\tborder-radius: 100%;\r\n\t\tbackground-color: $gold;\r\n\t\tborder: 2px solid rgba( 18, 22, 32, 0.9 );\r\n\t}\r\n\r\n\t\u0026.highlight\r\n\t{\r\n\t\tanimation: btn-nudge 2.4s ease-in-out infinite;\r\n\t}\r\n}\r\n\r\n@keyframes btn-nudge\r\n{\r\n\t0% { transform: translateY( 0px ); }\r\n\t8% { transform: translateY( -5px ); }\r\n\t16% { transform: translateY( 0px ); }\r\n\t24% { transform: translateY( -3px ); }\r\n\t32% { transform: translateY( 0px ); }\r\n\t100% { transform: translateY( 0px ); }\r\n}\r\n\r\n// ---------------------------------------------------------------- stage\r\n\r\n.stage\r\n{\r\n\tflex-grow: 1;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tposition: relative;\r\n}\r\n\r\n// Wraps the pump so the effects layer has something the exact size and place of the\r\n// pump to anchor to. Anchoring to .stage instead put every burst too low, because the\r\n// nameplate under the pump pushes the pump above the stage\u0027s centre.\r\n.pumpwrap\r\n{\r\n\tposition: relative;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.pumparea\r\n{\r\n\twidth: 330px;\r\n\theight: 330px;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tposition: relative;\r\n\tcursor: pointer;\r\n\tpointer-events: all;\r\n\ttransition: transform 0.07s ease-out;\r\n\r\n\t.pumpart\r\n\t{\r\n\t\twidth: 100%;\r\n\t\theight: 100%;\r\n\t}\r\n\r\n\t// Size and brightness come in as an inline style - they track pressure.\r\n\t.glow\r\n\t{\r\n\t\tposition: absolute;\r\n\t\tborder-radius: 100%;\r\n\t\tbackground-color: rgba( 255, 255, 255, 0.14 );\r\n\t\ttransition: width 0.2s ease-out, height 0.2s ease-out;\r\n\t}\r\n\r\n\t// Only shows once pressure is pinned. The ring, not the glow, is what tells you\r\n\t// you\u0027re at the top - a glow that just keeps brightening has no ceiling to read.\r\n\t.heatring\r\n\t{\r\n\t\tposition: absolute;\r\n\t\twidth: 86%;\r\n\t\theight: 86%;\r\n\t\tborder-radius: 100%;\r\n\t\tborder: 3px solid rgba( 255, 190, 90, 0.85 );\r\n\t\topacity: 0;\r\n\t}\r\n\r\n\t.ground\r\n\t{\r\n\t\tposition: absolute;\r\n\t\tbottom: 2%;\r\n\t\twidth: 70%;\r\n\t\theight: 8%;\r\n\t\tborder-radius: 50%;\r\n\t\tbackground-color: rgba( 90, 60, 20, 0.22 );\r\n\t}\r\n\r\n\t\u0026:hover\r\n\t{\r\n\t\ttransform: scale( 1.03 );\r\n\t}\r\n\r\n\t\u0026.pumping\r\n\t{\r\n\t\ttransform: scale( 0.93 );\r\n\t}\r\n\r\n\t\u0026.full .glow\r\n\t{\r\n\t\tbackground-color: rgba( 255, 140, 60, 0.25 );\r\n\t}\r\n\r\n\t\u0026.hot\r\n\t{\r\n\t\t.heatring\r\n\t\t{\r\n\t\t\topacity: 1;\r\n\t\t\tanimation: heat-ring 1.05s ease-out infinite;\r\n\t\t}\r\n\r\n\t\t.pumpart\r\n\t\t{\r\n\t\t\tanimation: hot-throb 0.62s ease-in-out infinite;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n@keyframes heat-ring\r\n{\r\n\t0% { transform: scale( 0.94 ); opacity: 0.9; }\r\n\t100% { transform: scale( 1.18 ); opacity: 0; }\r\n}\r\n\r\n@keyframes hot-throb\r\n{\r\n\t0% { transform: scale( 1 ); }\r\n\t50% { transform: scale( 1.035 ); }\r\n\t100% { transform: scale( 1 ); }\r\n}\r\n\r\n// ---------------------------------------------------------------- effects layer\r\n\r\n// A zero-size anchor dead centre on the pump. Everything in here positions itself off\r\n// it with margins, which is why the particle code can work in plain pixel offsets.\r\n.fx\r\n{\r\n\tposition: absolute;\r\n\tleft: 50%;\r\n\ttop: 50%;\r\n\twidth: 0px;\r\n\theight: 0px;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n}\r\n\r\n.particle\r\n{\r\n\tposition: absolute;\r\n\tpointer-events: none;\r\n\r\n\t// Water thrown up out of the pump head.\r\n\t\u0026.drop\r\n\t{\r\n\t\tborder-radius: 50%;\r\n\t\tbackground-color: #9fe0ff;\r\n\t\tborder: 2px solid rgba( 255, 255, 255, 0.75 );\r\n\t}\r\n\r\n\t// Coins off a sale. Round, gold, and spinning - the spin comes in inline.\r\n\t\u0026.coinbit\r\n\t{\r\n\t\tborder-radius: 100%;\r\n\t\tbackground-color: $gold;\r\n\t\tborder: 3px solid #a8791a;\r\n\t}\r\n\r\n\t// The flat radial burst on a surge or a new pump.\r\n\t\u0026.spark\r\n\t{\r\n\t\tborder-radius: 100%;\r\n\t\tbackground-color: #fff6d8;\r\n\t\tbox-shadow: 0px 0px 14px 4px rgba( 255, 214, 120, 0.75 );\r\n\t}\r\n}\r\n\r\n// A ring thrown off the pump on every click. Mounted fresh each time so it always\r\n// plays from the start.\r\n// Centred by hand with half its size off the top and left. The layout engine will\r\n// centre an absolutely positioned child off the parent\u0027s alignment, but only while no\r\n// inset is set - too subtle a rule to hang the whole effects layer on.\r\n.clickring\r\n{\r\n\tposition: absolute;\r\n\tleft: 50%;\r\n\ttop: 50%;\r\n\tmargin-left: -105px;\r\n\tmargin-top: -105px;\r\n\twidth: 210px;\r\n\theight: 210px;\r\n\tborder-radius: 100%;\r\n\tborder: 4px solid rgba( 190, 235, 255, 0.75 );\r\n\tpointer-events: none;\r\n\tanimation: click-ring 0.42s ease-out;\r\n}\r\n\r\n@keyframes click-ring\r\n{\r\n\t0% { transform: scale( 0.42 ); opacity: 0.85; }\r\n\t100% { transform: scale( 1.3 ); opacity: 0; }\r\n}\r\n\r\n.surgering\r\n{\r\n\tposition: absolute;\r\n\tleft: 50%;\r\n\ttop: 50%;\r\n\tmargin-left: -150px;\r\n\tmargin-top: -150px;\r\n\twidth: 300px;\r\n\theight: 300px;\r\n\tborder-radius: 100%;\r\n\tborder: 6px solid rgba( 255, 226, 150, 0.9 );\r\n\tbox-shadow: 0px 0px 40px 10px rgba( 255, 200, 90, 0.5 );\r\n\tpointer-events: none;\r\n\tanimation: surge-ring 0.6s ease-out;\r\n}\r\n\r\n@keyframes surge-ring\r\n{\r\n\t0% { transform: scale( 0.3 ); opacity: 1; }\r\n\t100% { transform: scale( 1.7 ); opacity: 0; }\r\n}\r\n\r\n// A wash of warm light over the whole screen for a fifth of a second.\r\n.surgeflash\r\n{\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\tbackground-color: rgba( 255, 220, 150, 0.4 );\r\n\tpointer-events: none;\r\n\tanimation: surge-flash 0.4s ease-out;\r\n}\r\n\r\n@keyframes surge-flash\r\n{\r\n\t0% { opacity: 0.75; }\r\n\t100% { opacity: 0; }\r\n}\r\n\r\n// ---------------------------------------------------------------- shake\r\n\r\n// The stage shakes, not the whole HUD. Moving the root would drag the sky and the\r\n// dunes with it and show bare edges at the screen border.\r\n.hud.shake-soft .stage\r\n{\r\n\tanimation: shake-soft 0.32s ease-out;\r\n}\r\n\r\n.hud.shake-hard .stage\r\n{\r\n\tanimation: shake-hard 0.32s ease-out;\r\n}\r\n\r\n@keyframes shake-soft\r\n{\r\n\t0% { transform: translateX( 0px ) translateY( 0px ); }\r\n\t25% { transform: translateX( -4px ) translateY( 2px ); }\r\n\t50% { transform: translateX( 3px ) translateY( -2px ); }\r\n\t75% { transform: translateX( -2px ) translateY( 1px ); }\r\n\t100% { transform: translateX( 0px ) translateY( 0px ); }\r\n}\r\n\r\n@keyframes shake-hard\r\n{\r\n\t0% { transform: translateX( 0px ) translateY( 0px ); }\r\n\t15% { transform: translateX( -9px ) translateY( 4px ); }\r\n\t32% { transform: translateX( 8px ) translateY( -5px ); }\r\n\t50% { transform: translateX( -6px ) translateY( 3px ); }\r\n\t70% { transform: translateX( 4px ) translateY( -2px ); }\r\n\t85% { transform: translateX( -2px ) translateY( 1px ); }\r\n\t100% { transform: translateX( 0px ) translateY( 0px ); }\r\n}\r\n\r\n// ---------------------------------------------------------------- pressure\r\n\r\n// Sits between the pump and the nameplate. Always laid out, faded out when cold, so\r\n// showing and hiding it never shunts the nameplate up and down the screen.\r\n.pressure\r\n{\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tmargin-top: 6px;\r\n\ttransition: opacity 0.25s linear;\r\n\r\n\t\u0026.cold { opacity: 0.25; }\r\n\r\n\t.pressure-bar\r\n\t{\r\n\t\twidth: 240px;\r\n\t\theight: 10px;\r\n\t\tborder-radius: 999px;\r\n\t\tbackground-color: rgba( 0, 0, 0, 0.45 );\r\n\t\tborder: 1px solid rgba( 255, 255, 255, 0.22 );\r\n\t\toverflow: hidden;\r\n\t}\r\n\r\n\t.pressure-fill\r\n\t{\r\n\t\theight: 100%;\r\n\t\tbackground-color: $water;\r\n\t\ttransition: width 0.1s linear, background-color 0.3s linear;\r\n\t}\r\n\r\n\t.pressure-label\r\n\t{\r\n\t\tmargin-top: 4px;\r\n\t\talign-items: center;\r\n\t}\r\n\r\n\t.pressure-name\r\n\t{\r\n\t\tfont-size: 11px;\r\n\t\tletter-spacing: 3px;\r\n\t\topacity: 0.7;\r\n\t\tmargin-right: 8px;\r\n\t}\r\n\r\n\t.pressure-value\r\n\t{\r\n\t\tfont-size: 14px;\r\n\t\tfont-weight: 800;\r\n\t\tcolor: #bfe9ff;\r\n\t}\r\n\r\n\t// Four bands rather than a continuous ramp - a colour you can name is a colour you\r\n\t// can chase, and the game tells you which band you\u0027re in without a number.\r\n\t\u0026.s1 .pressure-fill { background-color: #6fd3a0; }\r\n\t\u0026.s1 .pressure-value { color: #9ff0c6; }\r\n\r\n\t\u0026.s2 .pressure-fill { background-color: #ffb54a; }\r\n\t\u0026.s2 .pressure-value { color: #ffd08a; }\r\n\r\n\t\u0026.s3\r\n\t{\r\n\t\t.pressure-fill\r\n\t\t{\r\n\t\t\tbackground-color: #fff0c0;\r\n\t\t\tbox-shadow: 0px 0px 14px 3px rgba( 255, 200, 90, 0.8 );\r\n\t\t}\r\n\r\n\t\t.pressure-value\r\n\t\t{\r\n\t\t\tcolor: #fff2cc;\r\n\t\t}\r\n\r\n\t\t.pressure-bar\r\n\t\t{\r\n\t\t\tborder-color: rgba( 255, 214, 120, 0.9 );\r\n\t\t\tanimation: pressure-max 0.8s ease-in-out infinite;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n@keyframes pressure-max\r\n{\r\n\t0% { box-shadow: 0px 0px 0px 0px rgba( 255, 200, 90, 0.6 ); }\r\n\t50% { box-shadow: 0px 0px 16px 3px rgba( 255, 200, 90, 0.6 ); }\r\n\t100% { box-shadow: 0px 0px 0px 0px rgba( 255, 200, 90, 0.6 ); }\r\n}\r\n\r\n.nameplate\r\n{\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tmargin-top: 4px;\r\n\tpadding: 10px 30px 14px 30px;\r\n\tborder-radius: 22px;\r\n\tbackground-color: rgba( 16, 20, 30, 0.38 );\r\n\r\n\t.tierno\r\n\t{\r\n\t\tfont-size: 14px;\r\n\t\tletter-spacing: 3px;\r\n\t\topacity: 1;\r\n\t\ttext-shadow: 0px 2px 0px rgba( 0, 0, 0, 0.6 );\r\n\t}\r\n\r\n\t.name\r\n\t{\r\n\t\tfont-size: 40px;\r\n\t\tfont-weight: 800;\r\n\t\ttext-shadow: 0px 3px 0px rgba( 0, 0, 0, 0.35 );\r\n\t}\r\n\r\n\t.tagline\r\n\t{\r\n\t\tfont-size: 16px;\r\n\t\topacity: 1;\r\n\t\ttext-shadow: 0px 2px 0px rgba( 0, 0, 0, 0.55 );\r\n\t}\r\n\r\n\t.perclick\r\n\t{\r\n\t\tmargin-top: 8px;\r\n\t\tfont-size: 15px;\r\n\t\tbackground-color: rgba( 18, 22, 32, 0.55 );\r\n\t\tpadding: 4px 16px;\r\n\t\tborder-radius: 999px;\r\n\t}\r\n}\r\n\r\n// Lives in .fx now, so left/top come in inline off the pump\u0027s centre. The fixed width\r\n// with centred text is what lets the code place it without measuring the string - keep\r\n// it in step with FloaterWidth in GameHud.razor.\r\n.floater\r\n{\r\n\tposition: absolute;\r\n\twidth: 300px;\r\n\ttext-align: center;\r\n\tjustify-content: center;\r\n\tfont-size: 30px;\r\n\tfont-weight: 800;\r\n\tcolor: #d3f0ff;\r\n\ttext-shadow: 0px 2px 0px rgba( 0, 0, 0, 0.45 );\r\n\tpointer-events: none;\r\n\r\n\t\u0026.coin\r\n\t{\r\n\t\tcolor: $gold;\r\n\t}\r\n\r\n\t\u0026.surge\r\n\t{\r\n\t\tfont-size: 40px;\r\n\t\tcolor: #fff4d2;\r\n\t\tletter-spacing: 2px;\r\n\t\ttext-shadow: 0px 3px 0px rgba( 120, 60, 0, 0.55 );\r\n\t}\r\n}\r\n\r\n.toast\r\n{\r\n\tposition: absolute;\r\n\tbottom: 13%;\r\n\tbackground-color: rgba( 18, 22, 32, 0.85 );\r\n\tpadding: 10px 22px;\r\n\tborder-radius: 999px;\r\n\tfont-size: 18px;\r\n\tpointer-events: none;\r\n}\r\n\r\n.idlenote\r\n{\r\n\tposition: absolute;\r\n\ttop: 5%;\r\n\tbackground-color: rgba( 30, 90, 130, 0.85 );\r\n\tpadding: 8px 20px;\r\n\tborder-radius: 999px;\r\n\tfont-size: 16px;\r\n\tpointer-events: none;\r\n\talign-items: center;\r\n\tborder: 2px solid rgba( 140, 210, 255, 0.5 );\r\n\r\n\ti\r\n\t{\r\n\t\tfont-family: Material Icons;\r\n\t\tfont-size: 20px;\r\n\t\tmargin-right: 8px;\r\n\t}\r\n}\r\n\r\n// Scoped to the stage on purpose - as a bare \u0022.hint\u0022 this rule reached into the\r\n// settings panel and threw its sub-labels to the bottom of the screen.\r\n.stage .hint\r\n{\r\n\tposition: absolute;\r\n\tbottom: 4%;\r\n\tfont-size: 14px;\r\n\tletter-spacing: 2px;\r\n\topacity: 1;\r\n\tpadding: 5px 16px;\r\n\tborder-radius: 999px;\r\n\tbackground-color: rgba( 16, 20, 30, 0.38 );\r\n\tpointer-events: none;\r\n}\r\n\r\n// ---------------------------------------------------------------- actions\r\n\r\n.actions\r\n{\r\n\theight: 130px;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.bigbutton\r\n{\r\n\twidth: 420px;\r\n\theight: 92px;\r\n\tborder-radius: 22px;\r\n\talign-items: center;\r\n\tpadding: 0px 24px;\r\n\tmargin: 0px 12px;\r\n\tcursor: pointer;\r\n\tpointer-events: all;\r\n\ttransition: transform 0.07s ease-out;\r\n\tborder-bottom: 6px solid rgba( 0, 0, 0, 0.35 );\r\n\tposition: relative;\r\n\toverflow: hidden;\r\n\r\n\t.btn-icon i\r\n\t{\r\n\t\tfont-family: Material Icons;\r\n\t\tfont-size: 40px;\r\n\t\tmargin-right: 16px;\r\n\t}\r\n\r\n\t// A band of light that sweeps across the face. Parked off the left edge and only\r\n\t// sent across when the button is actually worth pressing.\r\n\t.shine\r\n\t{\r\n\t\tposition: absolute;\r\n\t\ttop: 0;\r\n\t\tleft: -40%;\r\n\t\twidth: 30%;\r\n\t\theight: 100%;\r\n\t\tbackground-image: linear-gradient( to right, rgba( 255, 255, 255, 0 ) 0%, rgba( 255, 255, 255, 0.35 ) 50%, rgba( 255, 255, 255, 0 ) 100% );\r\n\t\topacity: 0;\r\n\t}\r\n\r\n\t.btn-text\r\n\t{\r\n\t\tflex-direction: column;\r\n\t}\r\n\r\n\t.btn-title\r\n\t{\r\n\t\tfont-size: 23px;\r\n\t\tfont-weight: 800;\r\n\t}\r\n\r\n\t.btn-sub\r\n\t{\r\n\t\tfont-size: 16px;\r\n\t\topacity: 0.85;\r\n\t}\r\n\r\n\t\u0026:hover\r\n\t{\r\n\t\ttransform: translateY( -3px );\r\n\t}\r\n\r\n\t\u0026:active\r\n\t{\r\n\t\ttransform: translateY( 2px );\r\n\t}\r\n\r\n\t\u0026.sell\r\n\t{\r\n\t\tbackground-color: #3aa7e0;\r\n\r\n\t\t\u0026:not(.disabled) .shine\r\n\t\t{\r\n\t\t\topacity: 1;\r\n\t\t\tanimation: shine-sweep 3.4s linear infinite;\r\n\t\t}\r\n\t}\r\n\r\n\t\u0026.buy\r\n\t{\r\n\t\tbackground-color: #f0a92c;\r\n\t}\r\n\r\n\t// Affordable. This is the moment the whole loop is pointed at, so it gets the\r\n\t// brightest thing on screen after the pump itself.\r\n\t\u0026.buy.ready\r\n\t{\r\n\t\tanimation: buy-ready 1.6s ease-in-out infinite;\r\n\r\n\t\t.shine\r\n\t\t{\r\n\t\t\topacity: 1;\r\n\t\t\tanimation: shine-sweep 2.2s linear infinite;\r\n\t\t}\r\n\t}\r\n\r\n\t\u0026.maxed\r\n\t{\r\n\t\tbackground-color: #7a5cc4;\r\n\t\tcursor: default;\r\n\t}\r\n\r\n\t\u0026.disabled\r\n\t{\r\n\t\tbackground-color: #5f6675;\r\n\t\topacity: 0.65;\r\n\t\tcursor: default;\r\n\r\n\t\t\u0026:hover\r\n\t\t{\r\n\t\t\ttransform: translateY( 0px );\r\n\t\t}\r\n\t}\r\n}\r\n\r\n@keyframes shine-sweep\r\n{\r\n\t0% { left: -40%; }\r\n\t40% { left: 110%; }\r\n\t100% { left: 110%; }\r\n}\r\n\r\n@keyframes buy-ready\r\n{\r\n\t0% { box-shadow: 0px 0px 0px 0px rgba( 245, 197, 66, 0.55 ); }\r\n\t50% { box-shadow: 0px 0px 30px 6px rgba( 245, 197, 66, 0.5 ); }\r\n\t100% { box-shadow: 0px 0px 0px 0px rgba( 245, 197, 66, 0.55 ); }\r\n}\r\n\r\n// ---------------------------------------------------------------- collection\r\n\r\n.collection\r\n{\r\n\theight: 170px;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tpadding-bottom: 14px;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.slot\r\n{\r\n\twidth: 118px;\r\n\theight: 142px;\r\n\tmargin: 0px 6px;\r\n\tpadding: 8px;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tjustify-content: flex-end;\r\n\tborder-radius: 16px;\r\n\tbackground-color: rgba( 18, 22, 32, 0.4 );\r\n\tborder: 3px solid rgba( 0, 0, 0, 0 );\r\n\tposition: relative;\r\n\ttransition: transform 0.12s ease-out, background-color 0.2s linear;\r\n\r\n\t\u0026:hover\r\n\t{\r\n\t\ttransform: translateY( -4px );\r\n\t}\r\n\r\n\t.tick\r\n\t{\r\n\t\tposition: absolute;\r\n\t\ttop: 6px;\r\n\t\tright: 6px;\r\n\t\twidth: 22px;\r\n\t\theight: 22px;\r\n\t\tborder-radius: 100%;\r\n\t\tbackground-color: #4f9a45;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\r\n\t\ti\r\n\t\t{\r\n\t\t\tfont-family: Material Icons;\r\n\t\t\tfont-size: 15px;\r\n\t\t}\r\n\t}\r\n\r\n\t.slotart\r\n\t{\r\n\t\twidth: 100%;\r\n\t\theight: 92px;\r\n\t\tposition: relative;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t}\r\n\r\n\t.question\r\n\t{\r\n\t\tposition: absolute;\r\n\t\tfont-size: 38px;\r\n\t\tfont-weight: 800;\r\n\t\tcolor: rgba( 255, 255, 255, 0.7 );\r\n\t}\r\n\r\n\t.slotlabel\r\n\t{\r\n\t\tmargin-top: 4px;\r\n\t\tfont-size: 12px;\r\n\t\ttext-align: center;\r\n\t\topacity: 0.9;\r\n\t}\r\n\r\n\t\u0026.owned\r\n\t{\r\n\t\tbackground-color: rgba( 42, 92, 60, 0.5 );\r\n\t}\r\n\r\n\t\u0026.next\r\n\t{\r\n\t\tborder-color: $gold;\r\n\t\tbox-shadow: 0px 0px 16px 2px rgba( 245, 197, 66, 0.4 );\r\n\t\tanimation: slot-next 2s ease-in-out infinite;\r\n\t}\r\n}\r\n\r\n// The glow is a fixed shadow and the pulse is a colour. Animating the blur radius\r\n// instead makes the renderer rebuild a blurred quad on every frame of every second the\r\n// game is open, and this one is always on because there is always a next pump.\r\n//\r\n// The border, specifically. .slot animates neither - it transitions transform for its\r\n// hover lift and background-color for its owned state, and a keyframe on either of\r\n// those would fight something: an animation outranks the hover for as long as it runs,\r\n// which would have quietly killed the lift on the one slot the player is most likely\r\n// to point at.\r\n@keyframes slot-next\r\n{\r\n\t0% { border-color: rgba( 245, 197, 66, 0.45 ); }\r\n\t50% { border-color: rgba( 255, 226, 140, 1 ); }\r\n\t100% { border-color: rgba( 245, 197, 66, 0.45 ); }\r\n}\r\n\r\n// ---------------------------------------------------------------- fallback\r\n\r\n.missing\r\n{\r\n\tposition: absolute;\r\n\ttop: 40%;\r\n\tleft: 50%;\r\n\tmargin-left: -260px;\r\n\twidth: 520px;\r\n\tflex-direction: column;\r\n\talign-items: center;\r\n\tbackground-color: rgba( 18, 22, 32, 0.85 );\r\n\tborder-radius: 18px;\r\n\tpadding: 24px;\r\n\r\n\t.missing-title\r\n\t{\r\n\t\tfont-size: 22px;\r\n\t\tfont-weight: 700;\r\n\t}\r\n\r\n\t.missing-body\r\n\t{\r\n\t\tfont-size: 15px;\r\n\t\topacity: 0.8;\r\n\t\tmargin-top: 6px;\r\n\t}\r\n}\r\n\r\n// ---------------------------------------------------------------- scenery\r\n\r\n// Extra layers the default scene doesn\u0027t use - each background switches them on.\r\n.stars,\r\n.scenery\r\n{\r\n\tposition: absolute;\r\n\tpointer-events: none;\r\n}\r\n\r\n.stars\r\n{\r\n\ttop: 0;\r\n\tleft: 0;\r\n\twidth: 100%;\r\n\theight: 60%;\r\n\topacity: 0;\r\n}\r\n\r\n.scenery\r\n{\r\n\tbottom: 0;\r\n\tleft: 0;\r\n\twidth: 100%;\r\n\theight: 40%;\r\n\topacity: 0;\r\n}\r\n\r\n// ---- Blood Moon: night dunes under something large and red\r\n\r\n.scene-dusk\r\n{\r\n\t.sky { background-image: linear-gradient( to bottom, #150a24 0%, #241436 40%, #5c2536 78%, #8c3a35 100% ); }\r\n\r\n\t.sun\r\n\t{\r\n\t\tbackground-color: #d94f43;\r\n\t\tbox-shadow: 0px 0px 120px 40px rgba( 217, 79, 67, 0.4 );\r\n\t\twidth: 210px;\r\n\t\theight: 210px;\r\n\t}\r\n\r\n\t.dune.far { background-color: #45283f; }\r\n\t.dune.near { background-color: #45283f; }\r\n\t.floor { background-color: #2c1a2c; }\r\n\r\n\t// a scatter of stars, faked with a few hard-stop gradients\r\n\t.stars\r\n\t{\r\n\t\topacity: 0.9;\r\n\t\tbackground-image: linear-gradient( to bottom, rgba( 255, 255, 255, 0.16 ) 0%, rgba( 255, 255, 255, 0 ) 70% );\r\n\t}\r\n\r\n\t.nameplate .name { text-shadow: 0px 3px 0px rgba( 0, 0, 0, 0.55 ); }\r\n}\r\n\r\n// ---- Green Oasis: you pumped enough that things started growing\r\n\r\n.scene-oasis\r\n{\r\n\t.sky { background-image: linear-gradient( to bottom, #2f8fbf 0%, #6fc6c2 45%, #a9d98d 80%, #6ea84e 100% ); }\r\n\r\n\t.sun\r\n\t{\r\n\t\tbackground-color: #fff2a8;\r\n\t\tbox-shadow: 0px 0px 100px 34px rgba( 255, 242, 168, 0.45 );\r\n\t}\r\n\r\n\t.dune.far { background-color: #67ab55; }\r\n\t.dune.near { background-color: #4f8f3a; }\r\n\t.floor { background-color: #3d7330; }\r\n\r\n\t.scenery\r\n\t{\r\n\t\topacity: 1;\r\n\t\tbackground-image: linear-gradient( to top, rgba( 40, 90, 40, 0.55 ) 0%, rgba( 40, 90, 40, 0 ) 55% );\r\n\t}\r\n}\r\n\r\n// ---- Neon Wastes: the desert, some time after everyone left\r\n\r\n.scene-neon\r\n{\r\n\t.sky { background-image: linear-gradient( to bottom, #12042a 0%, #2a0b45 42%, #7b2062 76%, #ff5fa2 100% ); }\r\n\r\n\t.sun\r\n\t{\r\n\t\tbackground-color: #ff7ac6;\r\n\t\tbox-shadow: 0px 0px 140px 46px rgba( 255, 95, 162, 0.45 );\r\n\t\twidth: 220px;\r\n\t\theight: 220px;\r\n\t}\r\n\r\n\t.dune.far { background-color: #35114f; }\r\n\t.dune.near { background-color: #24093a; }\r\n\t.floor { background-color: #160526; }\r\n\r\n\t.stars\r\n\t{\r\n\t\topacity: 0.8;\r\n\t\tbackground-image: linear-gradient( to bottom, rgba( 255, 255, 255, 0.12 ) 0%, rgba( 255, 255, 255, 0 ) 65% );\r\n\t}\r\n\r\n\t// horizon glow standing in for a synth grid\r\n\t.scenery\r\n\t{\r\n\t\topacity: 1;\r\n\t\tbackground-image: linear-gradient( to top, rgba( 255, 95, 162, 0.35 ) 0%, rgba( 255, 95, 162, 0 ) 40% );\r\n\t}\r\n\r\n\t.nameplate .name { color: #ffd9ec; }\r\n}\r\n\r\n// ---------------------------------------------------------------- tier bar\r\n\r\n.tierbar\r\n{\r\n\twidth: 260px;\r\n\theight: 8px;\r\n\tborder-radius: 999px;\r\n\tbackground-color: rgba( 0, 0, 0, 0.45 );\r\n\tborder: 1px solid rgba( 255, 255, 255, 0.25 );\r\n\tmargin-top: 10px;\r\n\toverflow: hidden;\r\n}\r\n\r\n.tierfill\r\n{\r\n\theight: 100%;\r\n\tbackground-color: $gold;\r\n\ttransition: width 0.2s ease-out;\r\n}\r\n\r\n// ---------------------------------------------------------------- gusher\r\n\r\n.gusher\r\n{\r\n\tposition: absolute;\r\n\twidth: 96px;\r\n\theight: 96px;\r\n\tmargin-left: -48px;\r\n\tmargin-top: -48px;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tcursor: pointer;\r\n\tpointer-events: all;\r\n\tanimation: gusher-bob 1.6s ease-in-out infinite;\r\n\r\n\t// Teardrop rather than a disc, and blue-white rather than yellow - as a glowing\r\n\t// yellow circle it was indistinguishable from the sun behind it.\r\n\t.gusher-drop\r\n\t{\r\n\t\twidth: 46px;\r\n\t\theight: 58px;\r\n\t\tborder-radius: 50% 50% 50% 50%;\r\n\t\tbackground-image: linear-gradient( to bottom, #eaf9ff, #4fb0e8 );\r\n\t\tborder: 3px solid #ffffff;\r\n\t\tbox-shadow: 0px 0px 34px 10px rgba( 79, 176, 232, 0.6 );\r\n\t}\r\n\r\n\t.gusher-ring\r\n\t{\r\n\t\tposition: absolute;\r\n\t\twidth: 92px;\r\n\t\theight: 92px;\r\n\t\tborder-radius: 100%;\r\n\t\tborder: 3px solid rgba( 160, 225, 255, 0.7 );\r\n\t\tanimation: gusher-pulse 1.6s ease-out infinite;\r\n\t}\r\n\r\n\t\u0026:hover .gusher-drop { transform: scale( 1.12 ); }\r\n}\r\n\r\n@keyframes gusher-bob\r\n{\r\n\t0% { transform: translateY( 0px ); }\r\n\t50% { transform: translateY( -12px ); }\r\n\t100% { transform: translateY( 0px ); }\r\n}\r\n\r\n@keyframes gusher-pulse\r\n{\r\n\t0% { transform: scale( 0.7 ); opacity: 0.9; }\r\n\t100% { transform: scale( 1.25 ); opacity: 0; }\r\n}\r\n\r\n// ---------------------------------------------------------------- daily\r\n\r\n.daily\r\n{\r\n\tposition: absolute;\r\n\ttop: 110px;\r\n\tleft: 28px;\r\n\talign-items: center;\r\n\tbackground-color: rgba( 24, 30, 44, 0.9 );\r\n\tborder: 3px solid $gold;\r\n\tborder-radius: 18px;\r\n\tpadding: 12px 20px 12px 14px;\r\n\tcursor: pointer;\r\n\tpointer-events: all;\r\n\tanimation: daily-glow 2.2s ease-in-out infinite;\r\n\r\n\t.daily-icon\r\n\t{\r\n\t\tmargin-right: 12px;\r\n\r\n\t\ti\r\n\t\t{\r\n\t\t\tfont-family: Material Icons;\r\n\t\t\tfont-size: 34px;\r\n\t\t\tcolor: $gold;\r\n\t\t}\r\n\t}\r\n\r\n\t.daily-text { flex-direction: column; }\r\n\t.daily-title { font-size: 16px; font-weight: 800; letter-spacing: 1px; }\r\n\t.daily-sub { font-size: 13px; opacity: 0.8; }\r\n\r\n\t\u0026:hover { transform: scale( 1.03 ); }\r\n}\r\n\r\n@keyframes daily-glow\r\n{\r\n\t0% { box-shadow: 0px 0px 0px 0px rgba( 245, 197, 66, 0.5 ); }\r\n\t50% { box-shadow: 0px 0px 26px 4px rgba( 245, 197, 66, 0.45 ); }\r\n\t100% { box-shadow: 0px 0px 0px 0px rgba( 245, 197, 66, 0.5 ); }\r\n}\r\n\r\n// ---------------------------------------------------------------- the tree\r\n\r\n.treearea\r\n{\r\n\tposition: absolute;\r\n\tleft: 34px;\r\n\tbottom: 14%;\r\n\talign-items: flex-end;\r\n\tpointer-events: all;\r\n}\r\n\r\n.tree\r\n{\r\n\twidth: 118px;\r\n\theight: 150px;\r\n\tposition: relative;\r\n\tcursor: pointer;\r\n\tpointer-events: all;\r\n\tbackground-repeat: no-repeat;\r\n\tbackground-position: bottom center;\r\n\tbackground-size: contain;\r\n\ttransition: transform 0.25s ease-out;\r\n\r\n\t\u0026:hover { transform: scale( 1.06 ); }\r\n\r\n\t\u0026.growing { animation: tree-sway 3.4s ease-in-out infinite; }\r\n}\r\n\r\n@keyframes tree-sway\r\n{\r\n\t0% { transform: rotate( -1.5deg ); }\r\n\t50% { transform: rotate( 1.5deg ); }\r\n\t100% { transform: rotate( -1.5deg ); }\r\n}\r\n\r\n.treeinfo\r\n{\r\n\tflex-direction: column;\r\n\tmargin-left: 10px;\r\n\tmargin-bottom: 8px;\r\n\tbackground-color: rgba( 18, 22, 32, 0.7 );\r\n\tborder-radius: 14px;\r\n\tpadding: 10px 12px;\r\n\r\n\t.treeheight\r\n\t{\r\n\t\tfont-size: 15px;\r\n\t\tfont-weight: 700;\r\n\t\talign-items: center;\r\n\r\n\t\ti\r\n\t\t{\r\n\t\t\tfont-family: Material Icons;\r\n\t\t\tfont-size: 18px;\r\n\t\t\tmargin-right: 6px;\r\n\t\t\tcolor: #7fd06f;\r\n\t\t}\r\n\t}\r\n\r\n\t.treebar\r\n\t{\r\n\t\twidth: 130px;\r\n\t\theight: 7px;\r\n\t\tborder-radius: 999px;\r\n\t\tbackground-color: rgba( 0, 0, 0, 0.35 );\r\n\t\tmargin-top: 6px;\r\n\t\toverflow: hidden;\r\n\t}\r\n\r\n\t.treefill\r\n\t{\r\n\t\theight: 100%;\r\n\t\tbackground-color: #7fd06f;\r\n\t\ttransition: width 0.2s ease-out;\r\n\t}\r\n\r\n\t.treestatus\r\n\t{\r\n\t\tfont-size: 12px;\r\n\t\topacity: 0.75;\r\n\t\tmargin-top: 4px;\r\n\r\n\t\t\u0026.growing { color: #7fd06f; opacity: 1; }\r\n\t}\r\n\r\n\t.treebtn\r\n\t{\r\n\t\tmargin-top: 8px;\r\n\t\tpadding: 6px 12px;\r\n\t\tborder-radius: 10px;\r\n\t\tbackground-color: #4f9a45;\r\n\t\tfont-size: 13px;\r\n\t\tfont-weight: 700;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t\tcursor: pointer;\r\n\t\tpointer-events: all;\r\n\r\n\t\ti\r\n\t\t{\r\n\t\t\tfont-family: Material Icons;\r\n\t\t\tfont-size: 16px;\r\n\t\t\tmargin-right: 6px;\r\n\t\t}\r\n\r\n\t\t\u0026:hover { background-color: #5cae50; }\r\n\r\n\t\t\u0026.board { background-color: #3aa7e0; }\r\n\t\t\u0026.board:hover { background-color: #4bb8f0; }\r\n\r\n\t\t\u0026.disabled\r\n\t\t{\r\n\t\t\tbackground-color: #5f6675;\r\n\t\t\topacity: 0.6;\r\n\t\t\tcursor: default;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// ---- Sandstorm: ochre haze, the horizon eaten by dust\r\n\r\n.scene-sandstorm\r\n{\r\n\t.sky { background-image: linear-gradient( to bottom, #d9ae6b 0%, #c99a52 45%, #a97c3c 78%, #7a5326 100% ); }\r\n\t.sun { background-color: #f0d9a6; box-shadow: 0px 0px 130px 50px rgba( 240, 217, 166, 0.3 ); }\r\n\t.dune.far { background-color: #a9803f; }\r\n\t.dune.near { background-color: #96702f; }\r\n\t.floor { background-color: #7a5326; }\r\n\t.scenery\r\n\t{\r\n\t\topacity: 1;\r\n\t\tbackground-image: linear-gradient( to top, rgba( 200, 160, 90, 0.4 ) 0%, rgba( 200, 160, 90, 0 ) 60% );\r\n\t}\r\n}\r\n\r\n// ---- Golden Hour: the twenty minutes a day the desert looks kind\r\n\r\n.scene-golden\r\n{\r\n\t.sky { background-image: linear-gradient( to bottom, #5b3a86 0%, #c1476a 38%, #f2884b 74%, #f7b167 100% ); }\r\n\t.sun { background-color: #fff0c4; box-shadow: 0px 0px 120px 44px rgba( 255, 200, 120, 0.5 ); }\r\n\t.dune.far { background-color: #a4553f; }\r\n\t.dune.near { background-color: #8c4436; }\r\n\t.floor { background-color: #6d3630; }\r\n}\r\n\r\n// ---- Starfield: no moon, no cloud, rather a lot of sky\r\n\r\n.scene-starfield\r\n{\r\n\t.sky { background-image: linear-gradient( to bottom, #04060f 0%, #070b1c 40%, #18213f 75%, #2b3560 100% ); }\r\n\t.sun\r\n\t{\r\n\t\tbackground-color: #dfe6ff;\r\n\t\twidth: 90px;\r\n\t\theight: 90px;\r\n\t\tbox-shadow: 0px 0px 70px 22px rgba( 223, 230, 255, 0.35 );\r\n\t}\r\n\t.dune.far { background-color: #1b2340; }\r\n\t.dune.near { background-color: #141a30; }\r\n\t.floor { background-color: #0d1122; }\r\n\t.stars\r\n\t{\r\n\t\topacity: 1;\r\n\t\tbackground-image: linear-gradient( to bottom, rgba( 255, 255, 255, 0.22 ) 0%, rgba( 255, 255, 255, 0 ) 75% );\r\n\t}\r\n}\r\n\r\n// ---- Aurora: green light over the ice\r\n\r\n.scene-aurora\r\n{\r\n\t.sky { background-image: linear-gradient( to bottom, #04121f 0%, #0b2c3a 35%, #1c6b63 70%, #3fd6a0 100% ); }\r\n\t.sun { background-color: #b9f5e2; box-shadow: 0px 0px 110px 40px rgba( 100, 230, 190, 0.4 ); }\r\n\t.dune.far { background-color: #1d5a5a; }\r\n\t.dune.near { background-color: #16414a; }\r\n\t.floor { background-color: #0f2c35; }\r\n\t.stars\r\n\t{\r\n\t\topacity: 1;\r\n\t\tbackground-image: linear-gradient( to bottom, rgba( 120, 255, 210, 0.18 ) 0%, rgba( 120, 255, 210, 0 ) 70% );\r\n\t}\r\n\t.scenery\r\n\t{\r\n\t\topacity: 1;\r\n\t\tbackground-image: linear-gradient( to top, rgba( 63, 214, 160, 0.3 ) 0%, rgba( 63, 214, 160, 0 ) 45% );\r\n\t}\r\n}\r\n\r\n// ---- Volcanic: warm rock, water comes up boiling\r\n\r\n.scene-volcanic\r\n{\r\n\t.sky { background-image: linear-gradient( to bottom, #120404 0%, #1a0806 40%, #7d2412 78%, #e2542a 100% ); }\r\n\t.sun { background-color: #ff8a4a; box-shadow: 0px 0px 130px 46px rgba( 226, 84, 42, 0.5 ); }\r\n\t.dune.far { background-color: #3a1410; }\r\n\t.dune.near { background-color: #2a0d0a; }\r\n\t.floor { background-color: #1c0806; }\r\n\t.scenery\r\n\t{\r\n\t\topacity: 1;\r\n\t\tbackground-image: linear-gradient( to top, rgba( 226, 84, 42, 0.45 ) 0%, rgba( 226, 84, 42, 0 ) 40% );\r\n\t}\r\n}\r\n\r\n// ---- Crystal Cavern: deep enough that the walls glow\r\n\r\n.scene-crystal\r\n{\r\n\t.sky { background-image: linear-gradient( to bottom, #0a0620 0%, #160d33 40%, #3f2a70 76%, #7f6ae0 100% ); }\r\n\t.sun { background-color: #cfc2ff; box-shadow: 0px 0px 120px 42px rgba( 160, 140, 255, 0.45 ); }\r\n\t.dune.far { background-color: #2e2059; }\r\n\t.dune.near { background-color: #241947; }\r\n\t.floor { background-color: #170f2e; }\r\n\t.stars\r\n\t{\r\n\t\topacity: 1;\r\n\t\tbackground-image: linear-gradient( to bottom, rgba( 190, 170, 255, 0.2 ) 0%, rgba( 190, 170, 255, 0 ) 70% );\r\n\t}\r\n}\r\n\r\n// ---------------------------------------------------------------- new pump\r\n\r\n// Buying a pump is the reward the whole loop points at, and it used to be a line of\r\n// toast text. Two and a half seconds of the screen stopping to say so instead.\r\n.celebrate\r\n{\r\n\tposition: absolute;\r\n\ttop: 0;\r\n\tleft: 0;\r\n\twidth: 100%;\r\n\theight: 100%;\r\n\talign-items: center;\r\n\tjustify-content: center;\r\n\tpointer-events: none;\r\n\tanimation: celebrate-fade 2.6s ease-out;\r\n\r\n\t.celebrate-ring\r\n\t{\r\n\t\tposition: absolute;\r\n\t\tleft: 50%;\r\n\t\ttop: 50%;\r\n\t\tmargin-left: -210px;\r\n\t\tmargin-top: -210px;\r\n\t\twidth: 420px;\r\n\t\theight: 420px;\r\n\t\tborder-radius: 100%;\r\n\t\tborder: 8px solid rgba( 255, 226, 150, 0.85 );\r\n\t\tanimation: celebrate-ring 1.1s ease-out;\r\n\r\n\t\t\u0026.two\r\n\t\t{\r\n\t\t\tmargin-left: -130px;\r\n\t\t\tmargin-top: -130px;\r\n\t\t\twidth: 260px;\r\n\t\t\theight: 260px;\r\n\t\t\tborder-width: 5px;\r\n\t\t\tborder-color: rgba( 255, 255, 255, 0.8 );\r\n\t\t\tanimation: celebrate-ring 1.5s ease-out;\r\n\t\t}\r\n\t}\r\n\r\n\t.celebrate-card\r\n\t{\r\n\t\tflex-direction: column;\r\n\t\talign-items: center;\r\n\t\tbackground-color: rgba( 16, 20, 30, 0.86 );\r\n\t\tborder: 3px solid $gold;\r\n\t\tborder-radius: 24px;\r\n\t\tpadding: 20px 46px;\r\n\t\tanimation: celebrate-card 2.6s ease-out;\r\n\t}\r\n\r\n\t.celebrate-kicker\r\n\t{\r\n\t\tfont-size: 14px;\r\n\t\tletter-spacing: 5px;\r\n\t\tcolor: $gold;\r\n\t\tfont-weight: 700;\r\n\t}\r\n\r\n\t.celebrate-name\r\n\t{\r\n\t\tfont-size: 46px;\r\n\t\tfont-weight: 800;\r\n\t\tmargin-top: 2px;\r\n\t\ttext-shadow: 0px 3px 0px rgba( 0, 0, 0, 0.45 );\r\n\t}\r\n\r\n\t.celebrate-sub\r\n\t{\r\n\t\tfont-size: 16px;\r\n\t\topacity: 0.8;\r\n\t\tmargin-top: 2px;\r\n\t}\r\n}\r\n\r\n@keyframes celebrate-fade\r\n{\r\n\t0% { opacity: 1; }\r\n\t80% { opacity: 1; }\r\n\t100% { opacity: 0; }\r\n}\r\n\r\n@keyframes celebrate-ring\r\n{\r\n\t0% { transform: scale( 0.2 ); opacity: 0.95; }\r\n\t100% { transform: scale( 1.6 ); opacity: 0; }\r\n}\r\n\r\n@keyframes celebrate-card\r\n{\r\n\t0% { transform: scale( 0.6 ); opacity: 0; }\r\n\t9% { transform: scale( 1.08 ); opacity: 1; }\r\n\t16% { transform: scale( 1 ); opacity: 1; }\r\n\t86% { transform: scale( 1 ); opacity: 1; }\r\n\t100% { transform: scale( 1 ); opacity: 0; }\r\n}\r\n\r\n// ---------------------------------------------------------------- scene weather\r\n\r\n// The ambient layers are shared markup, so each scene only has to say what its own\r\n// air is doing. Dawn is the baseline: clouds, a little haze off the sand.\r\n\r\n.scene-dawn\r\n{\r\n\t.shimmer { opacity: 0.7; }\r\n}\r\n\r\n.scene-dusk\r\n{\r\n\t.cloud { background-color: rgba( 120, 70, 90, 0.5 ); box-shadow: 0px 0px 50px 24px rgba( 120, 70, 90, 0.3 ); }\r\n\t.mote { background-color: rgba( 255, 190, 190, 0.4 ); }\r\n\t.shimmer { opacity: 0.25; }\r\n}\r\n\r\n.scene-oasis\r\n{\r\n\t.cloud { background-color: rgba( 255, 255, 255, 0.7 ); }\r\n\t// Seeds and pollen rather than sand, drifting rather than blowing through.\r\n\t.mote { background-color: rgba( 220, 255, 190, 0.7 ); }\r\n\t.shimmer { opacity: 0; }\r\n}\r\n\r\n.scene-neon\r\n{\r\n\t.cloud { background-color: rgba( 255, 120, 190, 0.28 ); box-shadow: 0px 0px 60px 30px rgba( 255, 95, 162, 0.22 ); }\r\n\t.mote { background-color: rgba( 255, 150, 220, 0.75 ); }\r\n\t.shimmer { opacity: 0.4; }\r\n}\r\n\r\n// Enough dust in the air that the clouds have gone and the haze has taken over.\r\n.scene-sandstorm\r\n{\r\n\t.cloud { background-color: rgba( 220, 190, 140, 0.4 ); box-shadow: 0px 0px 70px 40px rgba( 200, 160, 90, 0.3 ); }\r\n\t.mote { background-color: rgba( 255, 230, 180, 0.85 ); width: 7px; height: 7px; }\r\n\t.shimmer { opacity: 1; }\r\n}\r\n\r\n.scene-golden\r\n{\r\n\t.cloud { background-color: rgba( 255, 210, 180, 0.55 ); box-shadow: 0px 0px 60px 26px rgba( 255, 170, 120, 0.35 ); }\r\n\t.mote { background-color: rgba( 255, 220, 170, 0.7 ); }\r\n\t.shimmer { opacity: 0.6; }\r\n}\r\n\r\n// No cloud and no wind - the motes hold still enough to read as stars.\r\n.scene-starfield\r\n{\r\n\t.clouds { opacity: 0; }\r\n\t.mote { background-color: rgba( 235, 240, 255, 0.9 ); }\r\n\t.shimmer { opacity: 0; }\r\n}\r\n\r\n.scene-aurora\r\n{\r\n\t.cloud { background-color: rgba( 120, 255, 210, 0.2 ); box-shadow: 0px 0px 70px 34px rgba( 100, 230, 190, 0.22 ); }\r\n\t.mote { background-color: rgba( 235, 255, 250, 0.85 ); width: 6px; height: 6px; }\r\n\t.shimmer { opacity: 0.3; }\r\n}\r\n\r\n// Embers, and air you can see moving.\r\n.scene-volcanic\r\n{\r\n\t.cloud { background-color: rgba( 90, 40, 30, 0.5 ); box-shadow: 0px 0px 70px 34px rgba( 226, 84, 42, 0.25 ); }\r\n\t.mote { background-color: rgba( 255, 160, 90, 0.9 ); box-shadow: 0px 0px 8px 2px rgba( 255, 120, 50, 0.6 ); }\r\n\t.shimmer { opacity: 1; }\r\n}\r\n\r\n.scene-crystal\r\n{\r\n\t.clouds { opacity: 0; }\r\n\t.mote { background-color: rgba( 210, 190, 255, 0.85 ); box-shadow: 0px 0px 8px 2px rgba( 170, 140, 255, 0.5 ); }\r\n\t.shimmer { opacity: 0.35; }\r\n}\r\n"},{"Ident":"fpkreastudios.desertpump","Path":"Game/UpgradeTrack.cs","FileName":"UpgradeTrack.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":343538,"IsPrivate":false,"Code":"namespace DesertPump;\r\n\r\npublic enum UpgradeKind\r\n{\r\n\t/// \u003Csummary\u003EBigger tank, so you sell every few minutes instead of every few seconds.\u003C/summary\u003E\r\n\tStorage,\r\n\r\n\t/// \u003Csummary\u003EMore litres per pump.\u003C/summary\u003E\r\n\tPower,\r\n\r\n\t/// \u003Csummary\u003EMore coins per litre.\u003C/summary\u003E\r\n\tValue,\r\n}\r\n\r\n/// \u003Csummary\u003E\r\n/// A shop upgrade you can buy over and over, each level costing more than the last.\r\n/// \u003C/summary\u003E\r\n/// \u003Cremarks\u003E\r\n/// Cost growth deliberately outruns effect growth by a wide margin. A track where the\r\n/// two are close never stops paying for itself, and the economy runs away - the first\r\n/// pass at this balance finished the whole game in two minutes for exactly that reason.\r\n/// \u003C/remarks\u003E\r\npublic sealed class UpgradeTrack\r\n{\r\n\tpublic UpgradeKind Kind { get; init; }\r\n\tpublic string Name { get; init; }\r\n\tpublic string Description { get; init; }\r\n\r\n\t/// \u003Csummary\u003EMaterial icon name.\u003C/summary\u003E\r\n\tpublic string Icon { get; init; }\r\n\r\n\tpublic double BaseCost { get; init; }\r\n\tpublic double CostGrowth { get; init; }\r\n\r\n\t/// \u003Csummary\u003EWhat one level multiplies its stat by.\u003C/summary\u003E\r\n\tpublic float EffectGrowth { get; init; }\r\n\r\n\tpublic int MaxLevel { get; init; }\r\n\r\n\tpublic double CostAt( int level ) =\u003E BaseCost * Math.Pow( CostGrowth, level );\r\n\r\n\tpublic float MultiplierAt( int level ) =\u003E MathF.Pow( EffectGrowth, level );\r\n\r\n\t/// \u003Csummary\u003E\u0022\u002B12%\u0022 - what the next level buys you.\u003C/summary\u003E\r\n\tpublic string PerLevelText =\u003E $\u0022\u002B{(EffectGrowth - 1f) * 100f:0}%\u0022;\r\n\r\n\t/// \u003Csummary\u003ERe-read on hotload so balance edits apply without restarting - see PumpTier.All.\u003C/summary\u003E\r\n\t[SkipHotload]\r\n\tpublic static readonly UpgradeTrack[] All = new UpgradeTrack[]\r\n\t{\r\n\t\tnew()\r\n\t\t{\r\n\t\t\tKind = UpgradeKind.Storage,\r\n\t\t\tName = \u0022Water Tank\u0022,\r\n\t\t\tDescription = \u0022Holds more water, so you can walk away between sales.\u0022,\r\n\t\t\tIcon = \u0022water_drop\u0022,\r\n\t\t\tBaseCost = 150,\r\n\t\t\tCostGrowth = 1.55,\r\n\t\t\tEffectGrowth = 1.12f,\r\n\t\t\tMaxLevel = 200\r\n\t\t},\r\n\t\tnew()\r\n\t\t{\r\n\t\t\tKind = UpgradeKind.Power,\r\n\t\t\tName = \u0022Pump Power\u0022,\r\n\t\t\tDescription = \u0022Every pump pulls up more water.\u0022,\r\n\t\t\tIcon = \u0022fitness_center\u0022,\r\n\t\t\tBaseCost = 250,\r\n\t\t\tCostGrowth = 1.60,\r\n\t\t\tEffectGrowth = 1.06f,\r\n\t\t\tMaxLevel = 200\r\n\t\t},\r\n\t\tnew()\r\n\t\t{\r\n\t\t\tKind = UpgradeKind.Value,\r\n\t\t\tName = \u0022Market Contacts\u0022,\r\n\t\t\tDescription = \u0022People pay better for the same water.\u0022,\r\n\t\t\tIcon = \u0022handshake\u0022,\r\n\t\t\tBaseCost = 400,\r\n\t\t\tCostGrowth = 1.68,\r\n\t\t\tEffectGrowth = 1.05f,\r\n\t\t\tMaxLevel = 200\r\n\t\t},\r\n\t};\r\n\r\n\tpublic static UpgradeTrack Get( UpgradeKind kind ) =\u003E All[(int)kind];\r\n}\r\n"}]}