{"TotalCount":91,"Files":[{"Ident":"dogecoin.nochillquarium","Path":"PixelFx.cs","FileName":"PixelFx.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"namespace NoChillquarium;\n\npublic enum PixelFxLayer\n{\n\tTank,\n\tShop\n}\n\npublic sealed class PixelSpark\n{\n\tpublic float X;\n\tpublic float Y;\n\tpublic float Vx;\n\tpublic float Vy;\n\tpublic float Life;\n\tpublic float MaxLife;\n\tpublic int Size;\n\tpublic string Color;\n\tpublic PixelFxLayer Layer;\n}\n\n/// \u003Csummary\u003E\n/// Hard-pixel spark bits. GBA confetti \u2014 squares, integer positions, no bloom.\n/// Shop layer sits on the unfold card; tank layer sits in the glass.\n/// \u003C/summary\u003E\npublic static class PixelFx\n{\n\tconst int Cap = 64;\n\tstatic readonly Random _rng = new();\n\tstatic readonly List\u003CPixelSpark\u003E _sparks = new();\n\tstatic int _version;\n\n\t/// \u003Csummary\u003EEvenly spaced scanline tops (percent). Overlay, not a shader.\u003C/summary\u003E\n\tpublic static readonly string[] ScanTops =\n\t{\n\t\t\u00220%\u0022, \u00226%\u0022, \u002212%\u0022, \u002218%\u0022, \u002225%\u0022, \u002231%\u0022, \u002237%\u0022, \u002243%\u0022,\n\t\t\u002250%\u0022, \u002256%\u0022, \u002262%\u0022, \u002268%\u0022, \u002275%\u0022, \u002281%\u0022, \u002287%\u0022, \u002293%\u0022\n\t};\n\n\tpublic static int Version =\u003E _version;\n\n\tpublic static IEnumerable\u003CPixelSpark\u003E TankSparks\n\t{\n\t\tget\n\t\t{\n\t\t\tforeach ( var s in _sparks )\n\t\t\t{\n\t\t\t\tif ( s.Layer == PixelFxLayer.Tank )\n\t\t\t\t\tyield return s;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic static IEnumerable\u003CPixelSpark\u003E ShopSparks\n\t{\n\t\tget\n\t\t{\n\t\t\tforeach ( var s in _sparks )\n\t\t\t{\n\t\t\t\tif ( s.Layer == PixelFxLayer.Shop )\n\t\t\t\t\tyield return s;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic static void Clear()\n\t{\n\t\t_sparks.Clear();\n\t\t_version\u002B\u002B;\n\t}\n\n\tpublic static void Tick( float dt )\n\t{\n\t\tif ( _sparks.Count == 0 )\n\t\t\treturn;\n\t\tif ( dt \u003C= 0f )\n\t\t\treturn;\n\t\tif ( dt \u003E 0.05f )\n\t\t\tdt = 0.05f;\n\n\t\tfor ( var i = _sparks.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar s = _sparks[i];\n\t\t\ts.Life -= dt;\n\t\t\tif ( s.Life \u003C= 0f )\n\t\t\t{\n\t\t\t\t_sparks.RemoveAt( i );\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\ts.X \u002B= s.Vx * dt;\n\t\t\ts.Y \u002B= s.Vy * dt;\n\t\t\ts.Vy \u002B= 90f * dt;\n\t\t}\n\t\t_version\u002B\u002B;\n\t}\n\n\t/// \u003Csummary\u003EPack-open / hatch / fusion burst in rarity colors.\u003C/summary\u003E\n\tpublic static void BurstRarity( PixelFxLayer layer, float x, float y, FishRarity rarity )\n\t{\n\t\tvar count = rarity switch\n\t\t{\n\t\t\tFishRarity.Common =\u003E 4,\n\t\t\tFishRarity.Uncommon =\u003E 6,\n\t\t\tFishRarity.Rare =\u003E 9,\n\t\t\tFishRarity.PaintedRare =\u003E 12,\n\t\t\tFishRarity.Legendary =\u003E 16,\n\t\t\t_ =\u003E 20\n\t\t};\n\t\tif ( GameSettings.ReduceStrobe )\n\t\t\tcount = Math.Max( 3, count / 2 );\n\n\t\tvar spd = rarity \u003E= FishRarity.Legendary ? 140f : rarity \u003E= FishRarity.Rare ? 110f : 80f;\n\t\tBurst( layer, x, y, count, ColorsFor( rarity ), spd, size: rarity \u003E= FishRarity.PaintedRare ? 4 : 3 );\n\t}\n\n\tpublic static void Burst( PixelFxLayer layer, float x, float y, int count, string[] colors, float speed, int size = 3 )\n\t{\n\t\tif ( colors is null || colors.Length == 0 )\n\t\t\treturn;\n\t\tcount = Math.Clamp( count, 1, 28 );\n\t\tsize = Math.Clamp( size, 2, 5 );\n\t\tfor ( var i = 0; i \u003C count; i\u002B\u002B )\n\t\t{\n\t\t\tif ( _sparks.Count \u003E= Cap )\n\t\t\t\t_sparks.RemoveAt( 0 );\n\t\t\tvar ang = (float)(_rng.NextDouble() * Math.PI * 2.0);\n\t\t\tvar spd = speed * (0.45f \u002B (float)_rng.NextDouble() * 0.7f);\n\t\t\tvar life = 0.22f \u002B (float)_rng.NextDouble() * 0.28f;\n\t\t\t_sparks.Add( new PixelSpark\n\t\t\t{\n\t\t\t\tX = x \u002B (float)(_rng.NextDouble() * 6.0 - 3.0),\n\t\t\t\tY = y \u002B (float)(_rng.NextDouble() * 6.0 - 3.0),\n\t\t\t\tVx = MathF.Cos( ang ) * spd,\n\t\t\t\tVy = MathF.Sin( ang ) * spd - 28f,\n\t\t\t\tLife = life,\n\t\t\t\tMaxLife = life,\n\t\t\t\tSize = size,\n\t\t\t\tColor = colors[i % colors.Length],\n\t\t\t\tLayer = layer\n\t\t\t} );\n\t\t}\n\t\t_version\u002B\u002B;\n\t}\n\n\tstatic readonly string[] CommonCols = { \u0022#8a9a90\u0022, \u0022#c8d0c8\u0022 };\n\tstatic readonly string[] UncommonCols = { \u0022#7ad8b0\u0022, \u0022#d0fff0\u0022 };\n\tstatic readonly string[] RareCols = { \u0022#7ab8ff\u0022, \u0022#d0e8ff\u0022 };\n\tstatic readonly string[] PaintedCols = { \u0022#f090e0\u0022, \u0022#40e0ff\u0022, \u0022#ff60c8\u0022 };\n\tstatic readonly string[] GoldenCols = { \u0022#ffd24a\u0022, \u0022#ffe8a0\u0022, \u0022#c8a020\u0022 };\n\tstatic readonly string[] RainbowCols = { \u0022#ff4040\u0022, \u0022#ffd24a\u0022, \u0022#40d070\u0022, \u0022#4080ff\u0022, \u0022#d060ff\u0022 };\n\n\tstatic string[] ColorsFor( FishRarity r ) =\u003E r switch\n\t{\n\t\tFishRarity.Uncommon =\u003E UncommonCols,\n\t\tFishRarity.Rare =\u003E RareCols,\n\t\tFishRarity.PaintedRare =\u003E PaintedCols,\n\t\tFishRarity.Legendary =\u003E GoldenCols,\n\t\tFishRarity.Mythical =\u003E RainbowCols,\n\t\t_ =\u003E CommonCols\n\t};\n}\n"},{"Ident":"dogecoin.nochillquarium","Path":"TankSim.cs","FileName":"TankSim.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"namespace NoChillquarium;\r\n\r\n/// \u003Csummary\u003E\r\n/// Dual tanks (fresh \u002B salt), swim AI, wrong-water sickness / meth survival.\r\n/// \u003C/summary\u003E\r\npublic static class TankSim\r\n{\r\n\t// Sized for ~1920\u00D71080 play \u2014 roomy tank without edge-to-edge chrome.\r\n\tpublic const float Width = 1280f;\r\n\tpublic const float Height = 620f;\r\n\t/// \u003Csummary\u003EStarter glass is tight \u2014 capacity pressure is the early game.\u003C/summary\u003E\r\n\tpublic const int DefaultCapacity = 4;\r\n\tpublic const float SickDeathSeconds = 10f;\r\n\t/// \u003Csummary\u003ESecond tank is a midgame buy \u2014 reachable same session, not a wall.\u003C/summary\u003E\r\n\tpublic const double SaltTankCost = 220;\r\n\r\n\tstatic readonly Random _rng = new();\r\n\tstatic readonly TankSlot _fresh = new()\r\n\t{\r\n\t\tWater = WaterType.Fresh,\r\n\t\tName = HabitatDef.StarterFresh.Name,\r\n\t\tCapacity = DefaultCapacity,\r\n\t\tUnlocked = true,\r\n\t\tHabitatId = HabitatDef.StarterFresh.Id\r\n\t};\r\n\tstatic readonly TankSlot _salt = new()\r\n\t{\r\n\t\tWater = WaterType.Salt,\r\n\t\tName = HabitatDef.StarterSalt.Name,\r\n\t\tCapacity = DefaultCapacity,\r\n\t\tUnlocked = false,\r\n\t\tHabitatId = HabitatDef.StarterSalt.Id\r\n\t};\r\n\r\n\tstatic TankSlot _active = _fresh;\r\n\tstatic string _banner = \u0022\u0022;\r\n\tstatic float _bannerTimer;\r\n\tstatic int _bannerVersion;\r\n\tstatic int _abominationsCreated;\r\n\tstatic int _fishDeaths;\r\n\t/// \u003Csummary\u003E\r\n\t/// Fish instance id with the context menu open. Soft-holds thrash so buttons stay usable\r\n\t/// (especially meth) without killing the high visuals.\r\n\t/// \u003C/summary\u003E\r\n\tstatic string _menuFocusId;\r\n\r\n\tpublic static float WidthPx =\u003E Width;\r\n\tpublic static float HeightPx =\u003E Height;\r\n\r\n\tpublic static string TankName =\u003E _active.Name;\r\n\tpublic static WaterType Water =\u003E _active.Water;\r\n\tpublic static int Capacity =\u003E _active.Capacity;\r\n\tpublic static IReadOnlyList\u003CFishActor\u003E Fish =\u003E _active.Fish;\r\n\tpublic static int FishCount =\u003E _active.Fish.Count;\r\n\t/// \u003Csummary\u003ELiving fish across every unlocked tank (story / census totals).\u003C/summary\u003E\r\n\tpublic static int TotalFishCount =\u003E\r\n\t\t_fresh.Fish.Count \u002B ( _salt.Unlocked ? _salt.Fish.Count : 0 );\r\n\r\n\t/// \u003Csummary\u003EAny fusion abom / decor-hybrid alive in an unlocked tank.\u003C/summary\u003E\r\n\tpublic static bool HasAnyMonster\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tforeach ( var f in _fresh.Fish )\r\n\t\t\t{\r\n\t\t\t\tif ( f?.Species?.IsMonster == true )\r\n\t\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t\tif ( _salt.Unlocked )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var f in _salt.Fish )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( f?.Species?.IsMonster == true )\r\n\t\t\t\t\t\treturn true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ELifetime successful combines / evolves this save.\u003C/summary\u003E\r\n\tpublic static int AbominationsCreated =\u003E _abominationsCreated;\r\n\t/// \u003Csummary\u003ELifetime salinity / neglect deaths this save.\u003C/summary\u003E\r\n\tpublic static int FishDeaths =\u003E _fishDeaths;\r\n\r\n\t/// \u003Csummary\u003EInstance id of fish currently showing the fish menu (null/empty = none).\u003C/summary\u003E\r\n\tpublic static string MenuFocusId\r\n\t{\r\n\t\tget =\u003E _menuFocusId;\r\n\t\tset =\u003E _menuFocusId = string.IsNullOrEmpty( value ) ? null : value;\r\n\t}\r\n\r\n\tpublic static bool IsMenuFocused( string instanceId ) =\u003E\r\n\t\t!string.IsNullOrEmpty( _menuFocusId )\r\n\t\t\u0026\u0026 string.Equals( _menuFocusId, instanceId, StringComparison.Ordinal );\r\n\r\n\t/// \u003Csummary\u003EExternal systems (sharks, etc.) when a living fish is lost to the world.\u003C/summary\u003E\r\n\tpublic static void NotifyFishDied()\r\n\t{\r\n\t\t_fishDeaths\u002B\u002B;\r\n\t\tif ( _fishDeaths == 1 )\r\n\t\t\tGrandmaReact.OnFirstDeath();\r\n\t\tJanitorPeep.On( \u0022dead\u0022 );\r\n\t\tAchievementSystem.Unlock( \u0022dead\u0022 );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EWrong-water death punchline \u2014 Grandma claims the corpse (dry love).\u003C/summary\u003E\r\n\tpublic static string GrandmaAteLine( string fishName )\r\n\t{\r\n\t\tif ( string.IsNullOrWhiteSpace( fishName ) )\r\n\t\t\tfishName = \u0022That fish\u0022;\r\n\t\t// Rotate a few lines so repeat deaths stay funny.\r\n\t\treturn (_fishDeaths % 4) switch\r\n\t\t{\r\n\t\t\t1 =\u003E $\u0022{fishName} hit the glass. Grandma: \\\u0022Supper. Still love you. Your fault. Wipe anyway.\\\u0022\u0022,\r\n\t\t\t2 =\u003E $\u0022Grandma plated {fishName}. Wrong water. Kiss on the forehead. Your fault. The kiss is not a pardon.\u0022,\r\n\t\t\t3 =\u003E $\u0022{fishName} is gone. Fridge full. Grandma full of opinions. Your fault. She said it nicer than this.\u0022,\r\n\t\t\t_ =\u003E $\u0022Wrong water. Grandma ate {fishName}. Hugs later. Blame now. That\u0027s the religion.\u0022\r\n\t\t};\r\n\t}\r\n\r\n\tpublic static int FrameId { get; private set; }\r\n\r\n\t/// \u003Csummary\u003ESpecies counts in the tank you\u0027re looking at (for HUD census).\u003C/summary\u003E\r\n\tpublic static IReadOnlyList\u003C(FishSpecies Species, int Count)\u003E CensusActive()\r\n\t{\r\n\t\tif ( _active.Fish.Count == 0 )\r\n\t\t\treturn Array.Empty\u003C(FishSpecies, int)\u003E();\r\n\r\n\t\tvar map = new Dictionary\u003Cstring, (FishSpecies Sp, int N)\u003E( StringComparer.OrdinalIgnoreCase );\r\n\t\tforeach ( var f in _active.Fish )\r\n\t\t{\r\n\t\t\tif ( f?.Species is null || f.OnAdventure )\r\n\t\t\t\tcontinue;\r\n\t\t\tvar id = f.Species.Id;\r\n\t\t\tif ( map.TryGetValue( id, out var e ) )\r\n\t\t\t\tmap[id] = (e.Sp, e.N \u002B 1);\r\n\t\t\telse\r\n\t\t\t\tmap[id] = (f.Species, 1);\r\n\t\t}\r\n\r\n\t\treturn map.Values\r\n\t\t\t.OrderByDescending( x =\u003E x.N )\r\n\t\t\t.ThenBy( x =\u003E x.Sp.Name, StringComparer.OrdinalIgnoreCase )\r\n\t\t\t.Select( x =\u003E (x.Sp, x.N) )\r\n\t\t\t.ToList();\r\n\t}\r\n\tpublic static bool IsActive { get; private set; }\r\n\tpublic static bool SaltUnlocked =\u003E _salt.Unlocked;\r\n\t/// \u003Csummary\u003ETrue when fresh \u002B salt are both available (move between tanks).\u003C/summary\u003E\r\n\tpublic static bool HasMultipleTanks =\u003E _fresh.Unlocked \u0026\u0026 _salt.Unlocked;\r\n\tpublic static bool ViewingSalt =\u003E _active.Water == WaterType.Salt;\r\n\t/// \u003Csummary\u003ELabel for the other tank when dual tanks are owned.\u003C/summary\u003E\r\n\tpublic static string OtherTankLabel =\u003E ViewingSalt ? \u0022Fresh\u0022 : \u0022Salt\u0022;\r\n\tpublic static string Banner =\u003E _bannerTimer \u003E 0f ? _banner : \u0022\u0022;\r\n\tpublic static int BannerVersion =\u003E _bannerVersion;\r\n\tpublic static HabitatDef ActiveHabitat =\u003E _active.Habitat;\r\n\tpublic static string HabitatCssClass =\u003E ActiveHabitat.CssClass ?? \u0022hab-glass\u0022;\r\n\tpublic static string HabitatId =\u003E _active.HabitatId ?? ActiveHabitat.Id;\r\n\tpublic static int HabitatRank =\u003E _active.HabitatRank;\r\n\tpublic static HabitatDef NextHabitat =\u003E HabitatDef.NextFor( _active.Water, _active.HabitatRank );\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Idle income from \u003Cb\u003Eall\u003C/b\u003E unlocked tanks \u2014 switching tabs is only a camera.\r\n\t/// \u003C/summary\u003E\r\n\tpublic static double IncomePerSecond\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tdouble sum = 0;\r\n\t\t\tsum \u002B= SlotIncome( _fresh );\r\n\t\t\tif ( _salt.Unlocked )\r\n\t\t\t\tsum \u002B= SlotIncome( _salt );\r\n\t\t\treturn sum;\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EIncome for the tank you\u0027re looking at only (HUD flavor).\u003C/summary\u003E\r\n\tpublic static double ActiveTankIncomePerSecond =\u003E SlotIncome( _active );\r\n\r\n\tstatic double SlotIncome( TankSlot slot )\r\n\t{\r\n\t\tif ( slot is null || !slot.Unlocked )\r\n\t\t\treturn 0;\r\n\r\n\t\tdouble sum = 0;\r\n\t\tforeach ( var fish in slot.Fish )\r\n\t\t\tsum \u002B= fish.EffectiveIncomePerSecond;\r\n\r\n\t\tforeach ( var piece in slot.Decor )\r\n\t\t\tsum \u002B= piece.Def.ComfortBonus;\r\n\r\n\t\tvar backdrop = DecorDef.FindBackdrop( slot.BackdropId );\r\n\t\tif ( backdrop is not null )\r\n\t\t\tsum \u002B= backdrop.ComfortBonus;\r\n\r\n\t\tvar gravel = DecorDef.FindGravel( slot.GravelId );\r\n\t\tif ( gravel is not null )\r\n\t\t\tsum \u002B= gravel.ComfortBonus;\r\n\r\n\t\treturn sum * slot.IncomeMult;\r\n\t}\r\n\r\n\tpublic static IReadOnlyList\u003CDecorPiece\u003E ActiveDecor =\u003E _active.Decor;\r\n\tpublic static int DecorCount =\u003E _active.Decor.Count;\r\n\tpublic static int MaxDecor =\u003E _active.MaxDecor;\r\n\tpublic static string ActiveBackdropId =\u003E _active.BackdropId;\r\n\tpublic static DecorDef ActiveBackdrop =\u003E DecorDef.FindBackdrop( _active.BackdropId );\r\n\tpublic static bool HasBackdrop =\u003E ActiveBackdrop is not null \u0026\u0026 ActiveBackdrop.HasSprite;\r\n\tpublic static string ActiveGravelId =\u003E _active.GravelId;\r\n\tpublic static DecorDef ActiveGravel =\u003E DecorDef.FindGravel( _active.GravelId );\r\n\tpublic static bool HasGravel =\u003E ActiveGravel is not null \u0026\u0026 ActiveGravel.HasSprite;\r\n\t/// \u003Csummary\u003EAlways a floor sprite \u2014 stock river gravel if they haven\u0027t bought a bag.\u003C/summary\u003E\r\n\tpublic static string GravelSpriteId =\u003E\r\n\t\tHasGravel ? ActiveGravel.SpriteId\r\n\t\t\t: (SpriteCatalog.Has( \u0022gravel_brown\u0022 ) ? \u0022gravel_brown\u0022 : \u0022\u0022);\r\n\r\n\tpublic static int AddictedFishCount\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tvar n = 0;\r\n\t\t\tforeach ( var f in _fresh.Fish )\r\n\t\t\t\tif ( f.IsAddicted ) n\u002B\u002B;\r\n\t\t\tif ( _salt.Unlocked )\r\n\t\t\t\tforeach ( var f in _salt.Fish )\r\n\t\t\t\t\tif ( f.IsAddicted ) n\u002B\u002B;\r\n\t\t\treturn n;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static int WithdrawingFishCount\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tvar n = 0;\r\n\t\t\tforeach ( var f in _active.Fish )\r\n\t\t\t\tif ( f.InWithdrawal ) n\u002B\u002B;\r\n\t\t\treturn n;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static void StartNewGame()\r\n\t{\r\n\t\t_fresh.Fish.Clear();\r\n\t\t_salt.Fish.Clear();\r\n\t\t_fresh.Decor.Clear();\r\n\t\t_salt.Decor.Clear();\r\n\t\t_fresh.ResetToStarter();\r\n\t\t_salt.ResetToStarter();\r\n\t\t_fresh.Unlocked = true;\r\n\t\t_salt.Unlocked = false;\r\n\t\t_active = _fresh;\r\n\t\tIsActive = true;\r\n\t\tFrameId = 0;\r\n\t\t_abominationsCreated = 0;\r\n\t\t_fishDeaths = 0;\r\n\t\tClearBanner();\r\n\r\n\t\t// No tank until Grandma gifts it.\r\n\t\tShowBanner( \u0022Suspended. Walk home. Chin up.\u0022 );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Grandma\u0027s fish_tank gift \u2014 two goldfish \u002B a free plant on the porch table.\r\n\t/// Safe to call more than once; only fills an empty starter tank.\r\n\t/// \u003C/summary\u003E\r\n\tpublic static void GrantTrailerLotTank()\r\n\t{\r\n\t\tif ( !IsActive )\r\n\t\t{\r\n\t\t\t// Gift can fire while sim is flagged active; never soft-fail silent.\r\n\t\t\tIsActive = true;\r\n\t\t}\r\n\r\n\t\t// Always deliver into the porch (fresh) tank, not whatever view is active.\r\n\t\t_active = _fresh;\r\n\t\t_fresh.Unlocked = true;\r\n\t\tif ( _fresh.Capacity \u003C DefaultCapacity )\r\n\t\t\t_fresh.Capacity = DefaultCapacity;\r\n\r\n\t\t// Already has fish (continue / re-claim) \u2014 still ensure a free plant if barren.\r\n\t\tif ( _fresh.Fish.Count \u003E 0 )\r\n\t\t{\r\n\t\t\tif ( _fresh.Decor.Count == 0 )\r\n\t\t\t{\r\n\t\t\t\tTryPlaceDecor( DecorDef.Plant, announce: false );\r\n\t\t\t\tFrameId\u002B\u002B;\r\n\t\t\t}\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Start juvenile \u2014 babies that print crumbs make the first minute feel broken.\r\n\t\tvar a = SpawnInto( _fresh, FishSpecies.Goldfish, FishRarity.Common, ageSeconds: 95f );\r\n\t\tvar b = SpawnInto( _fresh, FishSpecies.Goldfish, FishRarity.Common, ageSeconds: 150f );\r\n\t\tif ( _fresh.Decor.Count == 0 )\r\n\t\t\tTryPlaceDecor( DecorDef.Plant, announce: false );\r\n\r\n\t\tFrameId\u002B\u002B;\r\n\t\tif ( a \u0026\u0026 b )\r\n\t\t\tShowBanner( \u0022First tank. Two goldfish. Don\u0027t drown them.\u0022 );\r\n\t\telse if ( _fresh.Fish.Count \u003E 0 )\r\n\t\t\tShowBanner( \u0022First tank. Try not to kill the hostages.\u0022 );\r\n\t\telse\r\n\t\t{\r\n\t\t\tLog.Warning( $\u0022[NO-CHILLquarium] GrantTrailerLotTank failed \u2014 capacity={_fresh.Capacity}, fish={_fresh.Fish.Count}\u0022 );\r\n\t\t\tShowBanner( \u0022Tank\u0027s on the porch\u2026 but the goldfish slipped out. Try again?\u0022 );\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ESpawn into a specific slot (used by starter grant so we never miss _active).\u003C/summary\u003E\r\n\tstatic bool SpawnInto( TankSlot slot, FishSpecies species, FishRarity rarity, float ageSeconds ) =\u003E\r\n\t\tSpawnInto( slot, species, rarity, ageSeconds, x: null, y: null );\r\n\r\n\tstatic bool SpawnInto( TankSlot slot, FishSpecies species, FishRarity rarity, float ageSeconds, float? x, float? y )\r\n\t{\r\n\t\tif ( slot is null || species is null )\r\n\t\t\treturn false;\r\n\t\tif ( slot.Capacity \u003C 2 )\r\n\t\t\tslot.Capacity = DefaultCapacity;\r\n\t\tif ( slot.Fish.Count \u003E= slot.Capacity )\r\n\t\t\treturn false;\r\n\r\n\t\tvar prev = _active;\r\n\t\t_active = slot;\r\n\t\tvar ok = Spawn( species, rarity, ageSeconds );\r\n\t\tif ( ok \u0026\u0026 ( x.HasValue || y.HasValue ) \u0026\u0026 slot.Fish.Count \u003E 0 )\r\n\t\t{\r\n\t\t\tvar baby = slot.Fish[^1];\r\n\t\t\tif ( x.HasValue )\r\n\t\t\t\tbaby.X = Math.Clamp( x.Value, 8f, Width - 8f );\r\n\t\t\tif ( y.HasValue )\r\n\t\t\t\tbaby.Y = Math.Clamp( y.Value, 8f, Height - 8f );\r\n\t\t}\r\n\t\t_active = prev ?? _fresh;\r\n\t\treturn ok;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ESpawn at a tank-local point (egg hatch, scripted births).\u003C/summary\u003E\r\n\tpublic static bool SpawnAt( FishSpecies species, FishRarity rarity, float ageSeconds, float x, float y, TankSlot slot = null )\r\n\t{\r\n\t\tslot ??= _active;\r\n\t\treturn SpawnInto( slot, species, rarity, ageSeconds, x, y );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ECurrently viewed tank (null only if sim never set up).\u003C/summary\u003E\r\n\tpublic static TankSlot ActiveSlotOrNull =\u003E _active;\r\n\r\n\tpublic static FishActor FindFishInSlot( TankSlot slot, string instanceId )\r\n\t{\r\n\t\tif ( slot is null || string.IsNullOrEmpty( instanceId ) )\r\n\t\t\treturn null;\r\n\t\tforeach ( var f in slot.Fish )\r\n\t\t{\r\n\t\t\tif ( f.InstanceId == instanceId )\r\n\t\t\t\treturn f;\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\tpublic static void LoadFromSave( SaveData data )\r\n\t{\r\n\t\tBreedingSystem.Clear();\r\n\t\t_fresh.Fish.Clear();\r\n\t\t_salt.Fish.Clear();\r\n\t\t_fresh.Decor.Clear();\r\n\t\t_salt.Decor.Clear();\r\n\t\tFrameId = 0;\r\n\t\tClearBanner();\r\n\r\n\t\tif ( data is null )\r\n\t\t{\r\n\t\t\tIsActive = false;\r\n\t\t\t_abominationsCreated = 0;\r\n\t\t\t_fishDeaths = 0;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t_abominationsCreated = Math.Max( 0, data.AbominationsCreated );\r\n\t\t_fishDeaths = Math.Max( 0, data.FishDeaths );\r\n\r\n\t\t_fresh.Capacity = data.FreshCapacity \u003E 0 ? data.FreshCapacity : DefaultCapacity;\r\n\t\t_salt.Capacity = data.SaltCapacity \u003E 0 ? data.SaltCapacity : DefaultCapacity;\r\n\t\t_salt.Unlocked = data.SaltUnlocked;\r\n\t\tApplyHabitatFromSave( _fresh, data.FreshHabitatId, data.FreshTankName );\r\n\t\tApplyHabitatFromSave( _salt, data.SaltHabitatId, data.SaltTankName );\r\n\r\n\t\tLoadFishList( data.FreshFish, _fresh );\r\n\t\tLoadFishList( data.SaltFish, _salt );\r\n\t\tLoadDecorList( data.FreshDecor, _fresh );\r\n\t\tLoadDecorList( data.SaltDecor, _salt );\r\n\t\t_fresh.BackdropId = DecorDef.FindBackdrop( data.FreshBackdropId )?.Id;\r\n\t\t_salt.BackdropId = DecorDef.FindBackdrop( data.SaltBackdropId )?.Id;\r\n\t\t_fresh.GravelId = DecorDef.FindGravel( data.FreshGravelId )?.Id;\r\n\t\t_salt.GravelId = DecorDef.FindGravel( data.SaltGravelId )?.Id;\r\n\r\n\t\t// Back-compat: old saves only had one fish list \u002B Water string.\r\n\t\tif ( (_fresh.Fish.Count \u002B _salt.Fish.Count) == 0 \u0026\u0026 data.Fish is { Count: \u003E 0 } )\r\n\t\t{\r\n\t\t\tvar slot = data.Water == \u0022Salt\u0022 ? _salt : _fresh;\r\n\t\t\tif ( data.Water == \u0022Salt\u0022 )\r\n\t\t\t\t_salt.Unlocked = true;\r\n\t\t\tif ( data.Capacity \u003E 0 )\r\n\t\t\t\tslot.Capacity = data.Capacity;\r\n\t\t\tLoadFishList( data.Fish, slot );\r\n\t\t}\r\n\r\n\t\t_active = (data.ActiveWater == \u0022Salt\u0022 \u0026\u0026 _salt.Unlocked) ? _salt : _fresh;\r\n\t\tIsActive = _fresh.Fish.Count \u002B _salt.Fish.Count \u003E 0 || _fresh.Unlocked;\r\n\r\n\t\tif ( !IsActive )\r\n\t\t\treturn;\r\n\r\n\t\t// Old saves placed decor with tiny sizes / wrong Y \u2014 re-seat to current defs.\r\n\t\tReseatAllDecor();\r\n\t\tBreedingSystem.LoadEggs( data.TankEggs );\r\n\t}\r\n\r\n\tstatic void LoadFishList( List\u003CFishSaveData\u003E list, TankSlot slot )\r\n\t{\r\n\t\tif ( list is null )\r\n\t\t\treturn;\r\n\r\n\t\tforeach ( var entry in list )\r\n\t\t{\r\n\t\t\tvar species = FishSpecies.Find( entry.SpeciesId );\r\n\t\t\tif ( slot.Fish.Count \u003E= slot.Capacity )\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tvar actor = new FishActor\r\n\t\t\t{\r\n\t\t\t\tInstanceId = Guid.NewGuid().ToString( \u0022N\u0022 )[..8],\r\n\t\t\t\tSpecies = species,\r\n\t\t\t\tX = entry.X,\r\n\t\t\t\tY = entry.Y,\r\n\t\t\t\tVx = entry.Vx,\r\n\t\t\t\tVy = entry.Vy,\r\n\t\t\t\tBobPhase = (float)(_rng.NextDouble() * Math.PI * 2.0),\r\n\t\t\t\tBobSpeed = 2.2f \u002B (float)_rng.NextDouble() * 2.5f,\r\n\t\t\t\tBobAmp = 2.5f \u002B (float)_rng.NextDouble() * 3.5f,\r\n\t\t\t\tOnMeth = entry.OnMeth,\r\n\t\t\t\tMethTimer = entry.MethTimer \u003E 0 ? entry.MethTimer : (entry.OnMeth ? MethDurationSeconds : 0f),\r\n\t\t\t\tSickTimer = entry.SickTimer,\r\n\t\t\t\tAddiction = Math.Clamp( entry.Addiction, 0f, 100f ),\r\n\t\t\t\tWithdrawalTimer = Math.Max( 0f, entry.WithdrawalTimer ),\r\n\t\t\t\tEndurance = entry.Endurance \u003E 0 ? entry.Endurance : 3f,\r\n\t\t\t\tSpeedStat = entry.SpeedStat \u003E 0 ? entry.SpeedStat : 3f,\r\n\t\t\t\tAgility = entry.Agility \u003E 0 ? entry.Agility : 3f,\r\n\t\t\t\tJacked = entry.Jacked,\r\n\t\t\t\tJackedTimer = entry.JackedTimer,\r\n\t\t\t\tFat = Math.Clamp( entry.Fat, 0f, 100f ),\r\n\t\t\t\tStoned = entry.Stoned,\r\n\t\t\t\tStonedTimer = entry.Stoned\r\n\t\t\t\t\t? Math.Max( entry.StonedTimer, 1f )\r\n\t\t\t\t\t: Math.Max( 0f, entry.StonedTimer ),\r\n\t\t\t\tFightInjuryTimer = Math.Max( 0f, entry.FightInjuryTimer ),\r\n\t\t\t\t// Old saves: treat as adult commons so empires don\u0027t shrink to guppies.\r\n\t\t\t\tAgeSeconds = entry.AgeSeconds \u003E 0f\r\n\t\t\t\t\t? entry.AgeSeconds\r\n\t\t\t\t\t: FishActor.AdultAgeSeconds,\r\n\t\t\t\tPredationBulk = Math.Clamp( entry.PredationBulk, 0f, 100f ),\r\n\t\t\t\tFishEatenCount = Math.Max( 0, entry.FishEatenCount ),\r\n\t\t\t\tRarity = FishRarityInfo.FromSave( entry.Rarity ),\r\n\t\t\t\tPregnantTimer = Math.Max( 0f, entry.PregnantTimer ),\r\n\t\t\t\tMateCooldown = Math.Max( 0f, entry.MateCooldown )\r\n\t\t\t};\r\n\t\t\tif ( !string.IsNullOrWhiteSpace( entry.Nickname ) )\r\n\t\t\t\tactor.TrySetNickname( entry.Nickname );\r\n\t\t\tslot.Fish.Add( actor );\r\n\t\t}\r\n\t}\r\n\r\n\tstatic void LoadDecorList( List\u003CDecorSaveData\u003E list, TankSlot slot )\r\n\t{\r\n\t\tif ( list is null )\r\n\t\t\treturn;\r\n\r\n\t\tforeach ( var entry in list )\r\n\t\t{\r\n\t\t\tif ( slot.Decor.Count \u003E= slot.MaxDecor )\r\n\t\t\t\tbreak;\r\n\t\t\tif ( string.IsNullOrWhiteSpace( entry.DefId ) )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\t// Old saves shouldn\u0027t park a poster in the gravel list.\r\n\t\t\tvar maybe = DecorDef.Catalog.FirstOrDefault( d =\u003E d.Id == entry.DefId );\r\n\t\t\tif ( maybe is { IsBackdrop: true } )\r\n\t\t\t{\r\n\t\t\t\tslot.BackdropId = maybe.Id == DecorDef.BackdropNone.Id ? null : maybe.Id;\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tif ( maybe is { IsSubstrate: true } )\r\n\t\t\t{\r\n\t\t\t\tslot.GravelId = maybe.Id == DecorDef.GravelNone.Id ? null : maybe.Id;\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tslot.Decor.Add( new DecorPiece\r\n\t\t\t{\r\n\t\t\t\tDefId = entry.DefId,\r\n\t\t\t\tX = entry.X,\r\n\t\t\t\tY = entry.Y\r\n\t\t\t} );\r\n\t\t}\r\n\t}\r\n\r\n\tstatic void ApplyHabitatFromSave( TankSlot slot, string habitatId, string fallbackName )\r\n\t{\r\n\t\tvar def = HabitatDef.Find( habitatId );\r\n\t\tif ( def is null || !HabitatDef.MatchesTank( def, slot.Water ) )\r\n\t\t{\r\n\t\t\t// Infer from name for older saves, else starter.\r\n\t\t\tdef = HabitatDef.StarterFor( slot.Water );\r\n\t\t\tif ( !string.IsNullOrWhiteSpace( fallbackName ) )\r\n\t\t\t{\r\n\t\t\t\tforeach ( var h in HabitatDef.Catalog )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( HabitatDef.MatchesTank( h, slot.Water ) \u0026\u0026 h.Name == fallbackName )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdef = h;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tslot.HabitatId = def.Id;\r\n\t\tslot.Name = string.IsNullOrWhiteSpace( fallbackName ) ? def.Name : fallbackName;\r\n\t\t// Capacity already set from save; floor to habitat base.\r\n\t\tslot.Capacity = Math.Max( slot.Capacity, def.BaseCapacity );\r\n\t}\r\n\r\n\tpublic static void WriteToSave( SaveData data )\r\n\t{\r\n\t\tif ( data is null )\r\n\t\t\treturn;\r\n\r\n\t\tdata.FreshTankName = _fresh.Name;\r\n\t\tdata.SaltTankName = _salt.Name;\r\n\t\tdata.FreshHabitatId = _fresh.HabitatId;\r\n\t\tdata.SaltHabitatId = _salt.HabitatId;\r\n\t\tdata.FreshCapacity = _fresh.Capacity;\r\n\t\tdata.SaltCapacity = _salt.Capacity;\r\n\t\tdata.SaltUnlocked = _salt.Unlocked;\r\n\t\tdata.ActiveWater = _active.Water == WaterType.Salt ? \u0022Salt\u0022 : \u0022Fresh\u0022;\r\n\t\t// Legacy fields\r\n\t\tdata.TankName = _active.Name;\r\n\t\tdata.Water = data.ActiveWater;\r\n\t\tdata.Capacity = _active.Capacity;\r\n\t\tdata.FreshFish = SerializeFish( _fresh.Fish );\r\n\t\tdata.SaltFish = SerializeFish( _salt.Fish );\r\n\t\tdata.Fish = SerializeFish( _active.Fish );\r\n\t\tdata.FreshDecor = SerializeDecor( _fresh.Decor );\r\n\t\tdata.SaltDecor = SerializeDecor( _salt.Decor );\r\n\t\tdata.FreshBackdropId = _fresh.BackdropId;\r\n\t\tdata.SaltBackdropId = _salt.BackdropId;\r\n\t\tdata.FreshGravelId = _fresh.GravelId;\r\n\t\tdata.SaltGravelId = _salt.GravelId;\r\n\t\tdata.AbominationsCreated = _abominationsCreated;\r\n\t\tdata.FishDeaths = _fishDeaths;\r\n\t\tdata.TankEggs = BreedingSystem.WriteEggs();\r\n\t}\r\n\r\n\tstatic List\u003CFishSaveData\u003E SerializeFish( List\u003CFishActor\u003E list ) =\u003E\r\n\t\tlist.Select( f =\u003E new FishSaveData\r\n\t\t{\r\n\t\t\tSpeciesId = f.Species.Id,\r\n\t\t\tNickname = string.IsNullOrWhiteSpace( f.Nickname ) ? null : f.Nickname.Trim(),\r\n\t\t\tX = f.X,\r\n\t\t\tY = f.Y,\r\n\t\t\tVx = f.Vx,\r\n\t\t\tVy = f.Vy,\r\n\t\t\tOnMeth = f.OnMeth,\r\n\t\t\tMethTimer = f.MethTimer,\r\n\t\t\tSickTimer = f.SickTimer,\r\n\t\t\tAddiction = f.Addiction,\r\n\t\t\tWithdrawalTimer = f.WithdrawalTimer,\r\n\t\t\tEndurance = f.Endurance,\r\n\t\t\tSpeedStat = f.SpeedStat,\r\n\t\t\tAgility = f.Agility,\r\n\t\t\tJacked = f.Jacked,\r\n\t\t\tJackedTimer = f.JackedTimer,\r\n\t\t\tFat = f.Fat,\r\n\t\t\tStoned = f.Stoned,\r\n\t\t\tStonedTimer = f.StonedTimer,\r\n\t\t\tFightInjuryTimer = f.FightInjuryTimer,\r\n\t\t\tAgeSeconds = f.AgeSeconds,\r\n\t\t\tPredationBulk = f.PredationBulk,\r\n\t\t\tFishEatenCount = f.FishEatenCount,\r\n\t\t\tRarity = (int)f.Rarity,\r\n\t\t\tPregnantTimer = f.PregnantTimer,\r\n\t\t\tMateCooldown = f.MateCooldown\r\n\t\t} ).ToList();\r\n\r\n\tstatic List\u003CDecorSaveData\u003E SerializeDecor( List\u003CDecorPiece\u003E list ) =\u003E\r\n\t\tlist.Select( d =\u003E new DecorSaveData\r\n\t\t{\r\n\t\t\tDefId = d.DefId,\r\n\t\t\tX = d.X,\r\n\t\t\tY = d.Y\r\n\t\t} ).ToList();\r\n\r\n\tpublic static void Clear()\r\n\t{\r\n\t\tBreedingSystem.Clear();\r\n\t\t_fresh.Fish.Clear();\r\n\t\t_salt.Fish.Clear();\r\n\t\t_fresh.Decor.Clear();\r\n\t\t_salt.Decor.Clear();\r\n\t\t_fresh.ResetToStarter();\r\n\t\t_salt.ResetToStarter();\r\n\t\t_active = _fresh;\r\n\t\tIsActive = false;\r\n\t\tFrameId = 0;\r\n\t\t_abominationsCreated = 0;\r\n\t\t_fishDeaths = 0;\r\n\t\tClearBanner();\r\n\t}\r\n\r\n\tpublic static bool TrySetBackdrop( DecorDef def, bool announce = true )\r\n\t{\r\n\t\tif ( !_active.Unlocked )\r\n\t\t\treturn false;\r\n\r\n\t\tif ( def is null || def.Id == DecorDef.BackdropNone.Id || !def.IsBackdrop )\r\n\t\t{\r\n\t\t\t_active.BackdropId = null;\r\n\t\t\tif ( announce )\r\n\t\t\t\tShowBanner( \u0022Peeled the poster. Plain glass.\u0022 );\r\n\t\t\tFrameId\u002B\u002B;\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\t_active.BackdropId = def.Id;\r\n\t\tif ( def.ChaosOnPlace \u003E 0f )\r\n\t\t\tChaos.Add( def.ChaosOnPlace );\r\n\t\tif ( announce )\r\n\t\t\tShowBanner( $\u0022Hung {def.Name}.\u0022 );\r\n\t\tFrameId\u002B\u002B;\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic static bool TrySetGravel( DecorDef def, bool announce = true )\r\n\t{\r\n\t\tif ( !_active.Unlocked )\r\n\t\t\treturn false;\r\n\r\n\t\tif ( def is null || def.Id == DecorDef.GravelNone.Id || !def.IsSubstrate )\r\n\t\t{\r\n\t\t\t_active.GravelId = null;\r\n\t\t\tif ( announce )\r\n\t\t\t\tShowBanner( \u0022Scooped back to lot dirt.\u0022 );\r\n\t\t\tFrameId\u002B\u002B;\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\t_active.GravelId = def.Id;\r\n\t\tif ( def.ChaosOnPlace \u003E 0f )\r\n\t\t\tChaos.Add( def.ChaosOnPlace );\r\n\t\tif ( announce )\r\n\t\t\tShowBanner( $\u0022Dumped {def.Name}.\u0022 );\r\n\t\tFrameId\u002B\u002B;\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic static bool TryPlaceDecor( DecorDef def, bool announce = true )\r\n\t{\r\n\t\tif ( def is null || !_active.Unlocked )\r\n\t\t\treturn false;\r\n\r\n\t\tif ( def.IsBackdrop )\r\n\t\t\treturn TrySetBackdrop( def, announce );\r\n\t\tif ( def.IsSubstrate )\r\n\t\t\treturn TrySetGravel( def, announce );\r\n\r\n\t\tif ( _active.Decor.Count \u003E= _active.MaxDecor )\r\n\t\t{\r\n\t\t\tif ( announce )\r\n\t\t\t\tShowBanner( \u0022Tank is full of junk. Tasteful junk limit reached.\u0022 );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar w = def.Width;\r\n\t\tvar h = def.Height;\r\n\t\tvar margin = 20f;\r\n\t\tvar maxW = Width - margin * 2f;\r\n\t\tif ( w \u003E maxW )\r\n\t\t\tw = maxW;\r\n\r\n\t\tvar x = margin \u002B (float)_rng.NextDouble() * MathF.Max( 1f, Width - w - margin * 2f );\r\n\t\tvar y = ComputeDecorY( def, h );\r\n\r\n\t\t_active.Decor.Add( new DecorPiece\r\n\t\t{\r\n\t\t\tDefId = def.Id,\r\n\t\t\tX = x,\r\n\t\t\tY = y\r\n\t\t} );\r\n\r\n\t\tif ( def.ChaosOnPlace \u003E 0f )\r\n\t\t\tChaos.Add( def.ChaosOnPlace );\r\n\r\n\t\tif ( announce )\r\n\t\t\tShowBanner( $\u0022Placed {def.Name}.\u0022 );\r\n\r\n\t\tAchievementSystem.Unlock( \u0022decor\u0022 );\r\n\t\tFrameId\u002B\u002B;\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Bottom of sprite sits on gravel, then sinks by \u003Csee cref=\u0022DecorDef.GroundSink\u0022/\u003E\r\n\t/// so plant dirt bases don\u0027t float above the sand.\r\n\t/// \u003C/summary\u003E\r\n\tpublic static float ComputeDecorRestY( DecorDef def, float h )\r\n\t{\r\n\t\tconst float gravelH = 28f;\r\n\t\tvar gravelTop = Height - gravelH;\r\n\t\tvar sink = def?.GroundSink ?? 0f;\r\n\t\t// Panel top = gravel top - height \u002B sink (positive sink buries the base).\r\n\t\tvar y = gravelTop - h \u002B sink;\r\n\t\t// Keep a few px of headroom under the rim.\r\n\t\tif ( y \u003C 6f )\r\n\t\t\ty = 6f;\r\n\t\treturn y;\r\n\t}\r\n\r\n\t// Back-compat alias used by place/reseat paths.\r\n\tstatic float ComputeDecorY( DecorDef def, float h ) =\u003E ComputeDecorRestY( def, h );\r\n\r\n\tpublic static float ClampDecorX( DecorDef def, float x )\r\n\t{\r\n\t\tif ( def is null )\r\n\t\t\treturn x;\r\n\t\tvar maxX = MathF.Max( 8f, Width - def.Width - 8f );\r\n\t\treturn Math.Clamp( x, 8f, maxX );\r\n\t}\r\n\r\n\tpublic static float ClampDecorY( DecorDef def, float y )\r\n\t{\r\n\t\tif ( def is null )\r\n\t\t\treturn y;\r\n\t\tvar rest = ComputeDecorRestY( def, def.Height );\r\n\t\t// Allow floating above gravel; never below rest (buried base).\r\n\t\treturn Math.Clamp( y, 6f, rest );\r\n\t}\r\n\r\n\tpublic static DecorPiece GetActiveDecor( int index )\r\n\t{\r\n\t\tif ( index \u003C 0 || index \u003E= _active.Decor.Count )\r\n\t\t\treturn null;\r\n\t\treturn _active.Decor[index];\r\n\t}\r\n\r\n\tpublic static int IndexOfActiveDecor( DecorPiece piece )\r\n\t{\r\n\t\tif ( piece is null )\r\n\t\t\treturn -1;\r\n\t\treturn _active.Decor.IndexOf( piece );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EStart a player drag \u2014 freezes physics on that piece.\u003C/summary\u003E\r\n\tpublic static bool TryBeginDecorDrag( DecorPiece piece )\r\n\t{\r\n\t\tif ( piece is null || !_active.Decor.Contains( piece ) )\r\n\t\t\treturn false;\r\n\t\t// Only one held prop at a time.\r\n\t\tforeach ( var p in _active.Decor )\r\n\t\t\tp.Held = false;\r\n\t\tpiece.Held = true;\r\n\t\tpiece.Vx = 0f;\r\n\t\tpiece.Vy = 0f;\r\n\t\t// Lift slightly so it clearly leaves the gravel while grabbed.\r\n\t\tvar def = piece.Def;\r\n\t\tif ( def is not null )\r\n\t\t{\r\n\t\t\tvar rest = ComputeDecorRestY( def, def.Height );\r\n\t\t\tif ( piece.Y \u003E rest - 18f )\r\n\t\t\t\tpiece.Y = rest - 18f;\r\n\t\t}\r\n\t\tFrameId\u002B\u002B;\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ENudge a held decor by mouse delta (tank px).\u003C/summary\u003E\r\n\tpublic static void NudgeDecorHeld( DecorPiece piece, float dx, float dy )\r\n\t{\r\n\t\tif ( piece is null || !piece.Held )\r\n\t\t\treturn;\r\n\t\tvar def = piece.Def;\r\n\t\tif ( def is null )\r\n\t\t\treturn;\r\n\t\tpiece.X = ClampDecorX( def, piece.X \u002B dx );\r\n\t\tpiece.Y = ClampDecorY( def, piece.Y \u002B dy );\r\n\t\tFrameId\u002B\u002B;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ESnap held decor so its center sits at tank-local (cx, cy).\u003C/summary\u003E\r\n\tpublic static void SetDecorHeldCenter( DecorPiece piece, float cx, float cy )\r\n\t{\r\n\t\tif ( piece is null || !piece.Held )\r\n\t\t\treturn;\r\n\t\tvar def = piece.Def;\r\n\t\tif ( def is null )\r\n\t\t\treturn;\r\n\t\tpiece.X = ClampDecorX( def, cx - def.Width * 0.5f );\r\n\t\tpiece.Y = ClampDecorY( def, cy - def.Height * 0.5f );\r\n\t\tFrameId\u002B\u002B;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ERelease drag and apply throw velocity (tank px/s).\u003C/summary\u003E\r\n\tpublic static void EndDecorDrag( DecorPiece piece, float throwVx, float throwVy )\r\n\t{\r\n\t\tif ( piece is null )\r\n\t\t\treturn;\r\n\t\tpiece.Held = false;\r\n\t\tvar def = piece.Def;\r\n\r\n\t\tconst float maxThrow = 2600f;\r\n\t\tvar spd = MathF.Sqrt( throwVx * throwVx \u002B throwVy * throwVy );\r\n\t\tif ( spd \u003E maxThrow \u0026\u0026 spd \u003E 0.01f )\r\n\t\t{\r\n\t\t\tvar s = maxThrow / spd;\r\n\t\t\tthrowVx *= s;\r\n\t\t\tthrowVy *= s;\r\n\t\t\tspd = maxThrow;\r\n\t\t}\r\n\r\n\t\t// No deadzone kill \u2014 caller decides soft-drop by passing zeros.\r\n\t\tpiece.Vx = throwVx;\r\n\t\tpiece.Vy = throwVy;\r\n\r\n\t\tif ( spd \u003E= 1f \u0026\u0026 def is not null )\r\n\t\t{\r\n\t\t\tvar rest = ComputeDecorRestY( def, def.Height );\r\n\t\t\t// Leave the sand so the floor solver can\u0027t eat the launch.\r\n\t\t\tpiece.Y = MathF.Min( piece.Y, rest - 48f );\r\n\t\t\t// Always give a bit of loft so throws leave gravel even on flat flicks.\r\n\t\t\tif ( piece.Vy \u003E -220f )\r\n\t\t\t\tpiece.Vy = -220f - MathF.Abs( piece.Vx ) * 0.2f;\r\n\t\t\tpiece.ThrowGrace = 0.55f;\r\n\t\t}\r\n\t\telse if ( def is not null )\r\n\t\t{\r\n\t\t\tvar rest = ComputeDecorRestY( def, def.Height );\r\n\t\t\tif ( piece.Y \u003E rest - 28f )\r\n\t\t\t\tpiece.Y = rest;\r\n\t\t\tpiece.Vx = 0f;\r\n\t\t\tpiece.Vy = 0f;\r\n\t\t\tpiece.ThrowGrace = 0f;\r\n\t\t}\r\n\r\n\t\tFrameId\u002B\u002B;\r\n\t\tSaveGame.TrySave();\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EWater physics for floating / thrown decor.\u003C/summary\u003E\r\n\tstatic void TickDecorPhysics( TankSlot slot, float dt )\r\n\t{\r\n\t\tif ( slot is null || slot.Decor.Count == 0 || dt \u003C= 0f )\r\n\t\t\treturn;\r\n\r\n\t\tconst float gravity = 650f;\r\n\t\tconst float waterDrag = 0.85f;\r\n\t\tconst float bounce = 0.55f;\r\n\t\tconst float floorFriction = 0.92f;\r\n\t\tconst float restSpeed = 18f;\r\n\r\n\t\tvar any = false;\r\n\t\tforeach ( var piece in slot.Decor )\r\n\t\t{\r\n\t\t\tif ( piece is null || piece.Held )\r\n\t\t\t\tcontinue;\r\n\t\t\tvar def = piece.Def;\r\n\t\t\tif ( def is null )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tif ( piece.ThrowGrace \u003E 0f )\r\n\t\t\t\tpiece.ThrowGrace = MathF.Max( 0f, piece.ThrowGrace - dt );\r\n\r\n\t\t\tvar restY = ComputeDecorRestY( def, def.Height );\r\n\t\t\tvar grace = piece.ThrowGrace \u003E 0f;\r\n\t\t\tvar moving = MathF.Abs( piece.Vx ) \u003E 0.5f || MathF.Abs( piece.Vy ) \u003E 0.5f;\r\n\t\t\tvar onFloor = piece.Y \u003E= restY - 1.5f \u0026\u0026 !grace;\r\n\r\n\t\t\tif ( !moving \u0026\u0026 onFloor )\r\n\t\t\t{\r\n\t\t\t\tpiece.Vx = 0f;\r\n\t\t\t\tpiece.Vy = 0f;\r\n\t\t\t\tpiece.Y = restY;\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tany = true;\r\n\r\n\t\t\t// Gravity (always during grace so arc looks right after loft).\r\n\t\t\tpiece.Vy \u002B= gravity * dt;\r\n\r\n\t\t\tvar damp = MathF.Exp( -waterDrag * dt );\r\n\t\t\tpiece.Vx *= damp;\r\n\t\t\tpiece.Vy *= damp;\r\n\r\n\t\t\tpiece.X \u002B= piece.Vx * dt;\r\n\t\t\tpiece.Y \u002B= piece.Vy * dt;\r\n\r\n\t\t\tvar maxX = MathF.Max( 8f, Width - def.Width - 8f );\r\n\t\t\tif ( piece.X \u003C 8f )\r\n\t\t\t{\r\n\t\t\t\tpiece.X = 8f;\r\n\t\t\t\tpiece.Vx = MathF.Abs( piece.Vx ) * bounce;\r\n\t\t\t}\r\n\t\t\telse if ( piece.X \u003E maxX )\r\n\t\t\t{\r\n\t\t\t\tpiece.X = maxX;\r\n\t\t\t\tpiece.Vx = -MathF.Abs( piece.Vx ) * bounce;\r\n\t\t\t}\r\n\r\n\t\t\tif ( piece.Y \u003C 6f )\r\n\t\t\t{\r\n\t\t\t\tpiece.Y = 6f;\r\n\t\t\t\tpiece.Vy = MathF.Abs( piece.Vy ) * bounce * 0.45f;\r\n\t\t\t}\r\n\r\n\t\t\tif ( piece.Y \u003E= restY )\r\n\t\t\t{\r\n\t\t\t\tpiece.Y = restY;\r\n\t\t\t\tif ( piece.Vy \u003E 0f )\r\n\t\t\t\t\tpiece.Vy = -piece.Vy * bounce;\r\n\t\t\t\tif ( !grace )\r\n\t\t\t\t\tpiece.Vx *= floorFriction;\r\n\r\n\t\t\t\tif ( !grace\r\n\t\t\t\t\t\u0026\u0026 MathF.Abs( piece.Vy ) \u003C restSpeed\r\n\t\t\t\t\t\u0026\u0026 MathF.Abs( piece.Vx ) \u003C restSpeed )\r\n\t\t\t\t{\r\n\t\t\t\t\tpiece.Vx = 0f;\r\n\t\t\t\t\tpiece.Vy = 0f;\r\n\t\t\t\t\tpiece.Y = restY;\r\n\t\t\t\t\tpiece.ThrowGrace = 0f;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( any )\r\n\t\t\tFrameId\u002B\u002B;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ERe-seat every decor piece to current sizes (fixes old tiny/float saves).\u003C/summary\u003E\r\n\tpublic static void ReseatAllDecor()\r\n\t{\r\n\t\tReseatSlot( _fresh );\r\n\t\tif ( _salt.Unlocked )\r\n\t\t\tReseatSlot( _salt );\r\n\t\tFrameId\u002B\u002B;\r\n\t}\r\n\r\n\tstatic void ReseatSlot( TankSlot slot )\r\n\t{\r\n\t\tif ( slot is null )\r\n\t\t\treturn;\r\n\t\tforeach ( var piece in slot.Decor )\r\n\t\t{\r\n\t\t\tvar def = piece.Def;\r\n\t\t\tif ( def is null )\r\n\t\t\t\tcontinue;\r\n\t\t\tvar h = def.Height;\r\n\t\t\t// Keep X; clamp so wide props stay on-screen.\r\n\t\t\tpiece.X = ClampDecorX( def, piece.X );\r\n\t\t\tpiece.Y = ComputeDecorY( def, h );\r\n\t\t\tpiece.Vx = 0f;\r\n\t\t\tpiece.Vy = 0f;\r\n\t\t\tpiece.Held = false;\r\n\t\t\tpiece.ThrowGrace = 0f;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static bool TryUnlockSaltTank( bool announce = true )\r\n\t{\r\n\t\tif ( _salt.Unlocked )\r\n\t\t\treturn false;\r\n\r\n\t\t_salt.Unlocked = true;\r\n\t\t_salt.ResetToStarter();\r\n\t\t// Free starter salt fish so the second tank isn\u0027t an empty glass.\r\n\t\tvar prev = _active;\r\n\t\t_active = _salt;\r\n\t\tSpawn( FishSpecies.Clownfish, FishRarity.Common, ageSeconds: 120f );\r\n\t\t_active = prev ?? _fresh;\r\n\t\tif ( announce )\r\n\t\t\tShowBanner( \u0022Salt tank \u002B clownfish. Match the water or she eats them.\u0022 );\r\n\t\telse\r\n\t\t\tShowBanner( \u0022Salt tank online \u00B7 free clownfish \u00B7 wrong water = supper.\u0022 );\r\n\t\tBoomFeel.SoftPunch( 0.55f );\r\n\t\tEconomy.AddForced( 12 );\r\n\t\tGameAudio.PlaySaltUnlock();\r\n\t\tGrandmaReact.OnFirstSalt();\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EUpgrade the active tank to the next habitat in the chain.\u003C/summary\u003E\r\n\tpublic static bool TryUpgradeHabitat( HabitatDef def, bool announce = true )\r\n\t{\r\n\t\tif ( def is null || !_active.Unlocked )\r\n\t\t\treturn false;\r\n\r\n\t\tif ( def.FightPrizeOnly )\r\n\t\t{\r\n\t\t\tif ( announce )\r\n\t\t\t\tShowBanner( \u0022That tank is a fight prize \u2014 win it in the arena.\u0022 );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tif ( !HabitatDef.MatchesTank( def, _active.Water ) )\r\n\t\t{\r\n\t\t\tif ( announce )\r\n\t\t\t\tShowBanner( \u0022That habitat doesn\u0027t fit this water type.\u0022 );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tif ( def.Rank != _active.HabitatRank \u002B 1 )\r\n\t\t{\r\n\t\t\tif ( announce )\r\n\t\t\t\tShowBanner( \u0022Unlock habitats in order. No skipping the bathtub.\u0022 );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t_active.ApplyHabitat( def );\r\n\t\tif ( announce )\r\n\t\t\tShowBanner( string.IsNullOrWhiteSpace( def.Banner ) ? $\u0022Moved into {def.Name}.\u0022 : def.Banner );\r\n\t\tFrameId\u002B\u002B;\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Claim a tank won in battle \u2014 applies to the active tank (fresh or salt view).\r\n\t/// Keeps capacity floor if already larger; renames the vessel.\r\n\t/// \u003C/summary\u003E\r\n\tpublic static bool TryApplyFightPrize( HabitatDef def )\r\n\t{\r\n\t\tif ( def is null || !IsActive || !_active.Unlocked )\r\n\t\t\treturn false;\r\n\r\n\t\tif ( !HabitatDef.MatchesTank( def, _active.Water ) )\r\n\t\t{\r\n\t\t\t// Fall back to the other tank if it fits and is unlocked\r\n\t\t\tvar other = _active == _fresh ? _salt : _fresh;\r\n\t\t\tif ( other.Unlocked \u0026\u0026 HabitatDef.MatchesTank( def, other.Water ) )\r\n\t\t\t{\r\n\t\t\t\tother.ApplyHabitat( def, keepCapacityFloor: true );\r\n\t\t\t\tother.Name = def.Name;\r\n\t\t\t\tShowBanner( string.IsNullOrWhiteSpace( def.Banner )\r\n\t\t\t\t\t? $\u0022Fight prize: {def.Name} installed on {other.Water} tank!\u0022\r\n\t\t\t\t\t: def.Banner );\r\n\t\t\t\tFrameId\u002B\u002B;\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t\tShowBanner( $\u0022{def.Name} doesn\u0027t fit your current tank water.\u0022 );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t_active.ApplyHabitat( def, keepCapacityFloor: true );\r\n\t\t_active.Name = def.Name;\r\n\t\tShowBanner( string.IsNullOrWhiteSpace( def.Banner )\r\n\t\t\t? $\u0022Fight prize tank: {def.Name}!\u0022\r\n\t\t\t: def.Banner );\r\n\t\tGameAudio.PlayUi( GameAudio.Upgrade );\r\n\t\tFrameId\u002B\u002B;\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic static bool CanUpgradeTo( HabitatDef def )\r\n\t{\r\n\t\tif ( def is null || !_active.Unlocked || !IsActive )\r\n\t\t\treturn false;\r\n\t\tif ( !HabitatDef.MatchesTank( def, _active.Water ) )\r\n\t\t\treturn false;\r\n\t\tif ( def.Rank != _active.HabitatRank \u002B 1 )\r\n\t\t\treturn false;\r\n\t\tif ( Economy.PlaytimeSeconds \u003C def.UnlockPlaytime )\r\n\t\t\treturn false;\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic static bool TrySwitchTank( WaterType water )\r\n\t{\r\n\t\tif ( water == WaterType.Salt \u0026\u0026 !_salt.Unlocked )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022No salt tank yet. Buy one in the shop.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t_active = water == WaterType.Salt ? _salt : _fresh;\r\n\t\tFrameId\u002B\u002B;\r\n\t\tShowBanner( water == WaterType.Salt ? \u0022Now viewing SALT tank.\u0022 : \u0022Now viewing FRESH tank.\u0022 );\r\n\t\tGameAudio.PlayUi( GameAudio.Notice );\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic static void IncreaseCapacity( int amount )\r\n\t{\r\n\t\tif ( amount \u003C= 0 )\r\n\t\t\treturn;\r\n\r\n\t\t_active.Capacity \u002B= amount;\r\n\t}\r\n\r\n\tpublic static bool RemoveFish( string instanceId )\r\n\t{\r\n\t\tif ( string.IsNullOrEmpty( instanceId ) )\r\n\t\t\treturn false;\r\n\r\n\t\tvar removed = _active.Fish.RemoveAll( f =\u003E f.InstanceId == instanceId ) \u003E 0;\r\n\t\tif ( removed )\r\n\t\t\tFrameId\u002B\u002B;\r\n\t\treturn removed;\r\n\t}\r\n\r\n\tpublic static FishActor FindFish( string instanceId )\r\n\t{\r\n\t\tif ( string.IsNullOrEmpty( instanceId ) )\r\n\t\t\treturn null;\r\n\t\tforeach ( var f in _fresh.Fish )\r\n\t\t{\r\n\t\t\tif ( f.InstanceId == instanceId )\r\n\t\t\t\treturn f;\r\n\t\t}\r\n\t\tforeach ( var f in _salt.Fish )\r\n\t\t{\r\n\t\t\tif ( f.InstanceId == instanceId )\r\n\t\t\t\treturn f;\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EEvery living fish across both tanks (includes OnAdventure).\u003C/summary\u003E\r\n\tpublic static IEnumerable\u003CFishActor\u003E AllFish()\r\n\t{\r\n\t\tforeach ( var f in _fresh.Fish )\r\n\t\t\tyield return f;\r\n\t\tforeach ( var f in _salt.Fish )\r\n\t\t\tyield return f;\r\n\t}\r\n\r\n\tpublic static void BumpFrame() =\u003E FrameId\u002B\u002B;\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Chillquarium flip price: grow then sell.\r\n\t/// Guppies = crumbs; adults profit on commons; rarity is the jackpot.\r\n\t/// \u003C/summary\u003E\r\n\tpublic static double SellPrice( FishActor fish )\r\n\t{\r\n\t\tif ( fish?.Species is null )\r\n\t\t\treturn 1;\r\n\r\n\t\t// List: shop cost, or income-based for monsters/hybrids (BuyCost 0).\r\n\t\tvar list = fish.Species.BuyCost;\r\n\t\tif ( list \u003C 1.0 )\r\n\t\t\tlist = Math.Max( 18.0, fish.Species.IncomePerSecond * 90.0 );\r\n\r\n\t\t// Growth curve: baby still flips \u00B7 adult ~1.2\u00D7 \u00B7 mature ~1.55\u00D7 list.\r\n\t\t// Floor high enough that early sells fund the next buy (not a guilt tax).\r\n\t\tvar growthMul = 0.38 \u002B fish.Growth01 * 0.82 \u002B fish.Mature01 * 0.35;\r\n\t\tvar price = list * growthMul * fish.RaritySellMul;\r\n\r\n\t\t// Trained aboms flip harder \u2014 rewards investment over raw birth.\r\n\t\tif ( fish.IsMonster )\r\n\t\t\tprice *= 1.15 \u002B fish.TrainingNorm * 0.45;\r\n\t\t// Apex predators that snacked are meatier on the market.\r\n\t\tif ( fish.PredationBulkNorm \u003E 0f )\r\n\t\t\tprice *= 1.0 \u002B fish.PredationBulkNorm * 0.4;\r\n\t\tif ( fish.Jacked )\r\n\t\t\tprice *= 1.2;\r\n\t\tif ( fish.FatNorm \u003E 0.5f )\r\n\t\t\tprice *= 0.92; // chonk is harder to flip\r\n\t\tif ( fish.IsSick )\r\n\t\t\tprice *= 0.4;\r\n\t\treturn Math.Max( 1.0, Math.Round( price, 1 ) );\r\n\t}\r\n\r\n\tpublic static bool TrySellFish( string instanceId )\r\n\t{\r\n\t\tvar fish = FindFish( instanceId );\r\n\t\tif ( fish is null )\r\n\t\t\treturn false;\r\n\r\n\t\tvar wasMonster = fish.Species?.IsMonster == true;\r\n\t\tvar wasFailure = fish.IsAbominationFailure;\r\n\t\tvar price = SellPrice( fish );\r\n\t\tvar name = fish.DisplayName;\r\n\t\tvar sx = fish.X;\r\n\t\tvar sy = fish.DrawY;\r\n\t\tvar rare = fish.Rarity;\r\n\t\tif ( !RemoveFish( instanceId ) )\r\n\t\t\treturn false;\r\n\r\n\t\t// Always float \u002B confetti \u2014 sell is the active dopamine verb.\r\n\t\tEconomy.AddForced( price );\r\n\t\tExplodeSystem.SpawnCoinBurst( sx, sy, count: rare \u003E= FishRarity.Rare ? 20 : 12 );\r\n\t\tBoomFeel.SoftPunch( rare \u003E= FishRarity.Rare ? 0.65f : 0.4f );\r\n\t\tStatFloat.PushTag( \u0022SOLD \u002B\u0022 \u002B \u0022\u00D0\u0022 \u002B Economy.FormatDoge( price ), \u0022sell\u0022, sx, sy - 20f, life: 1.7f );\r\n\t\tif ( rare \u003E= FishRarity.Rare )\r\n\t\t\tStatFloat.PushTag( FishRarityInfo.Label( rare ).ToUpperInvariant() \u002B \u0022!\u0022, \u0022rare\u0022, sx, sy - 36f, delay: 0.12f, life: 1.9f );\r\n\t\tif ( rare \u003E= FishRarity.Rare || price \u003E= 40 )\r\n\t\t\tGameAudio.PlayBigCash();\r\n\t\telse\r\n\t\t\tGameAudio.PlayCoin();\r\n\t\tif ( wasMonster \u0026\u0026 wasFailure )\r\n\t\t{\r\n\t\t\t// Soft flip of a dud \u2014 she prefers M80, but cash is fine.\r\n\t\t\tKarma.Add( 0.4f );\r\n\t\t\tShowBanner( $\u0022Sold failure {name} for \u00D0{Economy.FormatDoge( price )}.\u0022 );\r\n\t\t}\r\n\t\telse if ( wasMonster )\r\n\t\t{\r\n\t\t\tKarma.Add( -1.2f );\r\n\t\t\tShowBanner( $\u0022Sold {name} for \u00D0{Economy.FormatDoge( price )}. Grandma wanted fights.\u0022 );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tKarma.Add( -0.3f );\r\n\t\t\tShowBanner( $\u0022Sold {name} for \u00D0{Economy.FormatDoge( price )}.\u0022 );\r\n\t\t}\r\n\t\tif ( wasMonster )\r\n\t\t\tGameAudio.OnMoodMaybeChanged( force: true );\r\n\t\tStorySystem.NotifySold();\r\n\t\t// Routine guppy flips are noise \u2014 he only comments on a real goodbye.\r\n\t\tif ( rare \u003E= FishRarity.Rare || wasMonster || price \u003E= 40 )\r\n\t\t\tJanitorPeep.On( \u0022sell\u0022 );\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Move a fish from the active tank to the other unlocked tank.\r\n\t/// Switches the camera to the destination so you see them arrive.\r\n\t/// \u003C/summary\u003E\r\n\tpublic static bool TryMoveFishToOtherTank( string instanceId )\r\n\t{\r\n\t\tif ( !IsActive || !HasMultipleTanks )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022Need both tanks to move fish.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar fish = FindFish( instanceId );\r\n\t\tif ( fish is null )\r\n\t\t{\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar dest = ViewingSalt ? _fresh : _salt;\r\n\t\tvar destLabel = ViewingSalt ? \u0022Fresh\u0022 : \u0022Salt\u0022;\r\n\r\n\t\tif ( dest.Fish.Count \u003E= dest.Capacity )\r\n\t\t{\r\n\t\t\tShowBanner( $\u0022{destLabel} tank is full ({dest.Capacity}).\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t// Drop M80 tape / fight roster if this fish was armed or picked.\r\n\t\tExplodeSystem.Untape( instanceId );\r\n\t\tBattleSystem.DeselectFish( instanceId );\r\n\r\n\t\t_active.Fish.RemoveAll( f =\u003E f.InstanceId == instanceId );\r\n\r\n\t\t// Drop into the middle of the other tank with a mild kick.\r\n\t\tvar marginX = fish.Species.Width * fish.DrawScaleX * 0.5f \u002B 12f;\r\n\t\tvar marginY = fish.Species.Height * fish.DrawScaleY * 0.5f \u002B 12f;\r\n\t\tfish.X = marginX \u002B (float)_rng.NextDouble() * (Width - marginX * 2f);\r\n\t\tfish.Y = marginY \u002B (float)_rng.NextDouble() * (Height - marginY * 2f);\r\n\t\tvar ang = (float)(_rng.NextDouble() * Math.PI * 2.0);\r\n\t\tvar spd = fish.Species.Speed * fish.SpeedMoveMul * (0.55f \u002B (float)_rng.NextDouble() * 0.35f);\r\n\t\tfish.Vx = MathF.Cos( ang ) * spd;\r\n\t\tfish.Vy = MathF.Sin( ang ) * spd * 0.45f;\r\n\r\n\t\tdest.Fish.Add( fish );\r\n\t\t_active = dest;\r\n\r\n\t\tvar name = fish.DisplayName;\r\n\t\tvar wrong = fish.IsWrongWater( dest.Water );\r\n\t\tif ( wrong )\r\n\t\t{\r\n\t\t\tShowBanner( $\u0022Moved {name} \u2192 {destLabel}. Wrong water \u26A0\u0022 );\r\n\t\t\tChaos.Add( 2f );\r\n\t\t\tKarma.Add( -1.5f );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tShowBanner( $\u0022Moved {name} \u2192 {destLabel}.\u0022 );\r\n\t\t}\r\n\r\n\t\tGameAudio.PlayUi( GameAudio.Confirm );\r\n\t\tSaveGame.TrySave();\r\n\t\tFrameId\u002B\u002B;\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Trade for a different random species of the same water (stats mostly reset).\r\n\t/// Small fee if upgrade; refund crumbs if downgrade.\r\n\t/// \u003C/summary\u003E\r\n\tpublic static bool TryTradeFish( string instanceId )\r\n\t{\r\n\t\tvar fish = FindFish( instanceId );\r\n\t\tif ( fish is null )\r\n\t\t\treturn false;\r\n\r\n\t\tvar pool = FishSpecies.AllCatalog\r\n\t\t\t.Where( s =\u003E s.Water == fish.Species.Water || s.Water == WaterType.Any )\r\n\t\t\t.Where( s =\u003E s.Id != fish.Species.Id )\r\n\t\t\t.ToList();\r\n\t\tif ( pool.Count == 0 )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022Nothing to trade for. Lonely catalog.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar next = pool[_rng.Next( pool.Count )];\r\n\t\tvar fee = Math.Max( 0, Math.Round( (next.BuyCost - fish.Species.BuyCost) * 0.35, 1 ) );\r\n\t\tif ( fee \u003E 0 \u0026\u0026 !Economy.TrySpend( fee ) )\r\n\t\t{\r\n\t\t\tShowBanner( $\u0022Trade needs \u00D0{Economy.FormatDoge( fee )}.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tif ( fee \u003C 0 )\r\n\t\t\tEconomy.Add( Math.Abs( fee ) );\r\n\r\n\t\tvar old = fish.Species.Name;\r\n\t\tfish.Species = next;\r\n\t\t// Mild keep of training; drop jacked/meth comedy state for fairness\r\n\t\tfish.Jacked = false;\r\n\t\tfish.JackedTimer = 0f;\r\n\t\tfish.OnMeth = false;\r\n\t\tfish.MethTimer = 0f;\r\n\t\tfish.Stoned = false;\r\n\t\tfish.StonedTimer = 0f;\r\n\t\tfish.Fat = MathF.Max( 0f, fish.Fat * 0.5f );\r\n\t\tfish.Vx = fish.Species.Speed * (fish.Vx \u003E= 0 ? 0.7f : -0.7f);\r\n\r\n\t\tGameAudio.PlayUi( GameAudio.Trade );\r\n\t\tShowBanner( fee \u003E 0\r\n\t\t\t? $\u0022Traded {old} \u2192 {next.Name} (\u2212\u00D0{Economy.FormatDoge( fee )}).\u0022\r\n\t\t\t: $\u0022Traded {old} \u2192 {next.Name}.\u0022 );\r\n\t\tFrameId\u002B\u002B;\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ELuck tilt for rarity rolls (habitat, salt, aboms, playtime).\u003C/summary\u003E\r\n\tpublic static float RarityLuck\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tvar luck = 1f \u002B Math.Clamp( HabitatRank, 0, 7 ) * 0.09f;\r\n\t\t\tif ( SaltUnlocked )\r\n\t\t\t\tluck \u002B= 0.08f;\r\n\t\t\tif ( HasAnyMonster )\r\n\t\t\t\tluck \u002B= 0.06f;\r\n\t\t\t// Mild late-session spice so rares show up during empire hours.\r\n\t\t\tluck \u002B= Math.Clamp( Economy.PlaytimeSeconds / 3600f, 0f, 0.35f );\r\n\t\t\treturn Math.Clamp( luck, 0.8f, 2.6f );\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static bool Spawn( FishSpecies species ) =\u003E\r\n\t\tSpawn( species, FishRarityInfo.Roll( _rng, RarityLuck ), ageSeconds: 0f );\r\n\r\n\t/// \u003Csummary\u003ESpawn with explicit rarity/age (story starters, debug, packs).\u003C/summary\u003E\r\n\tpublic static bool Spawn( FishSpecies species, FishRarity rarity, float ageSeconds = 0f )\r\n\t{\r\n\t\tif ( species is null || _active.Fish.Count \u003E= _active.Capacity )\r\n\t\t\treturn false;\r\n\r\n\t\t// Size uses growth scale \u2014 room for guppies.\r\n\t\tvar grow = ageSeconds \u003C= 0f ? 0.50f : Math.Clamp( ageSeconds / FishActor.AdultAgeSeconds, 0.50f, 1.14f );\r\n\t\tvar marginX = species.Width * grow * 0.5f \u002B 8f;\r\n\t\tvar marginY = species.Height * grow * 0.5f \u002B 8f;\r\n\t\tvar angle = (float)(_rng.NextDouble() * Math.PI * 2.0);\r\n\t\t// New fish start at base training (~3) so SpeedMoveMul \u2248 1.\r\n\t\t// Guppies dart a bit quicker relative to size.\r\n\t\tvar speed = species.Speed * (0.65f \u002B (float)_rng.NextDouble() * 0.5f) * (1.1f - grow * 0.15f);\r\n\r\n\t\tvar fish = new FishActor\r\n\t\t{\r\n\t\t\tInstanceId = Guid.NewGuid().ToString( \u0022N\u0022 )[..8],\r\n\t\t\tSpecies = species,\r\n\t\t\tX = marginX \u002B (float)_rng.NextDouble() * (Width - marginX * 2f),\r\n\t\t\tY = marginY \u002B (float)_rng.NextDouble() * (Height - marginY * 2f),\r\n\t\t\tVx = MathF.Cos( angle ) * speed,\r\n\t\t\tVy = MathF.Sin( angle ) * speed * 0.55f,\r\n\t\t\tBobPhase = (float)(_rng.NextDouble() * Math.PI * 2.0),\r\n\t\t\tBobSpeed = 2.2f \u002B (float)_rng.NextDouble() * 2.5f,\r\n\t\t\tBobAmp = 2.5f \u002B (float)_rng.NextDouble() * 3.5f,\r\n\t\t\tEndurance = 3f,\r\n\t\t\tSpeedStat = 3f,\r\n\t\t\tAgility = 3f,\r\n\t\t\tAgeSeconds = Math.Max( 0f, ageSeconds ),\r\n\t\t\tRarity = rarity\r\n\t\t};\r\n\r\n\t\t_active.Fish.Add( fish );\r\n\t\tLastSpawnRarity = rarity;\r\n\t\tLastSpawnX = fish.X;\r\n\t\tLastSpawnY = fish.Y;\r\n\r\n\t\tif ( fish.IsWrongWater( _active.Water ) )\r\n\t\t{\r\n\t\t\tShowBanner( $\u0022{species.Name} in {_active.Water} water. Science will notice.\u0022 );\r\n\t\t\tChaos.Add( 4f );\r\n\t\t\tKarma.Add( -3f );\r\n\t\t}\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ERarity of the most recent successful \u003Csee cref=\u0022Spawn\u0022/\u003E.\u003C/summary\u003E\r\n\tpublic static FishRarity LastSpawnRarity { get; private set; } = FishRarity.Common;\r\n\tpublic static float LastSpawnX { get; private set; }\r\n\tpublic static float LastSpawnY { get; private set; }\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Fuse two base fish into a monster. Removes both, spawns abomination, triggers flash VFX.\r\n\t/// \u003C/summary\u003E\r\n\tpublic static bool TryCombineFish( string idA, string idB )\r\n\t{\r\n\t\tif ( !IsActive || string.IsNullOrEmpty( idA ) || string.IsNullOrEmpty( idB ) || idA == idB )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022Need two different fish.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar a = FindFish( idA );\r\n\t\tvar b = FindFish( idB );\r\n\t\tif ( a is null || b is null )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022One of those fish swam off.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar monster = FishFusion.ResolveMonster( a.Species?.Id, b.Species?.Id );\r\n\t\tif ( monster is null )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022Those two refuse to fuse.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t// Midpoint of the crime\r\n\t\tvar mx = (a.X \u002B b.X) * 0.5f;\r\n\t\tvar my = (a.Y \u002B b.Y) * 0.5f;\r\n\r\n\t\t// Inherit rarity: max parent, chance to bump one tier\r\n\t\tvar rar = (FishRarity)Math.Max( (int)a.Rarity, (int)b.Rarity );\r\n\t\tif ( (int)rar \u003C (int)FishRarity.Mythical \u0026\u0026 _rng.NextDouble() \u003C 0.18 )\r\n\t\t\trar = (FishRarity)((int)rar \u002B 1);\r\n\r\n\t\t// Age: half-grown abomination (visible, already earning). Evolutions / tentacles keep bulk.\r\n\t\tvar evoBoost = monster.MonsterType \u003E= 30 ? 0.15f : 0f;\r\n\t\tif ( monster.MonsterType \u003E= 40 )\r\n\t\t\tevoBoost = 0.22f;\r\n\t\tvar age = MathF.Max( a.AgeSeconds, b.AgeSeconds ) * (0.55f \u002B evoBoost)\r\n\t\t\t\u002B MathF.Min( a.AgeSeconds, b.AgeSeconds ) * 0.2f;\r\n\t\tage = Math.Clamp( age, FishActor.AdultAgeSeconds * 0.35f, FishActor.AdultAgeSeconds * 1.1f );\r\n\r\n\t\tvar end = Math.Clamp( (a.Endurance \u002B b.Endurance) * 0.55f \u002B 0.8f \u002B evoBoost * 4f, 0.5f, 10f );\r\n\t\tvar spd = Math.Clamp( (a.SpeedStat \u002B b.SpeedStat) * 0.55f \u002B 0.6f \u002B evoBoost * 3f, 0.5f, 10f );\r\n\t\tvar agi = Math.Clamp( (a.Agility \u002B b.Agility) * 0.55f \u002B 0.6f \u002B evoBoost * 3f, 0.5f, 10f );\r\n\t\t// Monster parent bleeds a little bulk into the beast.\r\n\t\tvar parentBulk = MathF.Max( a.PredationBulk, b.PredationBulk );\r\n\r\n\t\t// Remove both parents (capacity: -2 before \u002B1 abom).\r\n\t\tvar okA = RemoveFish( idA );\r\n\t\tvar okB = RemoveFish( idB );\r\n\t\tif ( !okA || !okB )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022Combine failed mid-scream.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t// Spawn monster in place (capacity: -2 \u002B1 = net free slot)\r\n\t\tvar grow = Math.Clamp( age / FishActor.AdultAgeSeconds, 0.50f, 1.14f );\r\n\t\tvar marginX = monster.Width * grow * 0.5f \u002B 8f;\r\n\t\tvar marginY = monster.Height * grow * 0.5f \u002B 8f;\r\n\t\tmx = Math.Clamp( mx, marginX, Width - marginX );\r\n\t\tmy = Math.Clamp( my, marginY, Height - marginY );\r\n\r\n\t\tvar angle = (float)(_rng.NextDouble() * Math.PI * 2.0);\r\n\t\tvar speed = monster.Speed * (0.7f \u002B (float)_rng.NextDouble() * 0.4f);\r\n\r\n\t\tvar abom = new FishActor\r\n\t\t{\r\n\t\t\tInstanceId = Guid.NewGuid().ToString( \u0022N\u0022 )[..8],\r\n\t\t\tSpecies = monster,\r\n\t\t\tX = mx,\r\n\t\t\tY = my,\r\n\t\t\tVx = MathF.Cos( angle ) * speed,\r\n\t\t\tVy = MathF.Sin( angle ) * speed * 0.55f,\r\n\t\t\tBobPhase = (float)(_rng.NextDouble() * Math.PI * 2.0),\r\n\t\t\tBobSpeed = 3.2f \u002B (float)_rng.NextDouble() * 2.8f,\r\n\t\t\tBobAmp = 4f \u002B (float)_rng.NextDouble() * 4f,\r\n\t\t\tEndurance = end,\r\n\t\t\tSpeedStat = spd,\r\n\t\t\tAgility = agi,\r\n\t\t\tAgeSeconds = age,\r\n\t\t\tRarity = rar,\r\n\t\t\tPredationBulk = monster.MonsterType \u003E= 40\r\n\t\t\t\t? Math.Clamp( parentBulk * 0.6f \u002B 18f, 0f, 100f )\r\n\t\t\t\t: Math.Clamp( parentBulk * 0.35f, 0f, 100f )\r\n\t\t};\r\n\t\t_active.Fish.Add( abom );\r\n\t\tLastSpawnRarity = rar;\r\n\r\n\t\tFusionFeel.Trigger( mx, my );\r\n\t\tWaterFx.Splash( mx, my, 16, WaterLayer.Tank );\r\n\t\tWaterFx.Squirt( mx, my, 0f, -1f, 8, 220f, WaterLayer.Tank );\r\n\t\tChaos.Add( 6f \u002B (int)rar * 1.2f \u002B (monster.MonsterType \u003E= 40 ? 4f : 0f) );\r\n\t\t// Grandma lives for this. Higher tiers = more love \u002B \u00D0.\r\n\t\t_abominationsCreated\u002B\u002B;\r\n\t\tvar birthPay = BirthPayout( monster, rar, evolved: monster.MonsterType \u003E= 30 );\r\n\t\tEconomy.AddForced( birthPay );\r\n\t\tBoomFeel.SoftPunch( 0.7f );\r\n\t\tStatFloat.PushOverFish( abom, \u0022BORN \u002B\u0022 \u002B \u0022\u00D0\u0022 \u002B Economy.FormatDoge( birthPay ), \u0022rare\u0022, life: 2f );\r\n\t\tGameAudio.PlayBigWin( bark: rar \u003E= FishRarity.Rare || birthPay \u003E= 40 );\r\n\t\tKarma.Add( 4f \u002B (int)rar * 0.9f \u002B (monster.MonsterType \u003E= 30 ? 4f : 0f) \u002B (monster.MonsterType \u003E= 40 ? 2f : 0f) );\r\n\t\tGameAudio.PlayUi( GameAudio.Inject );\r\n\t\tvar born = monster.MonsterType \u003E= 40\r\n\t\t\t? $\u0022{monster.Name} unfurls. The glass looks smaller. \u002B\u00D0{Economy.FormatDoge( birthPay )}\u0022\r\n\t\t\t: monster.MonsterType \u003E= 30\r\n\t\t\t\t? $\u0022{monster.Name} evolves. Grandma gasps. \u002B\u00D0{Economy.FormatDoge( birthPay )}\u0022\r\n\t\t\t\t: $\u0022{monster.Name} born. Grandma is into it. \u002B\u00D0{Economy.FormatDoge( birthPay )}\u0022;\r\n\t\tShowBanner( born );\r\n\t\tStorySystem.NotifyCombined();\r\n\t\tJanitorPeep.On( \u0022combine\u0022 );\r\n\t\tGameAudio.OnMoodMaybeChanged( force: true );\r\n\t\tSaveGame.TrySave();\r\n\t\tFrameId\u002B\u002B;\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\u00D0 grant for creating / evolving an abom \u2014 makes combine feel rewarding.\u003C/summary\u003E\r\n\tpublic static double BirthPayout( FishSpecies monster, FishRarity rar, bool evolved )\r\n\t{\r\n\t\tif ( monster is null )\r\n\t\t\treturn 0;\r\n\t\tvar tier = Math.Max( 1, monster.MonsterType );\r\n\t\t// Juicy birth tip \u2014 combining should feel like a jackpot, not a fee.\r\n\t\tvar pay = 16.0 \u002B tier * 2.2 \u002B (int)rar * 6.0;\r\n\t\tif ( evolved )\r\n\t\t\tpay \u002B= 55;\r\n\t\t// Strong income monsters pay a little more at birth (investment signal).\r\n\t\tpay \u002B= monster.IncomePerSecond * 14.0;\r\n\t\treturn Math.Max( 14.0, Math.Round( pay, 1 ) );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Fuse a base fish with a decor prop \u2192 hilarious hybrid abomination.\r\n\t/// Removes fish \u002B decor, spawns hybrid, full fusion flash.\r\n\t/// \u003C/summary\u003E\r\n\tpublic static bool TryCombineFishWithDecor( string fishId, DecorPiece piece )\r\n\t{\r\n\t\tif ( !IsActive || string.IsNullOrEmpty( fishId ) || piece is null )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022Need a fish and a prop.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar fish = FindFish( fishId );\r\n\t\tif ( fish is null )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022That fish swam off.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tif ( fish.Species?.IsMonster == true )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022Monsters don\u0027t eat furniture. Usually.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar def = piece.Def;\r\n\t\tif ( def is null )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022That decor is a ghost.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar hybrid = FishFusion.ResolveDecorHybrid( def.Id );\r\n\t\tif ( hybrid is null )\r\n\t\t{\r\n\t\t\tShowBanner( $\u0022{def.Name} refuses the fish.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t// Not in this tank?\r\n\t\tif ( !_active.Decor.Contains( piece ) )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022Decor is on the other tank.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar mx = (fish.X \u002B piece.X \u002B def.Width * 0.5f) * 0.5f;\r\n\t\tvar my = (fish.Y \u002B piece.Y \u002B def.Height * 0.35f) * 0.5f;\r\n\r\n\t\tvar rar = fish.Rarity;\r\n\t\tif ( (int)rar \u003C (int)FishRarity.Mythical \u0026\u0026 _rng.NextDouble() \u003C 0.22 )\r\n\t\t\trar = (FishRarity)((int)rar \u002B 1);\r\n\r\n\t\tvar age = Math.Clamp( fish.AgeSeconds * 0.7f \u002B 60f, FishActor.AdultAgeSeconds * 0.4f, FishActor.AdultAgeSeconds );\r\n\t\tvar end = Math.Clamp( fish.Endurance \u002B 0.5f, 0.5f, 10f );\r\n\t\tvar spd = Math.Clamp( fish.SpeedStat \u002B 0.3f, 0.5f, 10f );\r\n\t\tvar agi = Math.Clamp( fish.Agility \u002B 0.3f, 0.5f, 10f );\r\n\r\n\t\tif ( !RemoveFish( fishId ) )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022Fish bolted mid-fusion.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\tif ( !_active.Decor.Remove( piece ) )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022Decor escaped. How.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar grow = Math.Clamp( age / FishActor.AdultAgeSeconds, 0.4f, 1.14f );\r\n\t\tvar marginX = hybrid.Width * grow * 0.5f \u002B 8f;\r\n\t\tvar marginY = hybrid.Height * grow * 0.5f \u002B 8f;\r\n\t\tmx = Math.Clamp( mx, marginX, Width - marginX );\r\n\t\tmy = Math.Clamp( my, marginY, Height - marginY );\r\n\r\n\t\tvar angle = (float)(_rng.NextDouble() * Math.PI * 2.0);\r\n\t\tvar speed = hybrid.Speed * (0.65f \u002B (float)_rng.NextDouble() * 0.45f);\r\n\r\n\t\tvar abom = new FishActor\r\n\t\t{\r\n\t\t\tInstanceId = Guid.NewGuid().ToString( \u0022N\u0022 )[..8],\r\n\t\t\tSpecies = hybrid,\r\n\t\t\tX = mx,\r\n\t\t\tY = my,\r\n\t\t\tVx = MathF.Cos( angle ) * speed,\r\n\t\t\tVy = MathF.Sin( angle ) * speed * 0.55f,\r\n\t\t\tBobPhase = (float)(_rng.NextDouble() * Math.PI * 2.0),\r\n\t\t\tBobSpeed = 2.8f \u002B (float)_rng.NextDouble() * 3f,\r\n\t\t\tBobAmp = 5f \u002B (float)_rng.NextDouble() * 5f,\r\n\t\t\tEndurance = end,\r\n\t\t\tSpeedStat = spd,\r\n\t\t\tAgility = agi,\r\n\t\t\tAgeSeconds = age,\r\n\t\t\tRarity = rar\r\n\t\t};\r\n\t\t_active.Fish.Add( abom );\r\n\t\tLastSpawnRarity = rar;\r\n\r\n\t\tFusionFeel.Trigger( mx, my );\r\n\t\tWaterFx.Splash( mx, my, 16, WaterLayer.Tank );\r\n\t\tWaterFx.Squirt( mx, my, 0f, -1f, 8, 220f, WaterLayer.Tank );\r\n\t\tChaos.Add( 8f \u002B def.ChaosOnPlace \u002B (int)rar * 0.8f );\r\n\t\t_abominationsCreated\u002B\u002B;\r\n\t\tvar birthPay = BirthPayout( hybrid, rar, evolved: false ) \u002B 4.0; // furniture crime bonus\r\n\t\tEconomy.Add( birthPay );\r\n\t\tKarma.Add( 5f \u002B (int)rar * 0.7f );\r\n\t\tGameAudio.PlayUi( GameAudio.Inject );\r\n\t\tShowBanner( $\u0022{hybrid.Name}! ({fish.Species.Name} \u002B {def.Name}) \u002B\u00D0{Economy.FormatDoge( birthPay )}\u0022 );\r\n\t\tStorySystem.NotifyCombined();\r\n\t\tJanitorPeep.On( \u0022combine\u0022 );\r\n\t\tAchievementSystem.Unlock( \u0022furniture\u0022 );\r\n\t\tGameAudio.OnMoodMaybeChanged( force: true );\r\n\t\tSaveGame.TrySave();\r\n\t\tFrameId\u002B\u002B;\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic const float MethDurationSeconds = 28f;\r\n\t/// \u003Csummary\u003EMonsters stay high longer \u2014 they asked for it.\u003C/summary\u003E\r\n\tpublic const float MonsterMethDurationSeconds = 48f;\r\n\r\n\t/// \u003Csummary\u003EDose every fish in the active tank. Builds addiction, clears withdrawal, zoomies.\u003C/summary\u003E\r\n\tpublic static int ApplyMethToActiveTank()\r\n\t{\r\n\t\tvar count = 0;\r\n\t\tvar monsterHits = 0;\r\n\t\tvar savedWrongWater = 0;\r\n\t\tvar newlyHooked = 0;\r\n\t\tforeach ( var fish in _active.Fish )\r\n\t\t{\r\n\t\t\tvar wasWrong = fish.IsWrongWater( _active.Water ) || fish.IsSick;\r\n\t\t\tvar wasAddicted = fish.IsAddicted;\r\n\t\t\tvar isMon = fish.IsMonster;\r\n\r\n\t\t\t// Chemical gym first (uses pre-dose addiction for diminishing returns).\r\n\t\t\tTrainingSystem.ApplyMethTraining( fish );\r\n\r\n\t\t\t// Tolerance: more addiction \u2192 shorter high (monsters barely care).\r\n\t\t\tfloat duration;\r\n\t\t\tif ( isMon )\r\n\t\t\t{\r\n\t\t\t\tduration = MonsterMethDurationSeconds * (1f - fish.AddictionNorm * 0.2f);\r\n\t\t\t\tduration = MathF.Max( 22f, duration );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tduration = MethDurationSeconds * (1f - fish.AddictionNorm * 0.55f);\r\n\t\t\t\tduration = MathF.Max( 8f, duration );\r\n\t\t\t}\r\n\r\n\t\t\t// Stack the habit hard on each dose (monsters still get hooked \u2014 they just enjoy it).\r\n\t\t\tvar before = fish.Addiction;\r\n\t\t\tvar addictHit = isMon\r\n\t\t\t\t? 10f \u002B fish.AddictionNorm * 6f\r\n\t\t\t\t: 16f \u002B fish.AddictionNorm * 10f;\r\n\t\t\tfish.Addiction = Math.Clamp( fish.Addiction \u002B addictHit, 0f, 100f );\r\n\t\t\tif ( !wasAddicted \u0026\u0026 fish.IsAddicted )\r\n\t\t\t\tnewlyHooked\u002B\u002B;\r\n\r\n\t\t\tfish.OnMeth = true;\r\n\t\t\tfish.MethTimer = duration;\r\n\t\t\tfish.WithdrawalTimer = 0f;\r\n\t\t\tfish.SickTimer = 0f;\r\n\r\n\t\t\t// Monsters also swell up (fat) when they \u0022enjoy\u0022 the dose \u2014 visual gross.\r\n\t\t\tif ( isMon )\r\n\t\t\t\tfish.Fat = Math.Clamp( fish.Fat \u002B 18f \u002B fish.AddictionNorm * 12f, 0f, 100f );\r\n\r\n\t\t\t// Launch manic sprint \u2014 monsters thrash harder.\r\n\t\t\tvar angle = (float)(_rng.NextDouble() * Math.PI * 2.0);\r\n\t\t\tvar burstMul = isMon\r\n\t\t\t\t? (3.4f \u002B fish.AddictionNorm * 1.6f)\r\n\t\t\t\t: (2.4f \u002B fish.AddictionNorm * 1.2f);\r\n\t\t\tvar burst = fish.Species.Speed * fish.SpeedMoveMul * burstMul;\r\n\t\t\tfish.Vx = MathF.Cos( angle ) * burst;\r\n\t\t\tfish.Vy = MathF.Sin( angle ) * burst * (isMon ? 0.9f : 0.75f);\r\n\t\t\tcount\u002B\u002B;\r\n\t\t\tif ( isMon )\r\n\t\t\t\tmonsterHits\u002B\u002B;\r\n\t\t\tif ( wasWrong )\r\n\t\t\t\tsavedWrongWater\u002B\u002B;\r\n\r\n\t\t\tif ( isMon )\r\n\t\t\t\tShowBanner( $\u0022{fish.DisplayName} LOVES this. Growing wrong.\u0022 );\r\n\t\t\telse if ( fish.Addiction \u003E= 75f \u0026\u0026 before \u003C 75f )\r\n\t\t\t\tShowBanner( $\u0022{fish.Species.Name} is a full-time client now.\u0022 );\r\n\t\t}\r\n\r\n\t\tif ( count \u003E 0 )\r\n\t\t{\r\n\t\t\tChaos.Add( 10f \u002B count * 2.5f \u002B newlyHooked * 4f \u002B monsterHits * 5f );\r\n\t\t\tif ( monsterHits \u003E 0 \u0026\u0026 monsterHits == count )\r\n\t\t\t\tShowBanner( $\u0022{monsterHits} abomination{(monsterHits == 1 ? \u0022\u0022 : \u0022s\u0022)} high \u00B7 stats \u0026 size up.\u0022 );\r\n\t\t\telse if ( monsterHits \u003E 0 )\r\n\t\t\t\tShowBanner( $\u0022{count} dosed \u00B7 {monsterHits} monster{(monsterHits == 1 ? \u0022\u0022 : \u0022s\u0022)} thrashing.\u0022 );\r\n\t\t\telse if ( newlyHooked \u003E 0 )\r\n\t\t\t\tShowBanner( $\u0022{count} dosed \u002B trained. {newlyHooked} just got hooked.\u0022 );\r\n\t\t\telse if ( savedWrongWater \u003E 0 )\r\n\t\t\t\tShowBanner( $\u0022{count} fish FRIED \u00B7 stats up. {savedWrongWater} wrong-water survivors.\u0022 );\r\n\t\t\telse\r\n\t\t\t\tShowBanner( $\u0022{count} fish juiced \u00B7 SPD training on the house.\u0022 );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tShowBanner( \u0022Empty tank. Meth has no audience.\u0022 );\r\n\t\t}\r\n\r\n\t\tFrameId\u002B\u002B;\r\n\t\treturn count;\r\n\t}\r\n\r\n\tpublic static void Tick( float dt )\r\n\t{\r\n\t\tif ( !IsActive || dt \u003C= 0f )\r\n\t\t\treturn;\r\n\r\n\t\tif ( dt \u003E 0.05f )\r\n\t\t\tdt = 0.05f;\r\n\r\n\t\tif ( _bannerTimer \u003E 0f )\r\n\t\t{\r\n\t\t\t_bannerTimer -= dt;\r\n\t\t\tif ( _bannerTimer \u003C= 0f )\r\n\t\t\t\t_bannerVersion\u002B\u002B;\r\n\t\t}\r\n\r\n\t\t// Swim \u002B salinity for the tank you\u0027re watching.\r\n\t\tTickSlot( _active, dt, visual: true );\r\n\r\n\t\t// Off-screen tank still suffers salinity (cruel but fair).\r\n\t\tvar other = _active == _fresh ? _salt : _fresh;\r\n\t\tif ( other.Unlocked )\r\n\t\t\tTickSlot( other, dt, visual: false );\r\n\r\n\t\t// Decor tumble / throw settle (both tanks so mid-flight props keep moving).\r\n\t\tTickDecorPhysics( _active, dt );\r\n\t\tif ( _salt.Unlocked )\r\n\t\t\tTickDecorPhysics( _salt, dt );\r\n\r\n\t\tFrameId\u002B\u002B;\r\n\t}\r\n\r\n\tstatic void TickSlot( TankSlot slot, float dt, bool visual )\r\n\t{\r\n\t\tvar recoveredFromFight = 0;\r\n\t\tstring recoveredName = null;\r\n\r\n\t\tfor ( var i = slot.Fish.Count - 1; i \u003E= 0; i-- )\r\n\t\t{\r\n\t\t\tvar fish = slot.Fish[i];\r\n\t\t\tTickSalinity( slot, fish, dt );\r\n\r\n\t\t\tif ( fish.SickTimer \u003E= SickDeathSeconds \u0026\u0026 !fish.OnMeth )\r\n\t\t\t{\r\n\t\t\t\tvar name = fish.DisplayName;\r\n\t\t\t\tvar deathX = fish.X;\r\n\t\t\t\tvar deathY = fish.DrawY;\r\n\t\t\t\tslot.Fish.RemoveAt( i );\r\n\t\t\t\tNotifyFishDied();\r\n\t\t\t\t// She is mad at you \u2014 and she still takes the body for dinner.\r\n\t\t\t\tKarma.Add( -6f );\r\n\t\t\t\tChaos.Add( 3f );\r\n\t\t\t\tif ( visual || slot == _active )\r\n\t\t\t\t{\r\n\t\t\t\t\tShowBanner( GrandmaAteLine( name ) );\r\n\t\t\t\t\tExplodeSystem.SpawnDeathPoof( deathX, deathY );\r\n\t\t\t\t\tWaterFx.Splash( deathX, deathY, 12, WaterLayer.Tank );\r\n\t\t\t\t}\r\n\t\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tTrainingSystem.TickFish( fish, dt );\r\n\t\t\tfish.TickGrowth( dt );\r\n\t\t\tfish.TickFat( dt );\r\n\t\t\tfish.TickStoned( dt );\r\n\t\t\tif ( fish.TickFightInjury( dt ) )\r\n\t\t\t{\r\n\t\t\t\trecoveredFromFight\u002B\u002B;\r\n\t\t\t\trecoveredName ??= fish.DisplayName;\r\n\t\t\t}\r\n\r\n\t\t\tif ( visual )\r\n\t\t\t\tIntegrate( fish, dt, slot );\r\n\t\t}\r\n\r\n\t\t// One banner for fight cooldown ending (not one per fish).\r\n\t\tif ( recoveredFromFight \u003E 0 \u0026\u0026 (visual || slot == _active) )\r\n\t\t{\r\n\t\t\tif ( recoveredFromFight == 1 \u0026\u0026 !string.IsNullOrEmpty( recoveredName ) )\r\n\t\t\t\tShowBanner( $\u0022{recoveredName} is fight-ready again.\u0022 );\r\n\t\t\telse\r\n\t\t\t\tShowBanner( $\u0022{recoveredFromFight} fish ready to fight again.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Notice );\r\n\t\t}\r\n\r\n\t\tif ( visual \u0026\u0026 slot.Fish.Count \u003E 0 )\r\n\t\t{\r\n\t\t\t// Per-fish wander: high Agility turns more often and sharper (via NudgeHeading).\r\n\t\t\t// Menu-focused fish: no thrash turns \u2014 player is trying to click buttons.\r\n\t\t\tforeach ( var fish in slot.Fish )\r\n\t\t\t{\r\n\t\t\t\tif ( IsMenuFocused( fish.InstanceId ) )\r\n\t\t\t\t\tcontinue;\r\n\r\n\t\t\t\tif ( fish.OnMeth )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( _rng.NextDouble() \u003C dt * (3.2f \u002B fish.Agility * 0.25f) )\r\n\t\t\t\t\t\tNudgeHeadingMeth( fish );\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( FeedSystem.TryGetNearestPellet( fish.X, fish.Y, out _, out _ ) )\r\n\t\t\t\t\tcontinue;\r\n\r\n\t\t\t\tif ( _rng.NextDouble() \u003C dt * fish.WanderRate )\r\n\t\t\t\t\tNudgeHeading( fish );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Aboms snack on smaller tankmates (both tanks \u2014 off-screen still gets eaten).\r\n\t\tTickPredation( slot, dt, visual );\r\n\r\n\t\t// Rare courtship \u2192 fat pregnant \u2192 eggs \u2192 guppies (fresh tanks).\r\n\t\tBreedingSystem.TickSlot( slot, dt, visual );\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Monster fish occasionally swallow smaller tankmates.\r\n\t/// Prey must be lighter by mass; predator gains stats \u002B permanent bulk/size.\r\n\t/// \u003C/summary\u003E\r\n\tstatic void TickPredation( TankSlot slot, float dt, bool visual )\r\n\t{\r\n\t\t// Need a spare body in the tank \u2014 don\u0027t vacuum a 2-fish starter abom setup.\r\n\t\tif ( slot is null || slot.Fish.Count \u003C 3 || dt \u003C= 0f )\r\n\t\t\treturn;\r\n\r\n\t\t// Snapshot monsters \u2014 list mutates when someone gets eaten.\r\n\t\tvar monsters = slot.Fish.Where( f =\u003E f.IsMonster \u0026\u0026 f.Species is not null ).ToList();\r\n\t\tif ( monsters.Count == 0 )\r\n\t\t\treturn;\r\n\r\n\t\tforeach ( var predator in monsters )\r\n\t\t{\r\n\t\t\tif ( !slot.Fish.Contains( predator ) )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tif ( predator.PredationCooldown \u003E 0f )\r\n\t\t\t{\r\n\t\t\t\tpredator.PredationCooldown = MathF.Max( 0f, predator.PredationCooldown - dt );\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\t// ~once every ~70\u2013120s base; hungrier when jacked / meth / already bulked.\r\n\t\t\t// Soft early so new aboms don\u0027t instantly eat the last combine partner.\r\n\t\t\tvar hunger = 0.009f\r\n\t\t\t\t\u002B predator.PredationBulkNorm * 0.005f\r\n\t\t\t\t\u002B (predator.Jacked ? 0.006f : 0f)\r\n\t\t\t\t\u002B (predator.OnMeth ? 0.01f : 0f)\r\n\t\t\t\t\u002B (predator.IsSick || predator.IsFightInjured ? -0.006f : 0f);\r\n\t\t\thunger = Math.Clamp( hunger, 0.003f, 0.03f );\r\n\t\t\tif ( _rng.NextDouble() \u003E hunger * dt )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tvar prey = PickPredationPrey( slot, predator );\r\n\t\t\tif ( prey is null )\r\n\t\t\t{\r\n\t\t\t\t// No snack this roll \u2014 short retry so they don\u0027t spam checks.\r\n\t\t\t\tpredator.PredationCooldown = 4f \u002B (float)_rng.NextDouble() * 6f;\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\t// Must be somewhat close (or meth thrash reaches further).\r\n\t\t\tvar dx = prey.X - predator.X;\r\n\t\t\tvar dy = prey.Y - predator.Y;\r\n\t\t\tvar dist = MathF.Sqrt( dx * dx \u002B dy * dy );\r\n\t\t\tvar reach = 70f \u002B predator.MassScore * 2.5f \u002B (predator.OnMeth ? 40f : 0f);\r\n\t\t\tif ( dist \u003E reach )\r\n\t\t\t{\r\n\t\t\t\t// Lunge toward them for next time.\r\n\t\t\t\tvar len = MathF.Max( 1f, dist );\r\n\t\t\t\tvar lunge = predator.Species.Speed * predator.SpeedMoveMul * 1.8f;\r\n\t\t\t\tpredator.Vx = dx / len * lunge;\r\n\t\t\t\tpredator.Vy = dy / len * lunge * 0.7f;\r\n\t\t\t\tpredator.PredationCooldown = 1.2f \u002B (float)_rng.NextDouble() * 2f;\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tTryMonsterEat( slot, predator, prey, visual );\r\n\t\t}\r\n\t}\r\n\r\n\tstatic FishActor PickPredationPrey( TankSlot slot, FishActor predator )\r\n\t{\r\n\t\tif ( predator?.Species is null )\r\n\t\t\treturn null;\r\n\r\n\t\tvar predMass = predator.MassScore;\r\n\t\tFishActor best = null;\r\n\t\tvar bestScore = float.MaxValue;\r\n\r\n\t\tforeach ( var f in slot.Fish )\r\n\t\t{\r\n\t\t\tif ( f is null || f.InstanceId == predator.InstanceId )\r\n\t\t\t\tcontinue;\r\n\t\t\t// Don\u0027t eat other apex monsters unless much smaller (evo chains stay safe).\r\n\t\t\tif ( f.IsMonster \u0026\u0026 f.MassScore \u003E= predMass * 0.55f )\r\n\t\t\t\tcontinue;\r\n\t\t\tif ( f.MassScore \u003E= predMass * 0.72f )\r\n\t\t\t\tcontinue;\r\n\t\t\t// Prefer soft targets: smaller mass \u002B closer.\r\n\t\t\tvar dx = f.X - predator.X;\r\n\t\t\tvar dy = f.Y - predator.Y;\r\n\t\t\tvar dist = MathF.Sqrt( dx * dx \u002B dy * dy );\r\n\t\t\tvar score = f.MassScore * 3f \u002B dist * 0.15f;\r\n\t\t\tif ( f.IsFightInjured || f.IsSick )\r\n\t\t\t\tscore *= 0.7f;\r\n\t\t\tif ( score \u003C bestScore )\r\n\t\t\t{\r\n\t\t\t\tbestScore = score;\r\n\t\t\t\tbest = f;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn best;\r\n\t}\r\n\r\n\tstatic void TryMonsterEat( TankSlot slot, FishActor predator, FishActor prey, bool visual )\r\n\t{\r\n\t\tif ( predator is null || prey is null || slot is null )\r\n\t\t\treturn;\r\n\t\tif ( !slot.Fish.Contains( predator ) || !slot.Fish.Contains( prey ) )\r\n\t\t\treturn;\r\n\r\n\t\tvar preyName = prey.DisplayName;\r\n\t\tvar predName = predator.DisplayName;\r\n\t\tvar preyMass = MathF.Max( 0.2f, prey.MassScore );\r\n\t\tvar predMass = MathF.Max( 0.5f, predator.MassScore );\r\n\t\tvar ratio = Math.Clamp( preyMass / predMass, 0.08f, 0.85f );\r\n\r\n\t\t// Stat siphon \u2014 bigger meals = bigger gains (capped).\r\n\t\tvar endGain = Math.Clamp( 0.15f \u002B prey.Endurance * 0.12f * ratio \u002B 0.08f, 0.12f, 0.85f );\r\n\t\tvar spdGain = Math.Clamp( 0.12f \u002B prey.SpeedStat * 0.1f * ratio \u002B 0.05f, 0.1f, 0.7f );\r\n\t\tvar agiGain = Math.Clamp( 0.12f \u002B prey.Agility * 0.1f * ratio \u002B 0.05f, 0.1f, 0.7f );\r\n\r\n\t\tvar beforeEnd = predator.Endurance;\r\n\t\tvar beforeSpd = predator.SpeedStat;\r\n\t\tvar beforeAgi = predator.Agility;\r\n\t\tpredator.Endurance = Math.Clamp( predator.Endurance \u002B endGain, 0.5f, 10f );\r\n\t\tpredator.SpeedStat = Math.Clamp( predator.SpeedStat \u002B spdGain, 0.5f, 10f );\r\n\t\tpredator.Agility = Math.Clamp( predator.Agility \u002B agiGain, 0.5f, 10f );\r\n\r\n\t\t// Permanent size bulk \u002B belly swell.\r\n\t\tvar bulkHit = Math.Clamp( 6f \u002B ratio * 14f \u002B (prey.IsMonster ? 4f : 0f), 5f, 22f );\r\n\t\tpredator.PredationBulk = Math.Clamp( predator.PredationBulk \u002B bulkHit, 0f, 100f );\r\n\t\tpredator.Fat = Math.Clamp( predator.Fat \u002B 10f \u002B ratio * 18f, 0f, 100f );\r\n\t\t// Nudge growth clock so snacks look older/meatier.\r\n\t\tpredator.AgeSeconds = MathF.Min(\r\n\t\t\tFishActor.MaxAgeSeconds,\r\n\t\t\tpredator.AgeSeconds \u002B 45f \u002B ratio * 90f );\r\n\t\tpredator.FishEatenCount\u002B\u002B;\r\n\r\n\t\t// Cooldown: bigger meal = longer digest (still hunts again later).\r\n\t\tpredator.PredationCooldown = 28f \u002B ratio * 40f \u002B (float)_rng.NextDouble() * 20f;\r\n\r\n\t\t// Lunge visual on the predator.\r\n\t\tpredator.Vx *= 0.4f;\r\n\t\tpredator.Vy *= 0.4f;\r\n\t\tpredator.BobAmp = MathF.Min( 12f, predator.BobAmp \u002B 1.5f );\r\n\r\n\t\tvar deathX = prey.X;\r\n\t\tvar deathY = prey.DrawY;\r\n\t\t// Cancel combine if either parent vanishes mid-pick.\r\n\t\tif ( FishFusion.PickingSecond\r\n\t\t\t\u0026\u0026 ( FishFusion.PendingId == prey.InstanceId || FishFusion.PendingId == predator.InstanceId ) )\r\n\t\t\tFishFusion.Cancel();\r\n\t\tslot.Fish.Remove( prey );\r\n\t\t// Drop M80 tape / fight pick if any.\r\n\t\tExplodeSystem.Untape( prey.InstanceId );\r\n\t\tBattleSystem.DeselectFish( prey.InstanceId );\r\n\r\n\t\t// Predation is not \u0022your fault\u0022 salinity death \u2014 grandma might even approve.\r\n\t\tChaos.Add( 2.5f \u002B ratio * 3f );\r\n\t\tKarma.Add( 0.6f \u002B (prey.IsMonster ? 0.4f : 0f) );\r\n\r\n\t\tif ( visual || slot == _active )\r\n\t\t{\r\n\t\t\tExplodeSystem.SpawnDeathPoof( deathX, deathY );\r\n\t\t\tWaterFx.Splash( deathX, deathY, 14, WaterLayer.Tank );\r\n\t\t\tvar de = predator.Endurance - beforeEnd;\r\n\t\t\tvar ds = predator.SpeedStat - beforeSpd;\r\n\t\t\tvar da = predator.Agility - beforeAgi;\r\n\t\t\tStatFloat.ShowSessionSummary( predator, de, ds, da );\r\n\t\t\tvar bulkPct = (int)MathF.Round( predator.PredationBulk );\r\n\t\t\tShowBanner( $\u0022{predName} ate {preyName}. Bulk {bulkPct}% \u00B7 stats up.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.M80 );\r\n\t\t\tGameAudio.OnMoodMaybeChanged( force: true );\r\n\t\t}\r\n\r\n\t\tFrameId\u002B\u002B;\r\n\t\tSaveGame.TrySave();\r\n\t}\r\n\r\n\tstatic void TickSalinity( TankSlot slot, FishActor fish, float dt )\r\n\t{\r\n\t\t// --- High ---\r\n\t\tif ( fish.OnMeth )\r\n\t\t{\r\n\t\t\tfish.MethTimer -= dt;\r\n\t\t\t// Habit creeps up while fried.\r\n\t\t\tfish.Addiction = Math.Clamp( fish.Addiction \u002B dt * 0.35f, 0f, 100f );\r\n\r\n\t\t\tif ( fish.MethTimer \u003C= 0f )\r\n\t\t\t{\r\n\t\t\t\tfish.OnMeth = false;\r\n\t\t\t\tfish.MethTimer = 0f;\r\n\t\t\t\t// Come-down crash \u2192 withdrawal if they\u0027ve built a habit.\r\n\t\t\t\tfish.Vx *= 0.28f;\r\n\t\t\t\tfish.Vy *= 0.28f;\r\n\t\t\t\tif ( fish.Addiction \u003E= 15f )\r\n\t\t\t\t{\r\n\t\t\t\t\tfish.WithdrawalTimer = 10f \u002B fish.AddictionNorm * 22f;\r\n\t\t\t\t\tif ( slot == _active )\r\n\t\t\t\t\t\tShowBanner( $\u0022{fish.Species.Name} crashed. Withdrawal has entered the chat.\u0022 );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tfish.SickTimer = 0f;\r\n\t\t\t\tfish.WithdrawalTimer = 0f;\r\n\t\t\t\tif ( _rng.NextDouble() \u003C dt * 0.35 )\r\n\t\t\t\t\tChaos.Add( 0.25f );\r\n\t\t\t}\r\n\t\t}\r\n\t\t// --- Withdrawal (sober but hurting) ---\r\n\t\telse if ( fish.WithdrawalTimer \u003E 0f )\r\n\t\t{\r\n\t\t\tfish.WithdrawalTimer -= dt;\r\n\t\t\t// Slow natural recovery of the habit while clean.\r\n\t\t\tfish.Addiction = MathF.Max( 0f, fish.Addiction - dt * 0.55f );\r\n\t\t\tif ( _rng.NextDouble() \u003C dt * 0.4 )\r\n\t\t\t\tChaos.Add( 0.15f \u002B fish.AddictionNorm * 0.2f );\r\n\r\n\t\t\t// Heavy addicts in withdrawal \u002B wrong water die faster.\r\n\t\t\tif ( fish.IsWrongWater( slot.Water ) )\r\n\t\t\t\tfish.SickTimer \u002B= dt * (1f \u002B fish.AddictionNorm * 0.85f);\r\n\t\t}\r\n\t\telse if ( fish.IsAddicted \u0026\u0026 !fish.OnMeth )\r\n\t\t{\r\n\t\t\t// Sober addict: very slow recovery, mild restlessness.\r\n\t\t\tfish.Addiction = MathF.Max( 0f, fish.Addiction - dt * 0.2f );\r\n\t\t}\r\n\r\n\t\tif ( !fish.IsWrongWater( slot.Water ) )\r\n\t\t{\r\n\t\t\tif ( !fish.OnMeth \u0026\u0026 !fish.InWithdrawal )\r\n\t\t\t\tfish.SickTimer = MathF.Max( 0f, fish.SickTimer - dt * 2f );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Wrong water without meth (and not already accelerating via withdrawal branch).\r\n\t\tif ( !fish.OnMeth \u0026\u0026 fish.WithdrawalTimer \u003C= 0f )\r\n\t\t\tfish.SickTimer \u002B= dt;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ERename a fish in either tank. Empty name clears nickname.\u003C/summary\u003E\r\n\tpublic static bool TryRenameFish( string instanceId, string nickname )\r\n\t{\r\n\t\tvar fish = FindFish( instanceId );\r\n\t\tif ( fish is null )\r\n\t\t\treturn false;\r\n\r\n\t\tif ( !fish.TrySetNickname( nickname ) )\r\n\t\t{\r\n\t\t\tShowBanner( \u0022Name needs letters/numbers only.\u0022 );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t// Quiet \u2014 fish menu title updates live; no banner spam while cycling names.\r\n\t\tSaveGame.TrySave();\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EKick / tap the whole glass \u2014 every swimmer scurries.\u003C/summary\u003E\r\n\tpublic static int KickAllFish()\r\n\t{\r\n\t\tif ( !IsActive || _active is null )\r\n\t\t\treturn 0;\r\n\t\tvar n = 0;\r\n\t\tforeach ( var f in _active.Fish )\r\n\t\t{\r\n\t\t\tif ( f is null || f.OnAdventure )\r\n\t\t\t\tcontinue;\r\n\t\t\tif ( PokeFish( f.InstanceId ) )\r\n\t\t\t\tn\u002B\u002B;\r\n\t\t}\r\n\t\tFrameId\u002B\u002B;\r\n\t\treturn n;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EClick a fish \u2014 it darts away. Does not feed.\u003C/summary\u003E\r\n\tpublic static bool PokeFish( string instanceId )\r\n\t{\r\n\t\tif ( !IsActive || string.IsNullOrEmpty( instanceId ) )\r\n\t\t\treturn false;\r\n\r\n\t\tFishActor fish = null;\r\n\t\tforeach ( var f in _active.Fish )\r\n\t\t{\r\n\t\t\tif ( f.InstanceId == instanceId )\r\n\t\t\t{\r\n\t\t\t\tfish = f;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( fish is null )\r\n\t\t\treturn false;\r\n\r\n\t\t// Burst away from center-ish with some randomness so they \u0022scurry\u0022.\r\n\t\tvar awayX = fish.X - Width * 0.5f;\r\n\t\tvar awayY = fish.Y - Height * 0.5f;\r\n\t\tif ( MathF.Abs( awayX ) \u003C 8f \u0026\u0026 MathF.Abs( awayY ) \u003C 8f )\r\n\t\t{\r\n\t\t\tawayX = (float)(_rng.NextDouble() * 2.0 - 1.0);\r\n\t\t\tawayY = (float)(_rng.NextDouble() * 2.0 - 1.0);\r\n\t\t}\r\n\r\n\t\t// Bias horizontal escape so facing flip is obvious.\r\n\t\tawayX \u002B= (float)(_rng.NextDouble() * 2.0 - 1.0) * 40f;\r\n\t\tawayY \u002B= (float)(_rng.NextDouble() * 2.0 - 1.0) * 18f;\r\n\t\tvar len = MathF.Sqrt( awayX * awayX \u002B awayY * awayY );\r\n\t\tif ( len \u003C 0.01f )\r\n\t\t\tlen = 1f;\r\n\r\n\t\tvar burst = fish.Species.Speed * fish.SpeedMoveMul * (2.6f \u002B (float)_rng.NextDouble() * 1.1f);\r\n\t\tif ( fish.Jacked )\r\n\t\t\tburst *= 1.25f;\r\n\t\tif ( fish.OnMeth )\r\n\t\t\tburst *= 1.35f;\r\n\t\t// Agile fish snap the dart harder; slow tanks lumber the escape.\r\n\t\tburst *= 0.85f \u002B fish.AgilityTurnMul * 0.2f;\r\n\r\n\t\tfish.Vx = awayX / len * burst;\r\n\t\tfish.Vy = awayY / len * burst * 0.72f;\r\n\t\t// High agility = shorter, snappier scurry; low = longer coast.\r\n\t\tfish.ScurryTimer = (0.4f \u002B (float)_rng.NextDouble() * 0.3f) * (1.15f - fish.AgilityNorm * 0.35f);\r\n\t\tfish.BobPhase \u002B= 1.2f;\r\n\t\tFrameId\u002B\u002B;\r\n\t\treturn true;\r\n\t}\r\n\r\n\tstatic void Integrate( FishActor fish, float dt, TankSlot slot )\r\n\t{\r\n\t\tvar trainSpd = fish.SpeedMoveMul;\r\n\t\tvar trainAgi = fish.AgilityTurnMul;\r\n\r\n\t\t// Fish menu open on this body: gentle hover so meth thrash doesn\u0027t steal clicks.\r\n\t\t// Visuals (meth aura / slime) still play \u2014 only locomotion calms.\r\n\t\tif ( IsMenuFocused( fish.InstanceId ) )\r\n\t\t{\r\n\t\t\tfish.ScurryTimer = 0f;\r\n\t\t\tfish.Vx *= MathF.Pow( 0.12f, dt );\r\n\t\t\tfish.Vy *= MathF.Pow( 0.12f, dt );\r\n\t\t\t// Soft bob only \u2014 no manic phase spin.\r\n\t\t\tfish.BobPhase \u002B= fish.BobSpeed * dt * 0.55f;\r\n\t\t\tfish.X \u002B= fish.Vx * dt * 0.35f;\r\n\t\t\tfish.Y \u002B= fish.Vy * dt * 0.35f;\r\n\t\t\tClampFish( fish, dt );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( fish.ScurryTimer \u003E 0f )\r\n\t\t{\r\n\t\t\tfish.ScurryTimer = MathF.Max( 0f, fish.ScurryTimer - dt );\r\n\t\t\t// Agile fish zigzag harder while darting.\r\n\t\t\tvar wiggle = 70f \u002B trainAgi * 55f;\r\n\t\t\tfish.Vx \u002B= ((float)_rng.NextDouble() - 0.5f) * wiggle * dt;\r\n\t\t\tfish.Vy \u002B= ((float)_rng.NextDouble() - 0.5f) * (wiggle * 0.8f) * dt;\r\n\t\t\tvar scurryBob = 2.4f \u002B fish.SpeedNorm;\r\n\t\t\tfish.BobPhase \u002B= fish.BobSpeed * dt * scurryBob;\r\n\t\t\tvar scurryMul = 1.2f \u002B trainSpd * 0.25f;\r\n\t\t\tfish.X \u002B= fish.Vx * dt * scurryMul;\r\n\t\t\tfish.Y \u002B= fish.Vy * dt * scurryMul;\r\n\t\t\tClampFish( fish, dt );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Seek flakes when present \u2014 trained speed actually chases faster.\r\n\t\tif ( FeedSystem.TryGetNearestPellet( fish.X, fish.Y, out var px, out var py ) )\r\n\t\t{\r\n\t\t\tvar dx = px - fish.X;\r\n\t\t\tvar dy = py - fish.Y;\r\n\t\t\tvar len = MathF.Sqrt( dx * dx \u002B dy * dy );\r\n\t\t\tif ( len \u003E 1f )\r\n\t\t\t{\r\n\t\t\t\tvar seek = fish.Species.Speed * trainSpd * 1.15f * FeedSystem.FishSpeedMul;\r\n\t\t\t\t// Agile fish cut corners toward the pellet (blend less with old vel).\r\n\t\t\t\tvar wantVx = dx / len * seek;\r\n\t\t\t\tvar wantVy = dy / len * seek * 0.85f;\r\n\t\t\t\tvar snap = Math.Clamp( fish.TurnSnap, 0.35f, 1f );\r\n\t\t\t\tfish.Vx \u002B= (wantVx - fish.Vx) * snap;\r\n\t\t\t\tfish.Vy \u002B= (wantVy - fish.Vy) * snap;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Meth zoomies / withdrawal shuffle / sick fade / food buzz.\r\n\t\t// Trained Speed is the base swim multiplier for healthy fish.\r\n\t\tvar speedMul = FeedSystem.FishSpeedMul * trainSpd;\r\n\t\tvar bobMul = FeedSystem.FishBobMul;\r\n\t\t// Fast fish bob harder (exertion); tanky fish bob slower/steadier.\r\n\t\tbobMul *= 1f \u002B fish.SpeedNorm * 0.35f - fish.EnduranceNorm * 0.12f;\r\n\r\n\t\t// Continuous micro-steering: agile fish weave; clumsy fish hold a line.\r\n\t\tif ( !fish.OnMeth \u0026\u0026 !fish.Stoned \u0026\u0026 !fish.IsSick \u0026\u0026 !fish.IsFightInjured )\r\n\t\t{\r\n\t\t\tvar weave = 12f \u002B trainAgi * 38f;\r\n\t\t\tfish.Vx \u002B= ((float)_rng.NextDouble() - 0.5f) * weave * dt;\r\n\t\t\tfish.Vy \u002B= ((float)_rng.NextDouble() - 0.5f) * (weave * 0.7f) * dt;\r\n\t\t}\r\n\r\n\t\t// Post-fight injury: sluggish \u002B limp bob until cooldown ends.\r\n\t\tif ( fish.IsFightInjured )\r\n\t\t{\r\n\t\t\tvar limp = 0.45f \u002B (1f - fish.FightInjuryProgress) * 0.35f;\r\n\t\t\tspeedMul *= limp;\r\n\t\t\tbobMul *= 1.4f \u002B fish.FightInjuryProgress * 0.8f;\r\n\t\t\tfish.Vy \u002B= 10f * fish.FightInjuryProgress * dt;\r\n\t\t}\r\n\r\n\t\t// Overfed chonks lumber \u2014 still swim, just slower and bouncier.\r\n\t\tif ( fish.FatNorm \u003E 0.2f )\r\n\t\t{\r\n\t\t\tspeedMul *= 1f - fish.FatNorm * 0.32f;\r\n\t\t\tbobMul *= 1f \u002B fish.FatNorm * 0.9f;\r\n\t\t\tfish.Vy \u002B= fish.FatNorm * 8f * dt; // sink a little under their own weight\r\n\t\t}\r\n\t\tif ( fish.Jacked \u0026\u0026 !fish.OnMeth )\r\n\t\t{\r\n\t\t\tspeedMul *= 1.45f \u002B fish.SpeedStat * 0.05f;\r\n\t\t\tbobMul *= 1.8f;\r\n\t\t\t// Bulk presence \u2014 slightly more aggressive wander\r\n\t\t\tfish.Vx \u002B= ((float)_rng.NextDouble() - 0.5f) * 40f * dt;\r\n\t\t}\r\n\r\n\t\tif ( fish.OnMeth )\r\n\t\t{\r\n\t\t\t// Base zoomies \u002B addiction makes them even more unhinged.\r\n\t\t\tspeedMul = (2.7f \u002B fish.AddictionNorm * 0.9f) * trainSpd;\r\n\t\t\tbobMul = 4.2f \u002B fish.AddictionNorm * 2f;\r\n\t\t\tif ( fish.Jacked )\r\n\t\t\t\tspeedMul \u002B= 0.4f;\r\n\t\t\tvar thrash = 220f \u002B fish.AddictionNorm * 120f \u002B trainAgi * 40f;\r\n\t\t\tfish.Vx \u002B= ((float)_rng.NextDouble() - 0.5f) * thrash * dt;\r\n\t\t\tfish.Vy \u002B= ((float)_rng.NextDouble() - 0.5f) * (thrash * 0.8f) * dt;\r\n\t\t\tvar spd = MathF.Sqrt( fish.Vx * fish.Vx \u002B fish.Vy * fish.Vy );\r\n\t\t\tvar want = fish.Species.Speed * trainSpd * (2.3f \u002B fish.AddictionNorm * 0.8f);\r\n\t\t\tif ( spd \u003C want \u0026\u0026 spd \u003E 0.01f )\r\n\t\t\t{\r\n\t\t\t\tfish.Vx *= want / spd;\r\n\t\t\t\tfish.Vy *= want / spd;\r\n\t\t\t}\r\n\t\t\telse if ( spd \u003C 0.01f )\r\n\t\t\t{\r\n\t\t\t\tNudgeHeadingMeth( fish );\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if ( fish.Stoned )\r\n\t\t{\r\n\t\t\t// Lava-lamp drift \u2014 slow, wavy, occasionally forgets where the wall is.\r\n\t\t\t// Trained speed still helps a little; agility becomes floaty weave.\r\n\t\t\tspeedMul *= 0.42f;\r\n\t\t\tbobMul *= 2.4f;\r\n\t\t\tfish.BobPhase \u002B= dt * 1.1f;\r\n\t\t\tfish.Vx \u002B= MathF.Sin( fish.BobPhase * 0.7f ) * (22f \u002B trainAgi * 10f) * dt;\r\n\t\t\tfish.Vy \u002B= MathF.Cos( fish.BobPhase * 0.55f ) * (18f \u002B trainAgi * 8f) * dt;\r\n\t\t\tfish.Vx *= 0.992f;\r\n\t\t\tfish.Vy *= 0.992f;\r\n\t\t}\r\n\t\telse if ( fish.InWithdrawal )\r\n\t\t{\r\n\t\t\t// Slow, twitchy, miserable \u2014 training barely helps.\r\n\t\t\tspeedMul = MathF.Max( 0.18f, (0.45f - fish.AddictionNorm * 0.2f) * (0.7f \u002B trainSpd * 0.3f) );\r\n\t\t\tbobMul = 1.6f \u002B fish.AddictionNorm;\r\n\t\t\tfish.Vx \u002B= ((float)_rng.NextDouble() - 0.5f) * 40f * dt;\r\n\t\t\tfish.Vy \u002B= ((float)_rng.NextDouble() - 0.5f) * 35f * dt;\r\n\t\t\tfish.Vy \u002B= 12f * dt; // sink a little, depressed\r\n\t\t}\r\n\t\telse if ( fish.IsSick )\r\n\t\t{\r\n\t\t\tvar p = fish.SickProgress;\r\n\t\t\tspeedMul = MathF.Max( 0.12f, (0.7f - p * 0.55f) * (0.75f \u002B trainSpd * 0.25f) );\r\n\t\t\tbobMul = 1f \u002B p * 2.8f;\r\n\t\t\tfish.Vx \u002B= ((float)_rng.NextDouble() - 0.5f) * (20f \u002B p * 140f) * dt;\r\n\t\t\tfish.Vy \u002B= ((float)_rng.NextDouble() - 0.5f) * (15f \u002B p * 100f) * dt;\r\n\t\t\tfish.Vy \u002B= (18f \u002B p * 55f) * dt;\r\n\t\t}\r\n\r\n\t\tfish.BobPhase \u002B= fish.BobSpeed * dt * bobMul;\r\n\t\tfish.X \u002B= fish.Vx * dt * speedMul;\r\n\t\tfish.Y \u002B= fish.Vy * dt * speedMul;\r\n\t\tClampFish( fish, dt );\r\n\t}\r\n\r\n\tstatic void ClampFish( FishActor fish, float dt )\r\n\t{\r\n\t\tvar halfW = fish.Species.Width * fish.DrawScaleX * 0.5f;\r\n\t\tvar halfH = fish.Species.Height * fish.DrawScaleY * 0.5f;\r\n\t\tvar minX = halfW \u002B 4f;\r\n\t\tvar maxX = Width - halfW - 4f;\r\n\t\tvar minY = halfH \u002B 6f;\r\n\t\tvar maxY = Height - halfH - 10f;\r\n\r\n\t\t// High agility = snappy wall bank; low = soft bounce (lose speed).\r\n\t\tvar bank = Math.Clamp( 0.55f \u002B fish.Agility * 0.05f, 0.55f, 1.05f );\r\n\r\n\t\tif ( fish.X \u003C minX )\r\n\t\t{\r\n\t\t\tfish.X = minX;\r\n\t\t\tfish.Vx = MathF.Abs( fish.Vx ) * bank;\r\n\t\t}\r\n\t\telse if ( fish.X \u003E maxX )\r\n\t\t{\r\n\t\t\tfish.X = maxX;\r\n\t\t\tfish.Vx = -MathF.Abs( fish.Vx ) * bank;\r\n\t\t}\r\n\r\n\t\tif ( fish.Y \u003C minY )\r\n\t\t{\r\n\t\t\tfish.Y = minY;\r\n\t\t\tfish.Vy = MathF.Abs( fish.Vy ) * bank;\r\n\t\t}\r\n\t\telse if ( fish.Y \u003E maxY )\r\n\t\t{\r\n\t\t\tfish.Y = maxY;\r\n\t\t\tfish.Vy = -MathF.Abs( fish.Vy ) * bank;\r\n\t\t}\r\n\r\n\t\tfish.Vy *= 1f - (0.15f * dt);\r\n\t\t// Cruise floor from species \u002B trained speed (food boost when fed).\r\n\t\tvar minSpeed = fish.Species.Speed * fish.SpeedMoveMul * 0.32f;\r\n\t\tif ( FeedSystem.IsBoosted )\r\n\t\t\tminSpeed *= FeedSystem.FishSpeedMul;\r\n\t\tvar speed = MathF.Sqrt( fish.Vx * fish.Vx \u002B fish.Vy * fish.Vy );\r\n\t\tif ( speed \u003C minSpeed \u0026\u0026 speed \u003E 0.001f )\r\n\t\t{\r\n\t\t\tvar scale = minSpeed / speed;\r\n\t\t\tfish.Vx *= scale;\r\n\t\t\tfish.Vy *= scale;\r\n\t\t}\r\n\t}\r\n\r\n\tstatic void NudgeHeading( FishActor fish )\r\n\t{\r\n\t\t// Turn within agility cone around current heading \u2014 high AGI = hard cut, low = gentle bank.\r\n\t\tvar cur = MathF.Atan2( fish.Vy, fish.Vx );\r\n\t\tif ( float.IsNaN( cur ) )\r\n\t\t\tcur = 0f;\r\n\t\tvar maxDelta = fish.MaxTurnRadians;\r\n\t\tvar delta = ((float)_rng.NextDouble() * 2f - 1f) * maxDelta;\r\n\t\tvar angle = cur \u002B delta;\r\n\t\tvar speed = fish.Species.Speed * fish.SpeedMoveMul * (0.7f \u002B (float)_rng.NextDouble() * 0.45f);\r\n\t\tvar wantVx = MathF.Cos( angle ) * speed;\r\n\t\tvar wantVy = MathF.Sin( angle ) * speed * (0.35f \u002B fish.AgilityNorm * 0.25f);\r\n\t\tvar snap = fish.TurnSnap;\r\n\t\tfish.Vx \u002B= (wantVx - fish.Vx) * snap;\r\n\t\tfish.Vy \u002B= (wantVy - fish.Vy) * snap;\r\n\t}\r\n\r\n\tstatic void NudgeHeadingMeth( FishActor fish )\r\n\t{\r\n\t\t// Manic sprint \u2014 still respects trained speed; agility widens thrash angles.\r\n\t\tvar cur = MathF.Atan2( fish.Vy, fish.Vx );\r\n\t\tif ( float.IsNaN( cur ) )\r\n\t\t\tcur = 0f;\r\n\t\tvar maxDelta = MathF.Min( MathF.PI * 0.95f, fish.MaxTurnRadians * 1.35f );\r\n\t\tvar delta = ((float)_rng.NextDouble() * 2f - 1f) * maxDelta;\r\n\t\tvar angle = cur \u002B delta;\r\n\t\tvar speed = fish.Species.Speed * fish.SpeedMoveMul * (2.2f \u002B (float)_rng.NextDouble() * 1.4f);\r\n\t\tvar wantVx = MathF.Cos( angle ) * speed;\r\n\t\tvar wantVy = MathF.Sin( angle ) * speed * 0.85f;\r\n\t\tvar snap = Math.Clamp( fish.TurnSnap \u002B 0.15f, 0.5f, 1f );\r\n\t\tfish.Vx \u002B= (wantVx - fish.Vx) * snap;\r\n\t\tfish.Vy \u002B= (wantVy - fish.Vy) * snap;\r\n\t}\r\n\r\n\tpublic static void ShowBanner( string text )\r\n\t{\r\n\t\t_banner = text ?? \u0022\u0022;\r\n\t\t// Short, discreet overlay \u2014 doesn\u0027t hold the screen hostage\r\n\t\t_bannerTimer = 2.4f;\r\n\t\t_bannerVersion\u002B\u002B;\r\n\t}\r\n\r\n\tstatic void ClearBanner()\r\n\t{\r\n\t\t_banner = \u0022\u0022;\r\n\t\t_bannerTimer = 0f;\r\n\t\t_bannerVersion\u002B\u002B;\r\n\t}\r\n}\r\n"},{"Ident":"dogecoin.nochillquarium","Path":"styles/form/_colorproperty.scss","FileName":"_colorproperty.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"\r\n\r\n.colorproperty\r\n{\r\n\talign-items: center;\r\n\tposition: relative;\r\n\r\n\t.colorsquare\r\n\t{\r\n\t\twidth: 20px;\r\n\t\theight: 20px;\r\n\t\tborder-radius: 4px;\r\n\t\tmargin-right: 8px;\r\n\t\tposition: absolute;\r\n\t\tleft: 7px;\r\n\t\tz-index: 1;\r\n\t\tcursor: pointer;\r\n\t}\r\n\r\n\t\u003E .textentry:not( .a.b.c )\r\n\t{\r\n\t\tpadding-left: 36px;\r\n\t}\r\n}"},{"Ident":"dogecoin.nochillquarium","Path":"ui/controls/color/colorsaturationvaluecontrol.cs.scss","FileName":"colorsaturationvaluecontrol.cs.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"ColorSaturationValueControl\r\n{\r\n\twidth: 240px;\r\n\theight: 240px;\r\n\tbackground-color: red;\r\n\tposition: relative;\r\n\tborder-radius: 4px;\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\twidth: 16px;\r\n\t\theight: 16px;\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% ) translateY( -50% );\r\n\t\tpointer-events: none;\r\n\t\tz-index: 100;\r\n\t\tz-index: 100;\r\n\t}\r\n\r\n\t.gradient\r\n\t{\r\n\t\tposition: absolute;\r\n\t\twidth: 100%;\r\n\t\theight: 100%;\r\n\t\tborder-radius: 4px;\r\n\t\tbackground: linear-gradient( to right, white, rgba( 255, 255, 255, 0 ) );\r\n\r\n\t\t\u0026:after\r\n\t\t{\r\n\t\t\tcontent: \u0022\u0022;\r\n\t\t\tposition: absolute;\r\n\t\t\twidth: 100%;\r\n\t\t\theight: 100%;\r\n\t\t\tborder-radius: 4px;\r\n\t\t\tbackground: linear-gradient( to top, black, rgba( 0, 0, 0, 0 ) );\r\n\t\t}\r\n\t}\r\n}\r\n"},{"Ident":"dogecoin.nochillquarium","Path":"ui/controls/color/colorhuecontrol.cs.scss","FileName":"colorhuecontrol.cs.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"ColorHueControl\r\n{\r\n\tgap: 0.5rem;\r\n\tflex-grow: 1;\r\n\tpointer-events: all;\r\n\tbackground: linear-gradient( to right, red, yellow, lime, cyan, blue, magenta, red );\r\n\tborder-radius: 4px;\r\n\tpadding: 2px;\r\n\theight: 12px;\r\n\tposition: relative;\r\n\tcursor: pointer;\r\n\tmargin: 10px 0;\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":"dogecoin.nochillquarium","Path":"Economy.cs","FileName":"Economy.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"namespace NoChillquarium;\n\n/// \u003Csummary\u003E\n/// Dogecoin wallet \u002B idle income from living fish.\n/// Real balance is always accurate; the HUD number settles calmly every couple seconds.\n/// \u003C/summary\u003E\npublic static class Economy\n{\n\tstatic readonly double[] MilestoneThresholds =\n\t{\n\t\t10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000\n\t};\n\n\t/// \u003Csummary\u003EHow often idle income refreshes the visible wallet (seconds).\u003C/summary\u003E\n\tconst float DisplaySettleInterval = 1.75f;\n\n\tstatic double _balance;\n\tstatic double _shownBalance;\n\tstatic double _lifetimeEarned;\n\tstatic double _nextMilestone = 10;\n\tstatic float _playtimeSeconds;\n\tstatic float _autosaveTimer;\n\tstatic float _displayTimer;\n\tstatic float _pendingShowDelay;\n\tstatic float _walletPulse;\n\tstatic int _displayVersion;\n\n\tpublic static double Balance =\u003E _balance;\n\t/// \u003Csummary\u003ECalm on-screen balance (settles every few seconds).\u003C/summary\u003E\n\tpublic static double ShownBalance =\u003E _shownBalance;\n\tpublic static double LifetimeEarned =\u003E _lifetimeEarned;\n\tpublic static float PlaytimeSeconds =\u003E _playtimeSeconds;\n\tpublic static int DisplayVersion =\u003E _displayVersion;\n\tpublic static double IncomePerSecond =\u003E TankSim.IncomePerSecond * FeedSystem.IncomeMultiplier;\n\n\t/// \u003Csummary\u003ETrue while the dogecoin cold wallet is doing a soft settle pulse.\u003C/summary\u003E\n\tpublic static bool WalletPulse =\u003E _walletPulse \u003E 0.04f;\n\n\t/// \u003Csummary\u003ESettled HUD balance (not the frantic live accrual).\u003C/summary\u003E\n\tpublic static string BalanceText =\u003E FormatDoge( _shownBalance );\n\n\tpublic static string IncomeText\n\t{\n\t\tget\n\t\t{\n\t\t\tvar rate = IncomePerSecond;\n\t\t\t// Round rate for a calm readout \u2014 not three decimals twitching.\n\t\t\tvar shownRate = rate \u003E= 10 ? Math.Round( rate, 1 ) : Math.Round( rate, 2 );\n\t\t\tvar text = $\u0022\u002B{FormatDoge( shownRate )}/s\u0022;\n\t\t\tif ( FeedSystem.IsBoosted )\n\t\t\t\ttext \u002B= \u0022 FED\u0022;\n\t\t\tif ( TankSim.SaltUnlocked )\n\t\t\t\ttext \u002B= \u0022 ALL TANKS\u0022;\n\t\t\treturn text;\n\t\t}\n\t}\n\n\tpublic static void ResetForNewGame()\n\t{\n\t\t// Free starter flakes \u002B room for a first guppy after the first feed tip.\n\t\t// Dead economy in minute one is how idle games lose the room.\n\t\t_balance = 26;\n\t\t_shownBalance = 26;\n\t\t_lifetimeEarned = 0;\n\t\t_playtimeSeconds = 0;\n\t\t_autosaveTimer = 0;\n\t\t_displayTimer = 0;\n\t\t_pendingShowDelay = 0;\n\t\t_walletPulse = 0;\n\t\t_nextMilestone = MilestoneThresholds[0];\n\t\tFeedSystem.Clear();\n\t\tExplodeSystem.Clear();\n\t\tDogeFloat.Clear();\n\t\tBumpDisplay();\n\t}\n\n\tpublic static void LoadFromSave( SaveData data )\n\t{\n\t\tif ( data is null )\n\t\t{\n\t\t\tResetForNewGame();\n\t\t\treturn;\n\t\t}\n\n\t\t_balance = Math.Max( 0, data.Dogecoin );\n\t\t_shownBalance = _balance;\n\t\t_lifetimeEarned = Math.Max( 0, data.LifetimeEarned );\n\t\t_playtimeSeconds = Math.Max( 0, data.PlaytimeSeconds );\n\t\t_autosaveTimer = 0;\n\t\t_displayTimer = 0;\n\t\t_pendingShowDelay = 0;\n\t\t_walletPulse = 0;\n\t\tRecalcNextMilestone();\n\t\tBumpDisplay();\n\t}\n\n\tpublic static void WriteToSave( SaveData data )\n\t{\n\t\tif ( data is null )\n\t\t\treturn;\n\n\t\tdata.Dogecoin = _balance;\n\t\tdata.LifetimeEarned = _lifetimeEarned;\n\t\tdata.PlaytimeSeconds = _playtimeSeconds;\n\t}\n\n\tpublic static void Tick( float dt )\n\t{\n\t\tif ( !TankSim.IsActive || dt \u003C= 0f )\n\t\t\treturn;\n\n\t\tif ( dt \u003E 0.05f )\n\t\t\tdt = 0.05f;\n\n\t\t_playtimeSeconds \u002B= dt;\n\n\t\tvar rate = IncomePerSecond;\n\t\tif ( rate \u003E 0 )\n\t\t{\n\t\t\tvar gained = rate * dt;\n\t\t\t_balance \u002B= gained;\n\t\t\t_lifetimeEarned \u002B= gained;\n\t\t\t// Idle drip \u2014 advance milestones silently (no bark spam).\n\t\t\tCheckMilestones( playSound: false );\n\t\t}\n\n\t\t_autosaveTimer \u002B= dt;\n\t\tif ( _autosaveTimer \u003E= 15f )\n\t\t{\n\t\t\t_autosaveTimer = 0f;\n\t\t\tSaveGame.TrySave( quiet: true );\n\t\t}\n\n\t\tif ( _walletPulse \u003E 0f )\n\t\t\t_walletPulse = MathF.Max( 0f, _walletPulse - dt * 1.35f );\n\n\t\t// Explicit earn: short pause so the float can appear, then settle the number.\n\t\tif ( _pendingShowDelay \u003E 0f )\n\t\t{\n\t\t\t_pendingShowDelay -= dt;\n\t\t\tif ( _pendingShowDelay \u003C= 0f )\n\t\t\t{\n\t\t\t\t_pendingShowDelay = 0f;\n\t\t\t\tSettleShown( soft: true );\n\t\t\t\t_displayTimer = 0f;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Idle: only refresh the chip every couple seconds \u2014 calm ticker, not a slot machine.\n\t\t\t_displayTimer \u002B= dt;\n\t\t\tif ( _displayTimer \u003E= DisplaySettleInterval )\n\t\t\t{\n\t\t\t\t_displayTimer = 0f;\n\t\t\t\tSettleShown( soft: true );\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic static bool TrySpend( double amount )\n\t{\n\t\tif ( amount \u003C= 0 )\n\t\t\treturn true;\n\n\t\tif ( _balance \u003C amount )\n\t\t\treturn false;\n\n\t\t_balance -= amount;\n\t\tDogeFloat.NotifySpend( amount );\n\t\t// Spends snap now \u2014 you always trust the number after a purchase.\n\t\t_pendingShowDelay = 0f;\n\t\t_displayTimer = 0f;\n\t\tSettleShown( soft: false );\n\t\treturn true;\n\t}\n\n\tpublic static void Add( double amount )\n\t{\n\t\tif ( amount \u003C= 0 )\n\t\t\treturn;\n\n\t\t_balance \u002B= amount;\n\t\t_lifetimeEarned \u002B= amount;\n\t\t// Explicit payouts only (not idle drip) \u2014 pleasant/meme floats \u002B milestone SFX.\n\t\tDogeFloat.NotifyEarn( amount );\n\t\tCheckMilestones( playSound: true );\n\t\t// Let the float lead, then the wallet ticks up with a soft pulse.\n\t\t_pendingShowDelay = 0.45f;\n\t\tBumpDisplay();\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Explicit payout that always floats \u002B\u00D0 (sells, fight wins, stage tips).\n\t/// Same wallet math as \u003Csee cref=\u0022Add\u0022/\u003E.\n\t/// \u003C/summary\u003E\n\tpublic static void AddForced( double amount )\n\t{\n\t\tif ( amount \u003C= 0 )\n\t\t\treturn;\n\n\t\t_balance \u002B= amount;\n\t\t_lifetimeEarned \u002B= amount;\n\t\tDogeFloat.ForceEarn( amount );\n\t\tCheckMilestones( playSound: true );\n\t\t_pendingShowDelay = 0.35f;\n\t\tBumpDisplay();\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Push the visible balance toward the real one.\n\t/// Soft = only if it changed enough to matter; hard = always (spends).\n\t/// \u003C/summary\u003E\n\tstatic void SettleShown( bool soft )\n\t{\n\t\tvar delta = Math.Abs( _balance - _shownBalance );\n\t\tif ( soft \u0026\u0026 delta \u003C 0.05 )\n\t\t\treturn;\n\n\t\t_shownBalance = _balance;\n\t\t_walletPulse = soft ? 0.85f : 1f;\n\t\tBumpDisplay();\n\t}\n\n\tstatic void CheckMilestones( bool playSound )\n\t{\n\t\twhile ( _balance \u003E= _nextMilestone )\n\t\t{\n\t\t\tvar hit = _nextMilestone;\n\t\t\tif ( playSound )\n\t\t\t{\n\t\t\t\t// Bark only for chunky wallet thresholds; small ones soft coin.\n\t\t\t\tif ( hit \u003E= 100 )\n\t\t\t\t\tGameAudio.PlayDogeBark();\n\t\t\t\telse\n\t\t\t\t\tGameAudio.PlayCoin();\n\t\t\t}\n\t\t\tAdvanceMilestone();\n\t\t}\n\t}\n\n\tstatic void AdvanceMilestone()\n\t{\n\t\tfor ( var i = 0; i \u003C MilestoneThresholds.Length; i\u002B\u002B )\n\t\t{\n\t\t\tif ( MilestoneThresholds[i] \u003E _nextMilestone \u002B 0.001 )\n\t\t\t{\n\t\t\t\t_nextMilestone = MilestoneThresholds[i];\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Beyond table: keep doubling.\n\t\t_nextMilestone = Math.Max( _nextMilestone * 2.0, _nextMilestone \u002B 10000 );\n\t}\n\n\tstatic void RecalcNextMilestone()\n\t{\n\t\t_nextMilestone = MilestoneThresholds[0];\n\t\tforeach ( var t in MilestoneThresholds )\n\t\t{\n\t\t\tif ( _balance \u003C t )\n\t\t\t{\n\t\t\t\t_nextMilestone = t;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t_nextMilestone = Math.Max( _balance * 2.0, MilestoneThresholds[^1] * 2.0 );\n\t}\n\n\tstatic void BumpDisplay() =\u003E _displayVersion\u002B\u002B;\n\n\tpublic static string FormatDoge( double value )\n\t{\n\t\tif ( value \u003E= 1_000_000_000 )\n\t\t\treturn $\u0022{value / 1_000_000_000:0.##}B\u0022;\n\t\tif ( value \u003E= 1_000_000 )\n\t\t\treturn $\u0022{value / 1_000_000:0.##}M\u0022;\n\t\tif ( value \u003E= 10_000 )\n\t\t\treturn $\u0022{value / 1000:0.#}K\u0022;\n\t\tif ( value \u003E= 100 )\n\t\t\treturn $\u0022{value:0}\u0022;\n\t\tif ( value \u003E= 10 )\n\t\t\treturn $\u0022{value:0.#}\u0022;\n\t\treturn $\u0022{value:0.##}\u0022;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Extra-compact form for the fixed LCD (keeps glyph count low so text stays large).\n\t/// Prefer abbreviations earlier than general HUD formatting.\n\t/// \u003C/summary\u003E\n\tpublic static string FormatDogeLcd( double value )\n\t{\n\t\tvalue = Math.Abs( value );\n\t\tif ( value \u003E= 1_000_000_000 )\n\t\t\treturn $\u0022{value / 1_000_000_000:0.#}B\u0022;\n\t\tif ( value \u003E= 1_000_000 )\n\t\t\treturn $\u0022{value / 1_000_000:0.#}M\u0022;\n\t\t// LCD switches to K earlier so we don\u0027t show 5\u20136 raw digits\n\t\tif ( value \u003E= 1_000 )\n\t\t\treturn $\u0022{value / 1000:0.#}K\u0022;\n\t\tif ( value \u003E= 100 )\n\t\t\treturn $\u0022{value:0}\u0022;\n\t\tif ( value \u003E= 10 )\n\t\t\treturn $\u0022{value:0.#}\u0022;\n\t\treturn $\u0022{value:0.##}\u0022;\n\t}\n}\n"},{"Ident":"dogecoin.nochillquarium","Path":"PipeCrawl.cs","FileName":"PipeCrawl.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"namespace NoChillquarium;\n\npublic enum CrawlKind\n{\n\tEnemy,\n\tHazard,\n\tLoot,\n\tBoss\n}\n\npublic enum CrawlOutcome\n{\n\tIdle,\n\tPlaying,\n\tCleared,\n\tDead\n}\n\npublic sealed class CrawlThing\n{\n\tpublic CrawlKind Kind;\n\tpublic string SpriteId = \u0022adv_mite\u0022;\n\tpublic string Name = \u0022\u0022;\n\tpublic float X;\n\tpublic float Y;\n\tpublic float W = 48f;\n\tpublic float H = 36f;\n\tpublic float Vx = -160f;\n\tpublic int HitsLeft = 1;\n\tpublic bool Dead;\n\tpublic float Pop;\n\tpublic double Payout;\n\tpublic bool Resolved;\n\tpublic string CssKind =\u003E Kind switch\n\t{\n\t\tCrawlKind.Boss =\u003E \u0022kind-boss\u0022,\n\t\tCrawlKind.Hazard =\u003E \u0022kind-hazard\u0022,\n\t\tCrawlKind.Loot =\u003E \u0022kind-loot\u0022,\n\t\t_ =\u003E \u0022kind-enemy\u0022\n\t};\n}\n\n/// \u003Csummary\u003E\n/// Adventure pipe crawl \u2014 chomp timing, not click-to-continue.\n/// One run of HP across rooms. Hits cost real health. Combo pays.\n/// \u003C/summary\u003E\npublic static class PipeCrawl\n{\n\tpublic const float FieldW = 640f;\n\tpublic const float FieldH = 196f;\n\tpublic const float BiteLo = 148f;\n\tpublic const float BiteHi = 258f;\n\tpublic const float FishX = 70f;\n\tpublic const float FishY = 92f;\n\n\tstatic readonly Random _rng = new();\n\tstatic readonly List\u003CCrawlThing\u003E _things = new();\n\tstatic readonly List\u003C(float Delay, CrawlThing Thing)\u003E _queue = new();\n\n\tstatic CrawlOutcome _outcome = CrawlOutcome.Idle;\n\tstatic int _hp;\n\tstatic int _maxHp;\n\tstatic int _combo;\n\tstatic int _bestCombo;\n\tstatic int _chomps;\n\tstatic int _perfects;\n\tstatic int _version;\n\tstatic float _cooldown;\n\tstatic float _hurtFlash;\n\tstatic float _chompFlash;\n\tstatic float _bob;\n\tstatic float _clearHold;\n\tstatic float _invuln;\n\tstatic float _bitePad;\n\tstatic string _shout = \u0022\u0022;\n\tstatic float _shoutTimer;\n\tstatic string _fishSprite = \u0022goldfish\u0022;\n\tstatic string _roomTitle = \u0022\u0022;\n\tstatic double _waveLoot;\n\tstatic bool _bossWave;\n\n\tpublic static int Version =\u003E _version;\n\tpublic static CrawlOutcome Outcome =\u003E _outcome;\n\tpublic static bool IsPlaying =\u003E _outcome == CrawlOutcome.Playing;\n\tpublic static int Hp =\u003E _hp;\n\tpublic static int MaxHp =\u003E _maxHp;\n\tpublic static int Combo =\u003E _combo;\n\tpublic static int BestCombo =\u003E _bestCombo;\n\tpublic static int Chomps =\u003E _chomps;\n\tpublic static int Perfects =\u003E _perfects;\n\tpublic static float HurtFlash =\u003E _hurtFlash;\n\tpublic static float ChompFlash =\u003E _chompFlash;\n\tpublic static float Bob =\u003E _bob;\n\tpublic static string Shout =\u003E _shoutTimer \u003E 0f ? _shout : \u0022\u0022;\n\tpublic static string FishSprite =\u003E _fishSprite;\n\tpublic static string RoomTitle =\u003E _roomTitle;\n\tpublic static double WaveLoot =\u003E _waveLoot;\n\tpublic static bool BossWave =\u003E _bossWave;\n\tpublic static IReadOnlyList\u003CCrawlThing\u003E Things =\u003E _things;\n\n\t/// \u003Csummary\u003ENearest living thing still ahead of the fish \u2014 drives gutter art.\u003C/summary\u003E\n\tpublic static CrawlThing FocusThing\n\t{\n\t\tget\n\t\t{\n\t\t\tCrawlThing best = null;\n\t\t\tvar bestX = 9999f;\n\t\t\tforeach ( var t in _things )\n\t\t\t{\n\t\t\t\tif ( t is null || t.Dead )\n\t\t\t\t\tcontinue;\n\t\t\t\tif ( t.X \u002B t.W \u003C 8f )\n\t\t\t\t\tcontinue;\n\t\t\t\tif ( t.X \u003C bestX )\n\t\t\t\t{\n\t\t\t\t\tbestX = t.X;\n\t\t\t\t\tbest = t;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn best;\n\t\t}\n\t}\n\tpublic static float BiteLoPx =\u003E BiteLo - _bitePad;\n\tpublic static float BiteHiPx =\u003E BiteHi \u002B _bitePad;\n\tpublic static float BiteWidth =\u003E BiteHiPx - BiteLoPx;\n\tpublic static bool InBiteWindow =\u003E\n\t\t_things.Exists( t =\u003E !t.Dead \u0026\u0026 !t.Resolved \u0026\u0026 InBite( t ) );\n\n\tpublic static IEnumerable\u003Cbool\u003E HpPips\n\t{\n\t\tget\n\t\t{\n\t\t\tfor ( var i = 0; i \u003C _maxHp; i\u002B\u002B )\n\t\t\t\tyield return i \u003C _hp;\n\t\t}\n\t}\n\n\tpublic static void Stop()\n\t{\n\t\t_outcome = CrawlOutcome.Idle;\n\t\t_things.Clear();\n\t\t_queue.Clear();\n\t\t_shout = \u0022\u0022;\n\t\t_shoutTimer = 0f;\n\t\t_waveLoot = 0;\n\t\t_clearHold = 0f;\n\t\t_version\u002B\u002B;\n\t}\n\n\t/// \u003Csummary\u003EEnd the location run (HP gone). Next map calls BeginRun again.\u003C/summary\u003E\n\tpublic static void EndRun()\n\t{\n\t\tStop();\n\t\t_hp = 0;\n\t\t_maxHp = 0;\n\t}\n\n\tpublic static void BeginRun( FishActor lead )\n\t{\n\t\t_fishSprite = lead?.DisplaySpriteId;\n\t\tif ( string.IsNullOrEmpty( _fishSprite ) )\n\t\t\t_fishSprite = \u0022goldfish\u0022;\n\n\t\tvar end = lead?.Endurance ?? 3f;\n\t\t_maxHp = Math.Clamp( 2 \u002B (int)(end / 2.6f), 2, 6 );\n\t\tif ( lead?.Jacked == true )\n\t\t\t_maxHp\u002B\u002B;\n\t\t_hp = _maxHp;\n\t\t_combo = 0;\n\t\t_bestCombo = 0;\n\t\t_chomps = 0;\n\t\t_perfects = 0;\n\t\t_waveLoot = 0;\n\t\t_hurtFlash = 0f;\n\t\t_invuln = 0f;\n\t\t_bitePad = 8f \u002B (lead?.AgilityNorm ?? 0.3f) * 22f;\n\t\t_outcome = CrawlOutcome.Idle;\n\t\t_things.Clear();\n\t\t_queue.Clear();\n\t\t_version\u002B\u002B;\n\t}\n\n\tpublic static void StartWave( LotRoomDef room, int roomIndex, int roomCount )\n\t{\n\t\t_things.Clear();\n\t\t_queue.Clear();\n\t\t_waveLoot = 0;\n\t\t_clearHold = 0f;\n\t\t_outcome = CrawlOutcome.Playing;\n\t\t_roomTitle = room?.Title ?? \u0022Pipes\u0022;\n\t\t_bossWave = room?.Kind == LotRoomKind.Boss;\n\t\tBuildWave( room, roomIndex, roomCount );\n\t\tYell( _bossWave ? \u0022BOSS\u0022 : \u0022CHOMP\u0022, 0.7f );\n\t\t_version\u002B\u002B;\n\t}\n\n\tpublic static void Tick( float dt )\n\t{\n\t\tif ( _outcome != CrawlOutcome.Playing || dt \u003C= 0f )\n\t\t\treturn;\n\n\t\t_bob \u002B= dt * 7.2f;\n\t\tif ( _cooldown \u003E 0f )\n\t\t\t_cooldown -= dt;\n\t\tif ( _hurtFlash \u003E 0f )\n\t\t\t_hurtFlash -= dt;\n\t\tif ( _chompFlash \u003E 0f )\n\t\t\t_chompFlash -= dt;\n\t\tif ( _invuln \u003E 0f )\n\t\t\t_invuln -= dt;\n\t\tif ( _shoutTimer \u003E 0f )\n\t\t\t_shoutTimer -= dt;\n\n\t\tif ( _clearHold \u003E 0f )\n\t\t{\n\t\t\t_clearHold -= dt;\n\t\t\tif ( _clearHold \u003C= 0f )\n\t\t\t{\n\t\t\t\t_outcome = CrawlOutcome.Cleared;\n\t\t\t\t_version\u002B\u002B;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Spawn queued threats.\n\t\tfor ( var i = _queue.Count - 1; i \u003E= 0; i-- )\n\t\t{\n\t\t\tvar q = _queue[i];\n\t\t\tq.Delay -= dt;\n\t\t\tif ( q.Delay \u003C= 0f )\n\t\t\t{\n\t\t\t\t_things.Add( q.Thing );\n\t\t\t\t_queue.RemoveAt( i );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t_queue[i] = q;\n\t\t\t}\n\t\t}\n\n\t\tvar live = 0;\n\t\tforeach ( var t in _things )\n\t\t{\n\t\t\tif ( t.Dead )\n\t\t\t{\n\t\t\t\tt.Pop \u002B= dt;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlive\u002B\u002B;\n\t\t\tt.X \u002B= t.Vx * dt;\n\t\t\tt.Y \u002B= MathF.Sin( _bob * 0.9f \u002B t.X * 0.02f ) * (t.Kind == CrawlKind.Loot ? 10f : 4f) * dt * 8f;\n\n\t\t\tif ( t.Resolved )\n\t\t\t\tcontinue;\n\n\t\t\t// Reached the fish \u2014 contact hit.\n\t\t\tif ( t.X \u002B t.W * 0.35f \u003C= FishX \u002B 36f )\n\t\t\t{\n\t\t\t\tt.Resolved = true;\n\t\t\t\tif ( t.Kind == CrawlKind.Loot )\n\t\t\t\t{\n\t\t\t\t\tt.Dead = true;\n\t\t\t\t\tt.Pop = 0f;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tTakeHit( t.Kind == CrawlKind.Hazard ? \u0022SCRAPE\u0022 : \u0022BIT\u0022 );\n\t\t\t\t\tt.Dead = true;\n\t\t\t\t\tt.Pop = 0f;\n\t\t\t\t\tif ( _outcome == CrawlOutcome.Dead )\n\t\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t_things.RemoveAll( t =\u003E t.Dead \u0026\u0026 t.Pop \u003E 0.28f );\n\n\t\tif ( live == 0 \u0026\u0026 _queue.Count == 0 \u0026\u0026 _things.Count == 0 )\n\t\t{\n\t\t\t_clearHold = 0.55f;\n\t\t\tYell( _bossWave ? \u0022DOWN\u0022 : \u0022CLEAR\u0022, 0.55f );\n\t\t\tGameAudio.PlayUi( GameAudio.Confirm );\n\t\t\t_version\u002B\u002B;\n\t\t}\n\t}\n\n\tpublic static void Chomp()\n\t{\n\t\tif ( _outcome != CrawlOutcome.Playing || _clearHold \u003E 0f )\n\t\t\treturn;\n\t\tif ( _cooldown \u003E 0f )\n\t\t\treturn;\n\n\t\t_cooldown = 0.18f;\n\t\t_chompFlash = 0.12f;\n\n\t\tCrawlThing best = null;\n\t\tvar bestDist = 9999f;\n\t\tvar lo = BiteLoPx;\n\t\tvar hi = BiteHiPx;\n\t\tvar mid = (lo \u002B hi) * 0.5f;\n\t\tforeach ( var t in _things )\n\t\t{\n\t\t\tif ( t.Dead || t.Resolved )\n\t\t\t\tcontinue;\n\t\t\tvar cx = t.X \u002B t.W * 0.5f;\n\t\t\tif ( cx \u003C lo || cx \u003E hi )\n\t\t\t\tcontinue;\n\t\t\tvar d = MathF.Abs( cx - mid );\n\t\t\tif ( d \u003C bestDist )\n\t\t\t{\n\t\t\t\tbestDist = d;\n\t\t\t\tbest = t;\n\t\t\t}\n\t\t}\n\n\t\tif ( best is null )\n\t\t{\n\t\t\tGameAudio.PlayUi( GameAudio.Pop );\n\t\t\t_version\u002B\u002B;\n\t\t\treturn;\n\t\t}\n\n\t\tvar perfect = bestDist \u003C= 16f;\n\t\tbest.HitsLeft--;\n\t\tif ( best.HitsLeft \u003E 0 )\n\t\t{\n\t\t\t// Boss chip.\n\t\t\t_combo\u002B\u002B;\n\t\t\t_chomps\u002B\u002B;\n\t\t\tif ( _combo \u003E _bestCombo )\n\t\t\t\t_bestCombo = _combo;\n\t\t\tYell( perfect ? \u0022CHIP\u0022 : \u0022HIT\u0022, 0.28f );\n\t\t\tGameAudio.PlayUi( GameAudio.Tape );\n\t\t\tBoomFeel.SoftPunch( 0.35f );\n\t\t\t_version\u002B\u002B;\n\t\t\treturn;\n\t\t}\n\n\t\tbest.Dead = true;\n\t\tbest.Resolved = true;\n\t\tbest.Pop = 0f;\n\t\t_combo\u002B\u002B;\n\t\t_chomps\u002B\u002B;\n\t\tif ( perfect )\n\t\t\t_perfects\u002B\u002B;\n\t\tif ( _combo \u003E _bestCombo )\n\t\t\t_bestCombo = _combo;\n\n\t\tvar pay = best.Payout;\n\t\tif ( pay \u003E 0 )\n\t\t{\n\t\t\tif ( perfect )\n\t\t\t\tpay = Math.Round( pay * 1.4, 1 );\n\t\t\tif ( _combo \u003E= 4 )\n\t\t\t\tpay = Math.Round( pay * (1.0 \u002B Math.Min( 0.6, (_combo - 3) * 0.12 )), 1 );\n\t\t\tEconomy.Add( pay );\n\t\t\tLotRunSystem.AddCrawlLoot( pay );\n\t\t\t_waveLoot \u002B= pay;\n\t\t}\n\n\t\tif ( best.Kind == CrawlKind.Boss )\n\t\t{\n\t\t\tYell( \u0022CHOMP\u0022, 0.45f );\n\t\t\tGameAudio.PlayBigWin( bark: pay \u003E= 20 );\n\t\t\tBoomFeel.SoftPunch( 0.85f );\n\t\t}\n\t\telse if ( best.Kind == CrawlKind.Loot )\n\t\t{\n\t\t\tYell( perfect ? \u0022SNAG\u0022 : \u0022YOINK\u0022, 0.28f );\n\t\t\tGameAudio.PlayCoin();\n\t\t}\n\t\telse if ( best.Kind == CrawlKind.Hazard )\n\t\t{\n\t\t\tYell( perfect ? \u0022SLIP\u0022 : \u0022THRU\u0022, 0.28f );\n\t\t\tGameAudio.PlayUi( GameAudio.Pop );\n\t\t\tBoomFeel.SoftPunch( 0.25f );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tYell( perfect ? \u0022PERFECT\u0022 : (_combo \u003E= 3 ? \u0022x\u0022 \u002B _combo : \u0022CHOMP\u0022), 0.32f );\n\t\t\tGameAudio.PlayUi( GameAudio.Confirm );\n\t\t\tBoomFeel.SoftPunch( perfect ? 0.55f : 0.4f );\n\t\t}\n\n\t\t_version\u002B\u002B;\n\t}\n\n\tstatic void TakeHit( string shout )\n\t{\n\t\tif ( _invuln \u003E 0f )\n\t\t\treturn;\n\t\t_combo = 0;\n\t\t_hp--;\n\t\t_hurtFlash = 0.38f;\n\t\t_invuln = 0.42f;\n\t\tYell( shout, 0.4f );\n\t\tGameAudio.PlayUi( GameAudio.Error );\n\t\tBoomFeel.SoftPunch( 0.7f );\n\t\tif ( _hp \u003C= 0 )\n\t\t{\n\t\t\t_hp = 0;\n\t\t\t_outcome = CrawlOutcome.Dead;\n\t\t}\n\t\t_version\u002B\u002B;\n\t}\n\n\tstatic void Yell( string text, float life )\n\t{\n\t\t_shout = text ?? \u0022\u0022;\n\t\t_shoutTimer = life;\n\t}\n\n\tstatic bool InBite( CrawlThing t )\n\t{\n\t\tvar cx = t.X \u002B t.W * 0.5f;\n\t\treturn cx \u003E= BiteLoPx \u0026\u0026 cx \u003C= BiteHiPx;\n\t}\n\n\tstatic void BuildWave( LotRoomDef room, int roomIndex, int roomCount )\n\t{\n\t\tvar kind = room?.Kind ?? LotRoomKind.Look;\n\t\tvar speed = 132f \u002B roomIndex * 16f;\n\t\tif ( kind == LotRoomKind.Boss )\n\t\t\tspeed \u002B= 18f;\n\t\tvar delay = 0.35f;\n\t\tvar names = room?.EnemyNames;\n\n\t\tvoid Q( float wait, CrawlThing t )\n\t\t{\n\t\t\tdelay \u002B= wait;\n\t\t\tt.X = FieldW \u002B 8f;\n\t\t\tt.Y = FishY - t.H * 0.35f \u002B (float)(_rng.NextDouble() * 18f - 8f);\n\t\t\tt.Vx = -speed * (0.9f \u002B (float)_rng.NextDouble() * 0.22f);\n\t\t\t_queue.Add( (delay, t) );\n\t\t}\n\n\t\tswitch ( kind )\n\t\t{\n\t\t\tcase LotRoomKind.Look:\n\t\t\t\tQ( 0.15f, Enemy( \u0022adv_mite\u0022, Pick( names, \u0022Pipe Mite\u0022 ), 4, 48, 34, speed ) );\n\t\t\t\tQ( 0.85f, Loot( 5 ) );\n\t\t\t\tQ( 0.75f, Enemy( \u0022adv_mite\u0022, Pick( names, \u0022Silt Ray\u0022 ), 5, 48, 34, speed ) );\n\t\t\t\tbreak;\n\t\t\tcase LotRoomKind.Hazard:\n\t\t\t\tQ( 0.1f, Hazard( \u0022adv_valve\u0022, \u0022Valve\u0022, speed ) );\n\t\t\t\tQ( 0.7f, Enemy( \u0022adv_mite\u0022, Pick( names, \u0022Pipe Mite\u0022 ), 5, 48, 34, speed ) );\n\t\t\t\tQ( 0.65f, Hazard( \u0022adv_grate\u0022, \u0022Grate\u0022, speed ) );\n\t\t\t\tif ( roomIndex \u003E= 2 )\n\t\t\t\t\tQ( 0.7f, Hazard( \u0022adv_valve\u0022, \u0022Valve\u0022, speed * 1.05f ) );\n\t\t\t\tbreak;\n\t\t\tcase LotRoomKind.Loot:\n\t\t\t\tQ( 0.1f, Loot( 6 ) );\n\t\t\t\tQ( 0.45f, Loot( 5 ) );\n\t\t\t\tQ( 0.55f, Enemy( \u0022adv_eel\u0022, Pick( names, \u0022Gutter Spec\u0022 ), 6, 62, 30, speed ) );\n\t\t\t\tQ( 0.5f, Loot( 7 ) );\n\t\t\t\tQ( 0.55f, Loot( 8 ) );\n\t\t\t\tbreak;\n\t\t\tcase LotRoomKind.Fight:\n\t\t\t\tQ( 0.1f, Enemy( \u0022adv_mite\u0022, Pick( names, \u0022Drain Runt\u0022 ), 6, 48, 34, speed ) );\n\t\t\t\tQ( 0.62f, Enemy( \u0022adv_eel\u0022, Pick( names, \u0022Mop Eel\u0022 ), 8, 62, 30, speed ) );\n\t\t\t\tQ( 0.58f, Enemy( \u0022adv_mite\u0022, Pick( names, \u0022Lot Skater\u0022 ), 6, 48, 34, speed ) );\n\t\t\t\tQ( 0.7f, Enemy( \u0022adv_eel\u0022, Pick( names, \u0022Wet Ratfish\u0022 ), 9, 62, 30, speed ) );\n\t\t\t\tif ( roomCount \u003E 4 )\n\t\t\t\t\tQ( 0.6f, Enemy( \u0022adv_mite\u0022, Pick( names, \u0022Scale Flake\u0022 ), 7, 48, 34, speed ) );\n\t\t\t\tbreak;\n\t\t\tcase LotRoomKind.Boss:\n\t\t\t\tQ( 0.1f, Enemy( \u0022adv_mite\u0022, \u0022Minion\u0022, 5, 48, 34, speed ) );\n\t\t\t\tQ( 0.85f, Boss( room ) );\n\t\t\t\tQ( 1.15f, Enemy( \u0022adv_eel\u0022, \u0022Escort\u0022, 8, 62, 30, speed * 1.05f ) );\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tQ( 0.2f, Enemy( \u0022adv_mite\u0022, \u0022Pipe Mite\u0022, 5, 48, 34, speed ) );\n\t\t\t\tQ( 0.7f, Loot( 5 ) );\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tstatic CrawlThing Enemy( string sprite, string name, double pay, float w, float h, float speed ) =\u003E\n\t\tnew()\n\t\t{\n\t\t\tKind = CrawlKind.Enemy,\n\t\t\tSpriteId = sprite,\n\t\t\tName = name,\n\t\t\tW = w,\n\t\t\tH = h,\n\t\t\tHitsLeft = 1,\n\t\t\tPayout = pay,\n\t\t\tVx = -speed\n\t\t};\n\n\tstatic CrawlThing Hazard( string sprite, string name, float speed ) =\u003E\n\t\tnew()\n\t\t{\n\t\t\tKind = CrawlKind.Hazard,\n\t\t\tSpriteId = sprite,\n\t\t\tName = name,\n\t\t\tW = 46f,\n\t\t\tH = 46f,\n\t\t\tHitsLeft = 1,\n\t\t\tPayout = 3,\n\t\t\tVx = -speed * 0.92f\n\t\t};\n\n\tstatic CrawlThing Loot( double pay ) =\u003E\n\t\tnew()\n\t\t{\n\t\t\tKind = CrawlKind.Loot,\n\t\t\tSpriteId = \u0022adv_coin\u0022,\n\t\t\tName = \u0022Coin\u0022,\n\t\t\tW = 28f,\n\t\t\tH = 28f,\n\t\t\tHitsLeft = 1,\n\t\t\tPayout = pay,\n\t\t\tVx = -118f\n\t\t};\n\n\tstatic CrawlThing Boss( LotRoomDef room )\n\t{\n\t\tvar lo = room?.FightRewardMin \u003E 0 ? room.FightRewardMin : 18;\n\t\tvar hi = room?.FightRewardMax \u003E lo ? room.FightRewardMax : lo \u002B 16;\n\t\tvar pay = lo \u002B _rng.NextDouble() * (hi - lo);\n\t\treturn new CrawlThing\n\t\t{\n\t\t\tKind = CrawlKind.Boss,\n\t\t\tSpriteId = \u0022adv_carp\u0022,\n\t\t\tName = Pick( room?.EnemyNames, \u0022Mainline Carp\u0022 ),\n\t\t\tW = 92f,\n\t\t\tH = 62f,\n\t\t\tHitsLeft = 3,\n\t\t\tPayout = Math.Round( pay, 1 ),\n\t\t\tVx = -108f\n\t\t};\n\t}\n\n\tstatic string Pick( string[] names, string fallback )\n\t{\n\t\tif ( names is { Length: \u003E 0 } )\n\t\t\treturn names[_rng.Next( names.Length )];\n\t\treturn fallback;\n\t}\n}\n"},{"Ident":"dogecoin.nochillquarium","Path":"SharkSystem.cs","FileName":"SharkSystem.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"namespace NoChillquarium;\r\n\r\n/// \u003Csummary\u003E\r\n/// Ocean hazard: cocaine sharks hunt fish in ocean habitats.\r\n/// Scare with \u00D0 \u002B team power, or Fend with trained/jacked fish.\r\n/// \u003C/summary\u003E\r\npublic sealed class SharkActor\r\n{\r\n\tpublic string Id { get; init; }\r\n\tpublic float X { get; set; }\r\n\tpublic float Y { get; set; }\r\n\tpublic float Vx { get; set; }\r\n\tpublic float Vy { get; set; }\r\n\tpublic float BiteCooldown { get; set; }\r\n\tpublic float Life { get; set; } = 1f; // 0 = dead\r\n\tpublic bool Alive =\u003E Life \u003E 0.01f;\r\n\tpublic int Facing =\u003E Vx \u003E= 0f ? 1 : -1;\r\n\tpublic float Width =\u003E 110f;\r\n\tpublic float Height =\u003E 56f;\r\n}\r\n\r\npublic static class SharkSystem\r\n{\r\n\tpublic const string OceanHabitatId = \u0022ocean\u0022;\r\n\tpublic const float SpawnIntervalMin = 16f;\r\n\tpublic const float SpawnIntervalMax = 30f;\r\n\tpublic const int MaxSharks = 3;\r\n\tpublic const double ScareCost = 18;\r\n\tpublic const double FendCost = 0; // free but needs power\r\n\tpublic const float BiteRange = 38f;\r\n\tpublic const float SharkSpeed = 100f;\r\n\r\n\tstatic readonly Random _rng = new();\r\n\tstatic readonly List\u003CSharkActor\u003E _sharks = new();\r\n\tstatic float _spawnTimer = 12f;\r\n\tstatic float _alertFlash;\r\n\tstatic int _version;\r\n\tstatic int _kills;\r\n\tstatic int _eaten;\r\n\tstatic bool _everSpawned;\r\n\r\n\tpublic static int Version =\u003E _version;\r\n\tpublic static IReadOnlyList\u003CSharkActor\u003E Sharks =\u003E _sharks;\r\n\tpublic static int SharkCount =\u003E _sharks.Count( s =\u003E s.Alive );\r\n\tpublic static bool HasSharks =\u003E SharkCount \u003E 0;\r\n\tpublic static float AlertFlash =\u003E _alertFlash;\r\n\tpublic static int Kills =\u003E _kills;\r\n\tpublic static int Eaten =\u003E _eaten;\r\n\tpublic static bool EverSpawned =\u003E _everSpawned;\r\n\r\n\tpublic static bool IsOceanActive =\u003E\r\n\t\tTankSim.IsActive\r\n\t\t\u0026\u0026 string.Equals( TankSim.HabitatId, OceanHabitatId, StringComparison.OrdinalIgnoreCase );\r\n\r\n\tpublic static void Clear()\r\n\t{\r\n\t\t_sharks.Clear();\r\n\t\t_spawnTimer = 12f;\r\n\t\t_alertFlash = 0f;\r\n\t\t_version\u002B\u002B;\r\n\t}\r\n\r\n\tpublic static void ResetProgress()\r\n\t{\r\n\t\t_kills = 0;\r\n\t\t_eaten = 0;\r\n\t\t_everSpawned = false;\r\n\t\tClear();\r\n\t}\r\n\r\n\tpublic static void Load( int kills, int eaten, bool everSpawned )\r\n\t{\r\n\t\t_kills = Math.Max( 0, kills );\r\n\t\t_eaten = Math.Max( 0, eaten );\r\n\t\t_everSpawned = everSpawned;\r\n\t\tClear();\r\n\t}\r\n\r\n\tpublic static void WriteToSave( SaveData data )\r\n\t{\r\n\t\tif ( data is null )\r\n\t\t\treturn;\r\n\t\tdata.SharkKills = _kills;\r\n\t\tdata.SharkEaten = _eaten;\r\n\t\tdata.SharkEverSpawned = _everSpawned;\r\n\t}\r\n\r\n\tpublic static void Tick( float dt )\r\n\t{\r\n\t\tif ( !TankSim.IsActive || dt \u003C= 0f )\r\n\t\t\treturn;\r\n\t\tif ( dt \u003E 0.05f )\r\n\t\t\tdt = 0.05f;\r\n\r\n\t\tif ( _alertFlash \u003E 0f )\r\n\t\t{\r\n\t\t\t_alertFlash = MathF.Max( 0f, _alertFlash - dt );\r\n\t\t\t_version\u002B\u002B;\r\n\t\t}\r\n\r\n\t\tif ( !IsOceanActive )\r\n\t\t{\r\n\t\t\t// Leave the ocean \u2192 sharks swim off.\r\n\t\t\tif ( _sharks.Count \u003E 0 )\r\n\t\t\t{\r\n\t\t\t\t_sharks.Clear();\r\n\t\t\t\t_version\u002B\u002B;\r\n\t\t\t}\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Spawn cadence\r\n\t\t_spawnTimer -= dt;\r\n\t\tif ( _spawnTimer \u003C= 0f )\r\n\t\t{\r\n\t\t\t_spawnTimer = SpawnIntervalMin \u002B (float)_rng.NextDouble() * (SpawnIntervalMax - SpawnIntervalMin);\r\n\t\t\tif ( SharkCount \u003C MaxSharks \u0026\u0026 TankSim.FishCount \u003E 0 )\r\n\t\t\t\tTrySpawn();\r\n\t\t}\r\n\r\n\t\tfor ( var i = _sharks.Count - 1; i \u003E= 0; i-- )\r\n\t\t{\r\n\t\t\tvar shark = _sharks[i];\r\n\t\t\tif ( !shark.Alive )\r\n\t\t\t{\r\n\t\t\t\t_sharks.RemoveAt( i );\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tif ( shark.BiteCooldown \u003E 0f )\r\n\t\t\t\tshark.BiteCooldown -= dt;\r\n\r\n\t\t\tHunt( shark, dt );\r\n\t\t\tIntegrate( shark, dt );\r\n\r\n\t\t\tif ( shark.BiteCooldown \u003C= 0f )\r\n\t\t\t\tTryBite( shark );\r\n\t\t}\r\n\r\n\t\tif ( _sharks.Count \u003E 0 )\r\n\t\t\t_version\u002B\u002B;\r\n\t}\r\n\r\n\tstatic void TrySpawn()\r\n\t{\r\n\t\tvar fromLeft = _rng.NextDouble() \u003C 0.5;\r\n\t\tvar y = 60f \u002B (float)_rng.NextDouble() * (TankSim.Height - 120f);\r\n\t\tvar x = fromLeft ? -40f : TankSim.Width \u002B 40f;\r\n\t\tvar speed = SharkSpeed * (0.85f \u002B (float)_rng.NextDouble() * 0.35f);\r\n\t\tvar vx = fromLeft ? speed : -speed;\r\n\r\n\t\t_sharks.Add( new SharkActor\r\n\t\t{\r\n\t\t\tId = Guid.NewGuid().ToString( \u0022N\u0022 )[..6],\r\n\t\t\tX = x,\r\n\t\t\tY = y,\r\n\t\t\tVx = vx,\r\n\t\t\tVy = ((float)_rng.NextDouble() - 0.5f) * 30f,\r\n\t\t\tBiteCooldown = 1.2f,\r\n\t\t\tLife = 1f\r\n\t\t} );\r\n\r\n\t\t_everSpawned = true;\r\n\t\t_alertFlash = 1.6f;\r\n\t\tChaos.Add( 6f );\r\n\t\tBoomFeel.SoftPunch( 0.55f );\r\n\t\tGameAudio.PlaySharkAlert();\r\n\t\tvar n = SharkCount;\r\n\t\tTankSim.ShowBanner( n \u003E 1\r\n\t\t\t? $\u0022COCAINE SHARKS \u00D7{n}. Fend or Scare \u2014 snacks are optional.\u0022\r\n\t\t\t: \u0022COCAINE SHARK in the water. Fend (train) or Scare (\u00D0).\u0022 );\r\n\t\t_version\u002B\u002B;\r\n\t}\r\n\r\n\tstatic void Hunt( SharkActor shark, float dt )\r\n\t{\r\n\t\tif ( TankSim.FishCount == 0 )\r\n\t\t{\r\n\t\t\t// Patrol\r\n\t\t\tshark.Vx \u002B= ((float)_rng.NextDouble() - 0.5f) * 40f * dt;\r\n\t\t\tshark.Vy \u002B= ((float)_rng.NextDouble() - 0.5f) * 30f * dt;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Prefer weak / slow fish\r\n\t\tFishActor prey = null;\r\n\t\tvar best = float.MaxValue;\r\n\t\tforeach ( var f in TankSim.Fish )\r\n\t\t{\r\n\t\t\tvar dx = f.X - shark.X;\r\n\t\t\tvar dy = f.DrawY - shark.Y;\r\n\t\t\tvar dist = MathF.Sqrt( dx * dx \u002B dy * dy );\r\n\t\t\t// Jacked / trained are harder targets (deprioritize)\r\n\t\t\tvar score = dist \u002B f.Agility * 8f \u002B f.SpeedStat * 6f \u002B (f.Jacked ? 40f : 0f);\r\n\t\t\tif ( f.IsSick || f.InWithdrawal )\r\n\t\t\t\tscore -= 30f;\r\n\t\t\tif ( score \u003C best )\r\n\t\t\t{\r\n\t\t\t\tbest = score;\r\n\t\t\t\tprey = f;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( prey is null )\r\n\t\t\treturn;\r\n\r\n\t\tvar pdx = prey.X - shark.X;\r\n\t\tvar pdy = prey.DrawY - shark.Y;\r\n\t\tvar len = MathF.Sqrt( pdx * pdx \u002B pdy * pdy );\r\n\t\tif ( len \u003C 1f )\r\n\t\t\treturn;\r\n\r\n\t\tvar want = SharkSpeed * (0.9f \u002B (1f - Math.Clamp( prey.TrainingNorm, 0f, 1f )) * 0.35f);\r\n\t\tshark.Vx = pdx / len * want;\r\n\t\tshark.Vy = pdy / len * want * 0.75f;\r\n\r\n\t\t// Panic nearby fish slightly\r\n\t\tif ( len \u003C 160f )\r\n\t\t{\r\n\t\t\tprey.Vx \u002B= (pdx / len) * 40f * dt;\r\n\t\t\tprey.Vy \u002B= (pdy / len) * 30f * dt;\r\n\t\t}\r\n\t}\r\n\r\n\tstatic void Integrate( SharkActor shark, float dt )\r\n\t{\r\n\t\tshark.X \u002B= shark.Vx * dt;\r\n\t\tshark.Y \u002B= shark.Vy * dt;\r\n\r\n\t\tvar minX = -20f;\r\n\t\tvar maxX = TankSim.Width \u002B 20f;\r\n\t\tvar minY = 30f;\r\n\t\tvar maxY = TankSim.Height - 40f;\r\n\r\n\t\tif ( shark.Y \u003C minY )\r\n\t\t{\r\n\t\t\tshark.Y = minY;\r\n\t\t\tshark.Vy = MathF.Abs( shark.Vy );\r\n\t\t}\r\n\t\telse if ( shark.Y \u003E maxY )\r\n\t\t{\r\n\t\t\tshark.Y = maxY;\r\n\t\t\tshark.Vy = -MathF.Abs( shark.Vy );\r\n\t\t}\r\n\r\n\t\t// Bounce horizontal within extended bounds\r\n\t\tif ( shark.X \u003C minX )\r\n\t\t{\r\n\t\t\tshark.X = minX;\r\n\t\t\tshark.Vx = MathF.Abs( shark.Vx );\r\n\t\t}\r\n\t\telse if ( shark.X \u003E maxX )\r\n\t\t{\r\n\t\t\tshark.X = maxX;\r\n\t\t\tshark.Vx = -MathF.Abs( shark.Vx );\r\n\t\t}\r\n\t}\r\n\r\n\tstatic void TryBite( SharkActor shark )\r\n\t{\r\n\t\tFishActor closest = null;\r\n\t\tvar bestDist = BiteRange;\r\n\t\tforeach ( var f in TankSim.Fish )\r\n\t\t{\r\n\t\t\tvar dx = f.X - shark.X;\r\n\t\t\tvar dy = f.DrawY - shark.Y;\r\n\t\t\tvar dist = MathF.Sqrt( dx * dx \u002B dy * dy );\r\n\t\t\tif ( dist \u003C bestDist )\r\n\t\t\t{\r\n\t\t\t\tbestDist = dist;\r\n\t\t\t\tclosest = f;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( closest is null )\r\n\t\t\treturn;\r\n\r\n\t\t// Escape chance from agility / speed / jacked / meth\r\n\t\tvar escape = 0.18f \u002B closest.Agility * 0.04f \u002B closest.SpeedStat * 0.03f;\r\n\t\tif ( closest.Jacked )\r\n\t\t\tescape \u002B= 0.22f;\r\n\t\tif ( closest.OnMeth )\r\n\t\t\tescape \u002B= 0.12f;\r\n\t\tif ( closest.IsSick || closest.InWithdrawal )\r\n\t\t\tescape -= 0.2f;\r\n\t\tescape = Math.Clamp( escape, 0.08f, 0.78f );\r\n\r\n\t\tshark.BiteCooldown = 1.6f \u002B (float)_rng.NextDouble() * 0.8f;\r\n\r\n\t\tif ( _rng.NextDouble() \u003C escape )\r\n\t\t{\r\n\t\t\t// Dash away\r\n\t\t\tvar ang = (float)(_rng.NextDouble() * Math.PI * 2);\r\n\t\t\tclosest.Vx = MathF.Cos( ang ) * closest.Species.Speed * 2.4f;\r\n\t\t\tclosest.Vy = MathF.Sin( ang ) * closest.Species.Speed * 1.2f;\r\n\t\t\tif ( _rng.NextDouble() \u003C 0.35 )\r\n\t\t\t\tTankSim.ShowBanner( $\u0022{closest.Species.Name} dodged a shark. Barely.\u0022 );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Eaten\r\n\t\tvar name = closest.Species.Name;\r\n\t\tvar ex = closest.X;\r\n\t\tvar ey = closest.DrawY;\r\n\t\tTankSim.RemoveFish( closest.InstanceId );\r\n\t\tExplodeSystem.SpawnDeathPoof( ex, ey );\r\n\t\tWaterFx.Splash( ex, ey, 16, WaterLayer.Tank );\r\n\t\t_eaten\u002B\u002B;\r\n\t\tTankSim.NotifyFishDied();\r\n\t\tChaos.Add( 5f );\r\n\t\tKarma.Add( -4f );\r\n\t\tGameAudio.PlayUi( GameAudio.M80 );\r\n\t\tTankSim.ShowBanner( $\u0022Shark ate {name}. Grandma: still your fault.\u0022 );\r\n\t\t_version\u002B\u002B;\r\n\t}\r\n\r\n\tpublic static float TeamPower()\r\n\t{\r\n\t\tif ( TankSim.FishCount == 0 )\r\n\t\t\treturn 0f;\r\n\t\tfloat sum = 0;\r\n\t\tforeach ( var f in TankSim.Fish )\r\n\t\t\tsum \u002B= BattleSystem.CombatPower( f );\r\n\t\treturn sum;\r\n\t}\r\n\r\n\tpublic static bool CanScare =\u003E\r\n\t\tIsOceanActive \u0026\u0026 HasSharks \u0026\u0026 Economy.Balance \u003E= ScareCost;\r\n\r\n\tpublic static bool CanFend =\u003E\r\n\t\tIsOceanActive \u0026\u0026 HasSharks \u0026\u0026 TankSim.FishCount \u003E 0 \u0026\u0026 TeamPower() \u003E= 6f;\r\n\r\n\t/// \u003Csummary\u003EDock labels \u2014 always show cost / power so ocean isn\u0027t a mystery.\u003C/summary\u003E\r\n\tpublic static string ScareButtonLabel =\u003E\r\n\t\t\u0022Scare \u00D0\u0022 \u002B Economy.FormatDoge( ScareCost );\r\n\r\n\tpublic static string FendButtonLabel\r\n\t{\r\n\t\tget\r\n\t\t{\r\n\t\t\tvar pow = TeamPower().ToString( \u00220.#\u0022 );\r\n\t\t\treturn CanFend ? \u0022Fend \u00B7 pow \u0022 \u002B pow : \u0022Fend \u00B7 need power\u0022;\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ESpend \u00D0 \u2014 chance to drive a shark off based on team power.\u003C/summary\u003E\r\n\tpublic static bool TryScare()\r\n\t{\r\n\t\tif ( !HasSharks )\r\n\t\t{\r\n\t\t\tTankSim.ShowBanner( \u0022No sharks. Ocean is... fine?\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tif ( !Economy.TrySpend( ScareCost ) )\r\n\t\t{\r\n\t\t\tTankSim.ShowBanner( $\u0022Scare costs \u00D0{Economy.FormatDoge( ScareCost )}.\u0022 );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar power = TeamPower();\r\n\t\tvar chance = Math.Clamp( 0.35f \u002B power * 0.022f, 0.35f, 0.92f );\r\n\t\tif ( _rng.NextDouble() \u003C chance )\r\n\t\t{\r\n\t\t\tvar shark = _sharks.FirstOrDefault( s =\u003E s.Alive );\r\n\t\t\tif ( shark is not null )\r\n\t\t\t{\r\n\t\t\t\tExplodeSystem.SpawnDeathPoof( shark.X, shark.Y );\r\n\t\t\t\t_sharks.Remove( shark );\r\n\t\t\t}\r\n\t\t\tChaos.Add( 2f );\r\n\t\t\tKarma.Add( 3f );\r\n\t\t\tBoomFeel.SoftPunch( 0.35f );\r\n\t\t\tGameAudio.PlayLayered( GameAudio.Upgrade, GameAudio.Notice, 0.4f );\r\n\t\t\tvar tip = Math.Round( ScareCost * 0.35, 1 );\r\n\t\t\tif ( tip \u003E 0.5 )\r\n\t\t\t\tEconomy.AddForced( tip );\r\n\t\t\tTankSim.ShowBanner( \u0022Shark scared off. \u002B\u00D0\u0022 \u002B Economy.FormatDoge( tip ) \u002B \u0022 hazard pay.\u0022 );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tChaos.Add( 3f );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\tTankSim.ShowBanner( \u0022Shark unimpressed. Wallet lighter. Train or Fend.\u0022 );\r\n\t\t\tforeach ( var s in _sharks )\r\n\t\t\t\ts.BiteCooldown = MathF.Min( s.BiteCooldown, 0.4f );\r\n\t\t}\r\n\r\n\t\tSaveGame.TrySave();\r\n\t\t_version\u002B\u002B;\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EFend with trained fish \u2014 can kill a shark for \u00D0 loot.\u003C/summary\u003E\r\n\tpublic static bool TryFend()\r\n\t{\r\n\t\tif ( !HasSharks || TankSim.FishCount == 0 )\r\n\t\t{\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar power = TeamPower();\r\n\t\tvar chance = Math.Clamp( 0.22f \u002B power * 0.028f, 0.22f, 0.94f );\r\n\t\tvar jacked = TankSim.Fish.Count( f =\u003E f.Jacked );\r\n\t\tif ( jacked \u003E 0 )\r\n\t\t\tchance = Math.Clamp( chance \u002B jacked * 0.07f, 0f, 0.96f );\r\n\t\t// Monsters eat sharks for breakfast.\r\n\t\tif ( TankSim.HasAnyMonster )\r\n\t\t\tchance = Math.Clamp( chance \u002B 0.08f, 0f, 0.97f );\r\n\r\n\t\tvar shark = _sharks.FirstOrDefault( s =\u003E s.Alive );\r\n\t\tif ( shark is null )\r\n\t\t\treturn false;\r\n\r\n\t\tif ( _rng.NextDouble() \u003C chance )\r\n\t\t{\r\n\t\t\tvar sx = shark.X;\r\n\t\t\tvar sy = shark.Y;\r\n\t\t\tExplodeSystem.SpawnDeathPoof( sx, sy );\r\n\t\t\tExplodeSystem.SpawnCoinBurst( sx, sy, count: 16 );\r\n\t\t\t_sharks.Remove( shark );\r\n\t\t\t_kills\u002B\u002B;\r\n\t\t\t// Kill streak pays \u2014 ocean is a real \u00D0 loop late game.\r\n\t\t\tvar streak = Math.Min( 8, _kills );\r\n\t\t\tvar loot = Math.Round( 55 \u002B _rng.NextDouble() * 55 \u002B streak * 8, 1 );\r\n\t\t\tEconomy.AddForced( loot );\r\n\t\t\tChaos.Add( 4f );\r\n\t\t\tKarma.Add( 2f );\r\n\t\t\tBoomFeel.SoftPunch( 0.7f );\r\n\t\t\tGameAudio.PlayBigWin( bark: _kills \u003C= 3 || _kills % 5 == 0 );\r\n\t\t\tStatFloat.PushTag( \u0022SHARK \u002B\u0022 \u002B \u0022\u00D0\u0022 \u002B Economy.FormatDoge( loot ), \u0022rare\u0022, sx, sy - 24f, life: 2f );\r\n\t\t\tvar killLine = _kills == 1\r\n\t\t\t\t? $\u0022First shark down! \u002B\u00D0{Economy.FormatDoge( loot )}.\u0022\r\n\t\t\t\t: $\u0022Shark kill \u00D7{_kills}! \u002B\u00D0{Economy.FormatDoge( loot )}.\u0022;\r\n\t\t\tTankSim.ShowBanner( killLine \u002B \u0022 Cocaine not recovered.\u0022 );\r\n\t\t\tif ( _kills == 1 )\r\n\t\t\t\tGrandmaReact.OnFirstSharkKill();\r\n\t\t\tforeach ( var f in TankSim.Fish )\r\n\t\t\t{\r\n\t\t\t\tf.Endurance = MathF.Min( 10f, f.Endurance \u002B 0.12f );\r\n\t\t\t\tf.Agility = MathF.Min( 10f, f.Agility \u002B 0.14f );\r\n\t\t\t\tf.SpeedStat = MathF.Min( 10f, f.SpeedStat \u002B 0.08f );\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tChaos.Add( 5f );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\tTankSim.ShowBanner( \u0022Fend failed. Train harder or Scare with \u00D0.\u0022 );\r\n\t\t\tshark.BiteCooldown = 0.2f;\r\n\t\t\t// Retaliation less common so fending stays attractive.\r\n\t\t\tif ( _rng.NextDouble() \u003C 0.28 \u0026\u0026 TankSim.FishCount \u003E 0 )\r\n\t\t\t{\r\n\t\t\t\tvar weak = TankSim.Fish.OrderBy( f =\u003E f.Agility \u002B f.SpeedStat ).First();\r\n\t\t\t\tvar name = weak.Species.Name;\r\n\t\t\t\tvar ex = weak.X;\r\n\t\t\t\tvar ey = weak.DrawY;\r\n\t\t\t\tTankSim.RemoveFish( weak.InstanceId );\r\n\t\t\t\tExplodeSystem.SpawnDeathPoof( ex, ey );\r\n\t\t\t\t_eaten\u002B\u002B;\r\n\t\t\t\tTankSim.NotifyFishDied();\r\n\t\t\t\tKarma.Add( -4f );\r\n\t\t\t\tTankSim.ShowBanner( $\u0022Retaliation: shark ate {name}. Your fault.\u0022 );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tSaveGame.TrySave();\r\n\t\t_version\u002B\u002B;\r\n\t\treturn true;\r\n\t}\r\n}\r\n"},{"Ident":"dogecoin.nochillquarium","Path":"ExplodeSystem.cs","FileName":"ExplodeSystem.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"namespace NoChillquarium;\r\n\r\npublic sealed class BoomParticle\r\n{\r\n\tpublic float X { get; set; }\r\n\tpublic float Y { get; set; }\r\n\tpublic float Vx { get; set; }\r\n\tpublic float Vy { get; set; }\r\n\tpublic float Life { get; set; }\r\n\tpublic string Color { get; set; }\r\n}\r\n\r\n/// \u003Csummary\u003E\r\n/// M80 tape-and-explode: click fish to tape a visible M80 on them, then detonate.\r\n/// \u003C/summary\u003E\r\npublic static class ExplodeSystem\r\n{\r\n\tstatic readonly Dictionary\u003Cstring, float\u003E _taped = new(); // instanceId -\u003E time since taped\r\n\tstatic readonly List\u003CBoomParticle\u003E _particles = new();\r\n\tstatic readonly Random _rng = new();\r\n\tstatic int _version;\r\n\tstatic bool _mode;\r\n\r\n\tpublic static bool ModeActive\r\n\t{\r\n\t\tget =\u003E _mode;\r\n\t\tset\r\n\t\t{\r\n\t\t\t_mode = value;\r\n\t\t\tif ( !_mode )\r\n\t\t\t\t_taped.Clear();\r\n\t\t\t_version\u002B\u002B;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic static int SelectedCount =\u003E _taped.Count;\r\n\tpublic static int Version =\u003E _version;\r\n\tpublic static IReadOnlyList\u003CBoomParticle\u003E Particles =\u003E _particles;\r\n\r\n\tpublic static string StatusText =\u003E\r\n\t\t!_mode\r\n\t\t\t? (Shop.M80Bags \u003E 0 ? $\u0022M80 \u00D7{Shop.M80Bags}\u0022 : \u0022NO M80 \u2014 BUY IN SHADY\u0022)\r\n\t\t\t: SelectedCount == 0\r\n\t\t\t\t? $\u0022CLICK FISH TO TAPE M80 ({Shop.M80Bags} left)\u0022\r\n\t\t\t\t: $\u0022M80 TAPED \u00D7{SelectedCount}/{Shop.M80Bags} \u00B7 HIT DETONATE\u0022;\r\n\r\n\tpublic static void Clear()\r\n\t{\r\n\t\t_mode = false;\r\n\t\t_taped.Clear();\r\n\t\t_particles.Clear();\r\n\t\t_version\u002B\u002B;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EEnter/leave arm mode. Requires at least one bought M80 to arm.\u003C/summary\u003E\r\n\tpublic static bool TrySetMode( bool active )\r\n\t{\r\n\t\tif ( active \u0026\u0026 !Shop.CanArmM80() )\r\n\t\t{\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\tTankSim.ShowBanner( \u0022No M80s. Buy them on the Shady tab.\u0022 );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tModeActive = active;\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic static bool IsSelected( string instanceId ) =\u003E\r\n\t\t!string.IsNullOrEmpty( instanceId ) \u0026\u0026 _taped.ContainsKey( instanceId );\r\n\r\n\t/// \u003Csummary\u003ERemove tape from one fish (e.g. moved to another tank).\u003C/summary\u003E\r\n\tpublic static void Untape( string instanceId )\r\n\t{\r\n\t\tif ( string.IsNullOrEmpty( instanceId ) )\r\n\t\t\treturn;\r\n\t\tif ( _taped.Remove( instanceId ) )\r\n\t\t\t_version\u002B\u002B;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E0\u20141, hot for a moment after tape so UI can pop the prop in.\u003C/summary\u003E\r\n\tpublic static float TapeFreshness( string instanceId )\r\n\t{\r\n\t\tif ( string.IsNullOrEmpty( instanceId ) || !_taped.TryGetValue( instanceId, out var age ) )\r\n\t\t\treturn 0f;\r\n\t\t// Fresh for ~0.45s\r\n\t\treturn Math.Clamp( 1f - (age / 0.45f), 0f, 1f );\r\n\t}\r\n\r\n\tpublic static bool IsFreshlyTaped( string instanceId ) =\u003E TapeFreshness( instanceId ) \u003E 0.05f;\r\n\r\n\tpublic static void ToggleSelect( string instanceId )\r\n\t{\r\n\t\tif ( !_mode || string.IsNullOrEmpty( instanceId ) )\r\n\t\t\treturn;\r\n\r\n\t\t// Must still exist in the tank.\r\n\t\tif ( TankSim.Fish.All( f =\u003E f.InstanceId != instanceId ) )\r\n\t\t\treturn;\r\n\r\n\t\tif ( _taped.ContainsKey( instanceId ) )\r\n\t\t{\r\n\t\t\t_taped.Remove( instanceId );\r\n\t\t\tGameAudio.PlayUi( GameAudio.Close );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// One stick per fish \u2014 can\u0027t tape more than you bought.\r\n\t\t\tif ( Shop.M80Bags \u003C= _taped.Count )\r\n\t\t\t{\r\n\t\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\t\tTankSim.ShowBanner( Shop.M80Bags \u003C= 0\r\n\t\t\t\t\t? \u0022Out of M80s. Buy more in Shady.\u0022\r\n\t\t\t\t\t: $\u0022Only {Shop.M80Bags} M80{(Shop.M80Bags == 1 ? \u0022\u0022 : \u0022s\u0022)} left.\u0022 );\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\t_taped[instanceId] = 0f;\r\n\t\t\t// The moment of comedy: tape slap onto the fish.\r\n\t\t\tGameAudio.PlayUi( GameAudio.Tape );\r\n\t\t\tAchievementSystem.Unlock( \u0022tape\u0022 );\r\n\t\t}\r\n\r\n\t\t_version\u002B\u002B;\r\n\t}\r\n\r\n\tpublic static bool TryDetonate()\r\n\t{\r\n\t\tif ( !_mode )\r\n\t\t{\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tif ( _taped.Count == 0 )\r\n\t\t{\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar victims = TankSim.Fish.Where( f =\u003E _taped.ContainsKey( f.InstanceId ) ).ToList();\r\n\t\tif ( victims.Count == 0 )\r\n\t\t{\r\n\t\t\t_taped.Clear();\r\n\t\t\t_version\u002B\u002B;\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t// Spend one M80 per taped fish.\r\n\t\tif ( !Shop.TryConsumeM80( victims.Count ) )\r\n\t\t{\r\n\t\t\tGameAudio.PlayUi( GameAudio.Error );\r\n\t\t\tTankSim.ShowBanner( \u0022Not enough M80s in stock.\u0022 );\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t// Fuse already taped \u2014 this is the boom.\r\n\t\tGameAudio.PlayUi( GameAudio.M80 );\r\n\r\n\t\tvar origins = new List\u003C(float X, float Y)\u003E( victims.Count );\r\n\t\tvar scrap = 0.0;\r\n\t\tvar failures = 0;\r\n\t\tvar keepers = 0;\r\n\t\tforeach ( var fish in victims )\r\n\t\t{\r\n\t\t\tvar ox = fish.X;\r\n\t\t\tvar oy = fish.DrawY;\r\n\t\t\torigins.Add( (ox, oy) );\r\n\t\t\tSpawnBurst( ox, oy, fish.Species.Color, fish.Species.FinColor );\r\n\t\t\t// Extra red stick bits for the M80 itself.\r\n\t\t\tSpawnM80Bits( ox, oy );\r\n\t\t\t// Fire / ash particles for comedy violence.\r\n\t\t\tSpawnFireRing( ox, oy );\r\n\r\n\t\t\t// Failures = insurance scrap. Good aboms you blow up = waste.\r\n\t\t\tif ( fish.IsAbominationFailure )\r\n\t\t\t{\r\n\t\t\t\tfailures\u002B\u002B;\r\n\t\t\t\t// Quality control: scrap a chunk of what they\u0027d flip for.\r\n\t\t\t\tscrap \u002B= Math.Max( 8.0, TankSim.SellPrice( fish ) * 0.55 );\r\n\t\t\t}\r\n\t\t\telse if ( fish.IsMonster )\r\n\t\t\t{\r\n\t\t\t\tkeepers\u002B\u002B;\r\n\t\t\t\tscrap \u002B= Math.Max( 3.0, TankSim.SellPrice( fish ) * 0.12 );\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tscrap \u002B= 2.5;\r\n\t\t\t}\r\n\r\n\t\t\tTankSim.RemoveFish( fish.InstanceId );\r\n\t\t}\r\n\r\n\t\tvar n = victims.Count;\r\n\t\t_taped.Clear();\r\n\r\n\t\t// Screen smash \u002B shock rings at each fish \u002B death metal sting.\r\n\t\tBoomFeel.Trigger( n, origins );\r\n\t\tforeach ( var o in origins )\r\n\t\t\tWaterFx.BoomSplash( o.X, o.Y );\r\n\r\n\t\tChaos.Add( 6f \u002B n * 5f );\r\n\t\t// Grandma respects scrapping failures; hates wasting champions.\r\n\t\tif ( failures \u003E 0 )\r\n\t\t\tKarma.Add( 2.5f * failures );\r\n\t\tif ( keepers \u003E 0 )\r\n\t\t\tKarma.Add( -5f * keepers );\r\n\t\tif ( failures == 0 \u0026\u0026 keepers == 0 )\r\n\t\t\tKarma.Add( -3f - n * 2f );\r\n\r\n\t\tscrap = Math.Round( scrap, 1 );\r\n\t\tif ( scrap \u003E 0 )\r\n\t\t\tEconomy.Add( scrap );\r\n\r\n\t\tif ( failures \u003E 0 \u0026\u0026 keepers == 0 )\r\n\t\t\tTankSim.ShowBanner( $\u0022Scrapped {failures} failure{(failures == 1 ? \u0022\u0022 : \u0022s\u0022)}. Grandma: \\\u0022Quality control. Family business.\\\u0022 \u002B\u00D0{Economy.FormatDoge( scrap )}\u0022 );\r\n\t\telse if ( keepers \u003E 0 )\r\n\t\t\tTankSim.ShowBanner( $\u0022Boom. Grandma: \\\u0022That one had potential, idiot. Love you. Sit.\\\u0022 \u002B\u00D0{Economy.FormatDoge( scrap )}\u0022 );\r\n\t\telse\r\n\t\t\tTankSim.ShowBanner( $\u0022Boom \u00D7{n}. \u002B\u00D0{Economy.FormatDoge( scrap )}\u0022 );\r\n\r\n\t\t// Always return to default dock mode after the boom (clear via property).\r\n\t\tModeActive = false;\r\n\r\n\t\tStorySystem.NotifyBoom();\r\n\t\tJanitorPeep.On( \u0022boom\u0022 );\r\n\t\tSaveGame.TrySave();\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic static void Tick( float dt )\r\n\t{\r\n\t\tif ( dt \u003C= 0f )\r\n\t\t\treturn;\r\n\r\n\t\tif ( dt \u003E 0.05f )\r\n\t\t\tdt = 0.05f;\r\n\r\n\t\tif ( _taped.Count \u003E 0 )\r\n\t\t{\r\n\t\t\t// Age tape timers (for \u0022just taped\u0022 pop animation).\r\n\t\t\tvar keys = _taped.Keys.ToList();\r\n\t\t\tforeach ( var key in keys )\r\n\t\t\t{\r\n\t\t\t\t_taped[key] \u002B= dt;\r\n\t\t\t\t// Drop selection if fish was removed somehow.\r\n\t\t\t\tif ( TankSim.Fish.All( f =\u003E f.InstanceId != key ) )\r\n\t\t\t\t\t_taped.Remove( key );\r\n\t\t\t}\r\n\r\n\t\t\t_version\u002B\u002B;\r\n\t\t}\r\n\r\n\t\tfor ( var i = _particles.Count - 1; i \u003E= 0; i-- )\r\n\t\t{\r\n\t\t\tvar p = _particles[i];\r\n\t\t\tp.X \u002B= p.Vx * dt;\r\n\t\t\tp.Y \u002B= p.Vy * dt;\r\n\t\t\tp.Vy \u002B= 180f * dt;\r\n\t\t\tp.Life -= dt;\r\n\t\t\tif ( p.Life \u003C= 0f )\r\n\t\t\t\t_particles.RemoveAt( i );\r\n\t\t}\r\n\r\n\t\tif ( _particles.Count \u003E 0 )\r\n\t\t\t_version\u002B\u002B;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EQuiet little death poof for salinity (not M80 comedy).\u003C/summary\u003E\r\n\tpublic static void SpawnDeathPoof( float x, float y )\r\n\t{\r\n\t\tfor ( var i = 0; i \u003C 10; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tvar ang = (float)(_rng.NextDouble() * Math.PI * 2);\r\n\t\t\tvar spd = 20f \u002B (float)_rng.NextDouble() * 50f;\r\n\t\t\t_particles.Add( new BoomParticle\r\n\t\t\t{\r\n\t\t\t\tX = x,\r\n\t\t\t\tY = y,\r\n\t\t\t\tVx = MathF.Cos( ang ) * spd,\r\n\t\t\t\tVy = MathF.Sin( ang ) * spd - 10f,\r\n\t\t\t\tLife = 0.4f \u002B (float)_rng.NextDouble() * 0.5f,\r\n\t\t\t\tColor = i % 2 == 0 ? \u0022#4a6040\u0022 : \u0022#2a3020\u0022\r\n\t\t\t} );\r\n\t\t}\r\n\t\t_version\u002B\u002B;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003EGold coin confetti for sells / big \u00D0 moments (reuses boom bits pipeline).\u003C/summary\u003E\r\n\tpublic static void SpawnCoinBurst( float x, float y, int count = 14 )\r\n\t{\r\n\t\tcount = Math.Clamp( count, 6, 28 );\r\n\t\tfor ( var i = 0; i \u003C count; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tvar ang = (float)(_rng.NextDouble() * Math.PI * 2);\r\n\t\t\tvar spd = 50f \u002B (float)_rng.NextDouble() * 140f;\r\n\t\t\t_particles.Add( new BoomParticle\r\n\t\t\t{\r\n\t\t\t\tX = x \u002B (float)(_rng.NextDouble() * 10.0 - 5.0),\r\n\t\t\t\tY = y \u002B (float)(_rng.NextDouble() * 8.0 - 4.0),\r\n\t\t\t\tVx = MathF.Cos( ang ) * spd,\r\n\t\t\t\tVy = MathF.Sin( ang ) * spd - 40f - (float)_rng.NextDouble() * 40f,\r\n\t\t\t\tLife = 0.45f \u002B (float)_rng.NextDouble() * 0.55f,\r\n\t\t\t\tColor = (i % 3) switch\r\n\t\t\t\t{\r\n\t\t\t\t\t0 =\u003E \u0022#ffd24a\u0022,\r\n\t\t\t\t\t1 =\u003E \u0022#ffe8a0\u0022,\r\n\t\t\t\t\t_ =\u003E \u0022#c8a020\u0022\r\n\t\t\t\t}\r\n\t\t\t} );\r\n\t\t}\r\n\t\t_version\u002B\u002B;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003ESoft green feed sparkles when pellets land.\u003C/summary\u003E\r\n\tpublic static void SpawnFeedSpark( float x, float y )\r\n\t{\r\n\t\tfor ( var i = 0; i \u003C 6; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tvar ang = (float)(_rng.NextDouble() * Math.PI * 2);\r\n\t\t\tvar spd = 25f \u002B (float)_rng.NextDouble() * 55f;\r\n\t\t\t_particles.Add( new BoomParticle\r\n\t\t\t{\r\n\t\t\t\tX = x,\r\n\t\t\t\tY = y,\r\n\t\t\t\tVx = MathF.Cos( ang ) * spd,\r\n\t\t\t\tVy = MathF.Sin( ang ) * spd - 20f,\r\n\t\t\t\tLife = 0.25f \u002B (float)_rng.NextDouble() * 0.3f,\r\n\t\t\t\tColor = i % 2 == 0 ? \u0022#c8ff90\u0022 : \u0022#e8c060\u0022\r\n\t\t\t} );\r\n\t\t}\r\n\t\t_version\u002B\u002B;\r\n\t}\r\n\r\n\tstatic void SpawnBurst( float x, float y, string colorA, string colorB )\r\n\t{\r\n\t\t// Bigger paste cloud.\r\n\t\tfor ( var i = 0; i \u003C 28; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tvar ang = (float)(_rng.NextDouble() * Math.PI * 2);\r\n\t\t\tvar spd = 80f \u002B (float)_rng.NextDouble() * 220f;\r\n\t\t\t_particles.Add( new BoomParticle\r\n\t\t\t{\r\n\t\t\t\tX = x,\r\n\t\t\t\tY = y,\r\n\t\t\t\tVx = MathF.Cos( ang ) * spd,\r\n\t\t\t\tVy = MathF.Sin( ang ) * spd - 50f,\r\n\t\t\t\tLife = 0.4f \u002B (float)_rng.NextDouble() * 0.7f,\r\n\t\t\t\tColor = (i % 2 == 0) ? colorA : colorB\r\n\t\t\t} );\r\n\t\t}\r\n\r\n\t\t// Hot white core.\r\n\t\tfor ( var i = 0; i \u003C 14; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tvar ang = (float)(_rng.NextDouble() * Math.PI * 2);\r\n\t\t\tvar spd = 60f \u002B (float)_rng.NextDouble() * 160f;\r\n\t\t\t_particles.Add( new BoomParticle\r\n\t\t\t{\r\n\t\t\t\tX = x,\r\n\t\t\t\tY = y,\r\n\t\t\t\tVx = MathF.Cos( ang ) * spd,\r\n\t\t\t\tVy = MathF.Sin( ang ) * spd,\r\n\t\t\t\tLife = 0.2f \u002B (float)_rng.NextDouble() * 0.35f,\r\n\t\t\t\tColor = i % 3 == 0 ? \u0022#ffffff\u0022 : \u0022#ffe080\u0022\r\n\t\t\t} );\r\n\t\t}\r\n\t}\r\n\r\n\tstatic void SpawnM80Bits( float x, float y )\r\n\t{\r\n\t\tfor ( var i = 0; i \u003C 10; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tvar ang = (float)(_rng.NextDouble() * Math.PI * 2);\r\n\t\t\tvar spd = 70f \u002B (float)_rng.NextDouble() * 130f;\r\n\t\t\t_particles.Add( new BoomParticle\r\n\t\t\t{\r\n\t\t\t\tX = x,\r\n\t\t\t\tY = y,\r\n\t\t\t\tVx = MathF.Cos( ang ) * spd,\r\n\t\t\t\tVy = MathF.Sin( ang ) * spd - 30f,\r\n\t\t\t\tLife = 0.45f \u002B (float)_rng.NextDouble() * 0.4f,\r\n\t\t\t\tColor = i % 2 == 0 ? \u0022#c41e1e\u0022 : \u0022#2a2a2a\u0022\r\n\t\t\t} );\r\n\t\t}\r\n\t}\r\n\r\n\tstatic void SpawnFireRing( float x, float y )\r\n\t{\r\n\t\tconst int n = 16;\r\n\t\tfor ( var i = 0; i \u003C n; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tvar ang = (i / (float)n) * MathF.PI * 2f;\r\n\t\t\tvar spd = 140f \u002B (float)_rng.NextDouble() * 40f;\r\n\t\t\tvar color = (i % 3) switch\r\n\t\t\t{\r\n\t\t\t\t0 =\u003E \u0022#ff4020\u0022,\r\n\t\t\t\t1 =\u003E \u0022#ff9020\u0022,\r\n\t\t\t\t_ =\u003E \u0022#ffd040\u0022\r\n\t\t\t};\r\n\t\t\t_particles.Add( new BoomParticle\r\n\t\t\t{\r\n\t\t\t\tX = x,\r\n\t\t\t\tY = y,\r\n\t\t\t\tVx = MathF.Cos( ang ) * spd,\r\n\t\t\t\tVy = MathF.Sin( ang ) * spd,\r\n\t\t\t\tLife = 0.35f \u002B (float)_rng.NextDouble() * 0.25f,\r\n\t\t\t\tColor = color\r\n\t\t\t} );\r\n\t\t}\r\n\t}\r\n}\r\n"},{"Ident":"dogecoin.nochillquarium","Path":"GrandmaGifts.cs","FileName":"GrandmaGifts.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"namespace NoChillquarium;\n\n/// \u003Csummary\u003EUI prop for the gift item popup.\u003C/summary\u003E\npublic enum GrandmaGiftProp\n{\n\tNone,\n\t/// \u003Csummary\u003ENew-game establishing shot \u2014 trailer park before the tank.\u003C/summary\u003E\n\tEstablishing,\n\tTank,\n\tWallet,\n\t/// \u003Csummary\u003ECombine / fusion unlock (janitor intro).\u003C/summary\u003E\n\tFusion,\n\t/// \u003Csummary\u003EFight unlock (lot bully challenge).\u003C/summary\u003E\n\tFight,\n\t/// \u003Csummary\u003EAdventure Mode unlock (flush) \u2014 last ladder intro.\u003C/summary\u003E\n\tAdventure\n}\n\n/// \u003Csummary\u003EWhere we are in a gift sequence.\u003C/summary\u003E\npublic enum GrandmaGiftPhase\n{\n\t/// \u003Csummary\u003EPlaying; watching for triggers.\u003C/summary\u003E\n\tIdle,\n\t/// \u003Csummary\u003ERPG typewriter dialogue.\u003C/summary\u003E\n\tDialogue,\n\t/// \u003Csummary\u003EItem received popup after dialogue.\u003C/summary\u003E\n\tItemPopup\n}\n\n/// \u003Csummary\u003EOne lot character gift/event: trigger \u2192 dialogue \u2192 item popup.\u003C/summary\u003E\npublic sealed class GrandmaGiftDef\n{\n\tpublic string Id { get; init; }\n\tpublic string[] Lines { get; init; }\n\tpublic string ItemTitle { get; init; }\n\tpublic string ItemBody { get; init; }\n\tpublic GrandmaGiftProp Prop { get; init; }\n\tpublic string PopupButton { get; init; } = \u0022Got it!\u0022;\n\t/// \u003Csummary\u003EDialogue nameplate (default Grandma).\u003C/summary\u003E\n\tpublic string Speaker { get; init; } = \u0022Grandma\u0022;\n\t/// \u003Csummary\u003EPortrait sprite id (default ui_grandma).\u003C/summary\u003E\n\tpublic string PortraitSprite { get; init; } = \u0022ui_grandma\u0022;\n\t/// \u003Csummary\u003EOptional CSS theme on the dialogue shell (e.g. janitor).\u003C/summary\u003E\n\tpublic string ThemeClass { get; init; } = \u0022\u0022;\n\t/// \u003Csummary\u003EMin total playtime before auto-check can fire.\u003C/summary\u003E\n\tpublic float MinPlaytime { get; init; }\n\t/// \u003Csummary\u003E\n\t/// Seconds of playtime that must pass after the previous gift claim before this one can fire.\n\t/// Lets the player use the last unlock before the next intro. 0 = no gap (onboarding chain).\n\t/// \u003C/summary\u003E\n\tpublic float MinGapAfterPrior { get; init; }\n\t/// \u003Csummary\u003EOptional: must already have received this gift id.\u003C/summary\u003E\n\tpublic string RequiresGiftId { get; init; }\n\t/// \u003Csummary\u003EExtra world condition (null = only playtime / flags).\u003C/summary\u003E\n\tpublic Func\u003Cbool\u003E ExtraReady { get; init; }\n\t/// \u003Csummary\u003E\n\t/// Base \u00D0 paid when the scene finishes (item popup closed).\n\t/// Long scenes get a per-line bonus on top \u2014 sitting through dialogue pays.\n\t/// \u003C/summary\u003E\n\tpublic double DogeReward { get; init; }\n\t/// \u003Csummary\u003EExtra karma on claim (on top of anything in OnClaimed).\u003C/summary\u003E\n\tpublic float KarmaReward { get; init; }\n\t/// \u003Csummary\u003ERun when the player closes the item popup (after cash/karma grant).\u003C/summary\u003E\n\tpublic Action OnClaimed { get; init; }\n}\n\n/// \u003Csummary\u003E\n/// Play around \u2192 hit a gift trigger \u2192 Grandma dialogue \u2192 item popup.\n/// One-shot per save. Extend \u003Csee cref=\u0022Catalog\u0022/\u003E for more gifts.\n/// \u003C/summary\u003E\npublic static class GrandmaGifts\n{\n\t/// \u003Csummary\u003EFast typewriter \u2014 click still skips / advances hard.\u003C/summary\u003E\n\tpublic const float TypeCharsPerSecond = 96f;\n\n\tstatic readonly HashSet\u003Cstring\u003E _received = new( StringComparer.OrdinalIgnoreCase );\n\tstatic GrandmaGiftPhase _phase = GrandmaGiftPhase.Idle;\n\tstatic GrandmaGiftDef _active;\n\tstatic int _line;\n\tstatic int _visibleChars;\n\tstatic float _typeTimer;\n\tstatic int _version;\n\t/// \u003Csummary\u003ESeconds of active play this run (for \u201Cplay a little first\u201D).\u003C/summary\u003E\n\tstatic float _sessionPlay;\n\t/// \u003Csummary\u003EEconomy.PlaytimeSeconds at last gift claim (persisted) \u2014 enforces MinGapAfterPrior.\u003C/summary\u003E\n\tstatic float _lastGiftClaimPlaytime;\n\t/// \u003Csummary\u003EOne recovery attempt if tank gift was claimed but glass is empty.\u003C/summary\u003E\n\tstatic bool _triedTankRecovery;\n\n\t/// \u003Csummary\u003EDefault gap between mid-ladder gifts when a def uses the shared floor.\u003C/summary\u003E\n\tpublic const float DefaultLadderGap = 35f;\n\n\tpublic static int Version =\u003E _version;\n\tpublic static GrandmaGiftPhase Phase =\u003E _phase;\n\tpublic static bool IsBusy =\u003E _phase != GrandmaGiftPhase.Idle;\n\tpublic static bool IsDialogue =\u003E _phase == GrandmaGiftPhase.Dialogue;\n\tpublic static bool IsItemPopup =\u003E _phase == GrandmaGiftPhase.ItemPopup;\n\tpublic static GrandmaGiftDef Active =\u003E _active;\n\tpublic static string ActiveId =\u003E _active?.Id ?? \u0022\u0022;\n\tpublic static GrandmaGiftProp ActiveProp =\u003E _active?.Prop ?? GrandmaGiftProp.None;\n\tpublic static int LineIndex =\u003E _line;\n\tpublic static int LineCount =\u003E _active?.Lines?.Length ?? 0;\n\n\tpublic static string Speaker =\u003E\n\t\tstring.IsNullOrWhiteSpace( _active?.Speaker ) ? \u0022Grandma\u0022 : _active.Speaker;\n\n\t/// \u003Csummary\u003EEmpty string = no portrait (You monologue). Null/default \u2192 Grandma.\u003C/summary\u003E\n\tpublic static string PortraitSprite\n\t{\n\t\tget\n\t\t{\n\t\t\tif ( _active is null )\n\t\t\t\treturn \u0022ui_grandma\u0022;\n\t\t\t// Explicit empty on the def = hide portrait.\n\t\t\tif ( _active.PortraitSprite is not null \u0026\u0026 _active.PortraitSprite.Length == 0 )\n\t\t\t\treturn \u0022\u0022;\n\t\t\treturn string.IsNullOrWhiteSpace( _active.PortraitSprite ) ? \u0022ui_grandma\u0022 : _active.PortraitSprite;\n\t\t}\n\t}\n\n\tpublic static string ThemeClass =\u003E _active?.ThemeClass ?? \u0022\u0022;\n\n\t/// \u003Csummary\u003ECSS scene class for the dialogue backdrop (avoid multi-line ternaries in Razor).\u003C/summary\u003E\n\tpublic static string SceneCssClass =\u003E ActiveProp switch\n\t{\n\t\tGrandmaGiftProp.Establishing =\u003E \u0022scene-lot\u0022,\n\t\tGrandmaGiftProp.Wallet =\u003E \u0022scene-wallet\u0022,\n\t\tGrandmaGiftProp.Fusion =\u003E \u0022scene-fusion\u0022,\n\t\tGrandmaGiftProp.Fight =\u003E \u0022scene-fight\u0022,\n\t\tGrandmaGiftProp.Adventure =\u003E \u0022scene-adventure\u0022,\n\t\t_ =\u003E \u0022scene-tank\u0022\n\t};\n\n\t/// \u003Csummary\u003EITEM GET vs NEW MOVE / CHALLENGE kicker.\u003C/summary\u003E\n\tpublic static string PopupKicker =\u003E ActiveProp switch\n\t{\n\t\tGrandmaGiftProp.Establishing =\u003E \u0022SCHOOL NOTICE\u0022,\n\t\tGrandmaGiftProp.Fusion =\u003E \u0022NEW MOVE\u0022,\n\t\tGrandmaGiftProp.Fight =\u003E \u0022CHALLENGE\u0022,\n\t\tGrandmaGiftProp.Adventure =\u003E \u0022NEW MODE\u0022,\n\t\t_ =\u003E \u0022ITEM GET\u0022\n\t};\n\n\t/// \u003Csummary\u003EAdventure Mode intro claimed (or mid-empire save). Gates Flush UI.\u003C/summary\u003E\n\tpublic static bool HasAdventureIntro =\u003E HasReceived( \u0022adventure_intro\u0022 );\n\n\t/// \u003Csummary\u003EPlaytime left before the next gap-gated gift can fire (0 = free).\u003C/summary\u003E\n\tpublic static float GiftGapSecondsLeft\n\t{\n\t\tget\n\t\t{\n\t\t\tif ( _lastGiftClaimPlaytime \u003C= 0f )\n\t\t\t\treturn 0f;\n\t\t\t// Next pending gift\u0027s gap \u2014 use the largest still-relevant ladder gap as UI soft hint.\n\t\t\tvar need = NextPendingMinGap();\n\t\t\tif ( need \u003C= 0f )\n\t\t\t\treturn 0f;\n\t\t\tvar readyAt = _lastGiftClaimPlaytime \u002B need;\n\t\t\treturn MathF.Max( 0f, readyAt - Economy.PlaytimeSeconds );\n\t\t}\n\t}\n\n\tstatic float NextPendingMinGap()\n\t{\n\t\tvar best = 0f;\n\t\tforeach ( var g in Catalog )\n\t\t{\n\t\t\tif ( g is null || _received.Contains( g.Id ) )\n\t\t\t\tcontinue;\n\t\t\tif ( g.MinGapAfterPrior \u003E best )\n\t\t\t\tbest = g.MinGapAfterPrior;\n\t\t\t// Only care about the first unreceived mid-ladder gift in order.\n\t\t\tif ( g.MinGapAfterPrior \u003E 0f )\n\t\t\t\treturn g.MinGapAfterPrior;\n\t\t}\n\t\treturn best;\n\t}\n\n\t/// \u003Csummary\u003EPorch tank exists \u2014 HUD / glass after Grandma gifts it.\u003C/summary\u003E\n\tpublic static bool HasTank =\u003E HasReceived( \u0022fish_tank\u0022 );\n\n\t/// \u003Csummary\u003EPre-game trailer-lot inner monologue (looking around before tank).\u003C/summary\u003E\n\tpublic static bool IsLookingAround =\u003E\n\t\tIsBusy \u0026\u0026 ActiveProp == GrandmaGiftProp.Establishing;\n\n\tpublic static IReadOnlyList\u003CGrandmaGiftDef\u003E Catalog { get; } = new[]\n\t{\n\t\t// Jr-high troublemaker walks home with a suspension form (Bully-energy kid, not a sad softie).\n\t\tnew GrandmaGiftDef\n\t\t{\n\t\t\tId = \u0022lot_establishing\u0022,\n\t\t\tMinPlaytime = 0.15f,\n\t\t\tSpeaker = \u0022You\u0022,\n\t\t\tPortraitSprite = \u0022ui_you\u0022,\n\t\t\tThemeClass = \u0022you-theme\u0022,\n\t\t\tItemTitle = \u0022Suspension Form\u0022,\n\t\t\tItemBody = \u0022Jr high paper. Warm. Embarrassing. Show Grandma.\u0022,\n\t\t\tProp = GrandmaGiftProp.Establishing,\n\t\t\tPopupButton = \u0022Show Grandma\u0022,\n\t\t\tLines = new[]\n\t\t\t{\n\t\t\t\t\u0022Suspended. Again. Form\u0027s damp, gravel\u0027s eating my shoes. Hi. Yeah. You\u0027re the one with the mouse. Don\u0027t make it weird. This is my life.\u0022,\n\t\t\t\t\u0022Brad from 4B is already drafting the rumor like it\u0027s a screenplay. His toilet runs all night. Spoilers: that\u0027s a plant. Think about it later.\u0022,\n\t\t\t\t\u0022Porch light\u0027s on. Cookies and judgment. She\u0027s gonna roast me, then help. That\u0027s the deal. That\u0027s the whole movie. Credits after cookies.\u0022,\n\t\t\t},\n\t\t\tDogeReward = 3,\n\t\t\tKarmaReward = 1f,\n\t\t\tOnClaimed = () =\u003E\n\t\t\t{\n\t\t\t\tTankSim.ShowBanner( \u0022Form ready. Chin up. She already knows. She always knows.\u0022 );\n\t\t\t}\n\t\t},\n\t\tnew GrandmaGiftDef\n\t\t{\n\t\t\tId = \u0022fish_tank\u0022,\n\t\t\tMinPlaytime = 0f,\n\t\t\tRequiresGiftId = \u0022lot_establishing\u0022,\n\t\t\tSpeaker = \u0022Grandma\u0022,\n\t\t\tPortraitSprite = \u0022ui_grandma\u0022,\n\t\t\tItemTitle = \u0022Your First Fish Tank\u0022,\n\t\t\tItemBody = \u0022Porch glass \u002B two goldfish. Don\u0027t drown them.\u0022,\n\t\t\tProp = GrandmaGiftProp.Tank,\n\t\t\tPopupButton = \u0022Thanks, Grandma\u0022,\n\t\t\tDogeReward = 4,\n\t\t\tKarmaReward = 2f,\n\t\t\tLines = new[]\n\t\t\t{\n\t\t\t\t\u0022Suspended again? Wipe your feet. I\u0027ve been madder about burnt toast. Sit. Don\u0027t narrate. Breathe.\u0022,\n\t\t\t\t\u0022I don\u0027t lecture. I gift. First fish tank \u2014 two goldfish. Feed \u0027em or they become supper, and I will make that a Tuesday.\u0022,\n\t\t\t\t\u0022You\u0027re still my punk. Don\u0027t drown them. Love you. Go be useful trouble. Cookies are coming. Act like you live here.\u0022,\n\t\t\t},\n\t\t\tOnClaimed = () =\u003E\n\t\t\t{\n\t\t\t\tTankSim.GrantTrailerLotTank();\n\t\t\t\tStorySystem.OpenFirstCardAfterPrologue();\n\t\t\t\tFeedSystem.Grant( FoodDef.Flakes, announce: false );\n\t\t\t\tTankSim.ShowBanner( \u0022Flakes in the bag. Tools \u2192 Feed. She packed you a lunch like you\u0027re still employable.\u0022 );\n\t\t\t}\n\t\t},\n\t\tnew GrandmaGiftDef\n\t\t{\n\t\t\tId = \u0022cold_wallet\u0022,\n\t\t\t// First feed \u2192 wallet almost immediately. Numbers on screen = the hook.\n\t\t\tMinPlaytime = 12f,\n\t\t\tMinGapAfterPrior = 10f,\n\t\t\tRequiresGiftId = \u0022fish_tank\u0022,\n\t\t\tSpeaker = \u0022Grandma\u0022,\n\t\t\tPortraitSprite = \u0022ui_grandma\u0022,\n\t\t\tExtraReady = () =\u003E\n\t\t\t\tStorySystem.HasFedOnce\n\t\t\t\t|| Economy.LifetimeEarned \u003E= 3\n\t\t\t\t|| StorySystem.HasPassedBeat( \u0022feed\u0022 ),\n\t\t\tItemTitle = \u0022Dogecoin Cold Wallet\u0022,\n\t\t\tItemBody = \u0022Grandpa\u0027s 2014 miner. Tank keeps it ticking. Funeral canceled.\u0022,\n\t\t\tProp = GrandmaGiftProp.Wallet,\n\t\t\tPopupButton = \u0022Take wallet\u0022,\n\t\t\tDogeReward = 10,\n\t\t\tKarmaReward = 1f,\n\t\t\tLines = new[]\n\t\t\t{\n\t\t\t\t\u0022Your grandpa mined this junk in 2014. Tower\u0027s still under the porch. I ran the filter through it so the box would shut up. Worked. Sort of. Now it\u0027s your problem.\u0022,\n\t\t\t\t\u0022Fish swim, pump pulls, leftover dog money drips into this tin. Was for my funeral. Plans change. Don\u0027t look at me like that. I\u0027m not dying for your upgrade path.\u0022,\n\t\t\t\t\u0022Yours now. Fill the glass, fill the tin. Food. Not detention candy. Tap it if your brain falls out. Bingo\u0027s at seven. I am serious about bingo.\u0022,\n\t\t\t},\n\t\t\tOnClaimed = () =\u003E\n\t\t\t{\n\t\t\t\tStorySystem.NotifyWalletGiftClaimed();\n\t\t\t}\n\t\t},\n\t\t// Fusion intro \u2014 after a little porch play, not a five-minute detention.\n\t\tnew GrandmaGiftDef\n\t\t{\n\t\t\tId = \u0022janitor_fusion\u0022,\n\t\t\tMinPlaytime = 70f,\n\t\t\tMinGapAfterPrior = 55f,\n\t\t\tRequiresGiftId = \u0022cold_wallet\u0022,\n\t\t\tSpeaker = \u0022Janitor \u00B7 outside\u0022,\n\t\t\tPortraitSprite = \u0022ui_janitor\u0022,\n\t\t\tThemeClass = \u0022theme-janitor\u0022,\n\t\t\tExtraReady = () =\u003E\n\t\t\t\tProgression.CombineUnlocked\n\t\t\t\t\u0026\u0026 TankSim.TotalFishCount \u003E= 2\n\t\t\t\t\u0026\u0026 !StorySystem.HasCombinedOnce\n\t\t\t\t\u0026\u0026 Economy.LifetimeEarned \u003E= 12\n\t\t\t\t\u0026\u0026 (StorySystem.HasSoldOnce\n\t\t\t\t\t|| StorySystem.HasTrainedOnce\n\t\t\t\t\t|| TankSim.TotalFishCount \u003E= 3\n\t\t\t\t\t|| StorySystem.HasPassedBeat( \u0022sell_tip\u0022 )\n\t\t\t\t\t|| Economy.PlaytimeSeconds \u003E= 90f),\n\t\t\tItemTitle = \u0022Combine\u0022,\n\t\t\tItemBody = \u0022Fish menu \u2192 Combine. Mash two fish (or a fish \u002B a flamingo). Pays \u00D0.\u0022,\n\t\t\tProp = GrandmaGiftProp.Fusion,\n\t\t\tPopupButton = \u0022Got it\u0022,\n\t\t\tDogeReward = 14,\n\t\t\tKarmaReward = 0.5f,\n\t\t\tLines = new[]\n\t\t\t{\n\t\t\t\t\u0022*tap tap* Don\u0027t mind the face. I don\u0027t pry. I lurk. Professionally. I\u0027m not even supposed to be on this shift. Brad only respects monsters. I am also a suggestion.\u0022,\n\t\t\t\t\u0022Mmm. Lot janitor. Stick two fish together. Or a fish and a flamingo. Pays \u00D0. Grandma\u0027ll call it ugly. That\u0027s a compliment where I\u0027m from. Where I\u0027m from is a closet.\u0022,\n\t\t\t\t\u0022Fish menu. Combine. Click another. Explodes? Your floor. I mop. The pipes are later. I\u0027ll be back. I always am. That\u0027s the fun part. HR knows. HR is me.\u0022,\n\t\t\t},\n\t\t\tOnClaimed = () =\u003E\n\t\t\t{\n\t\t\t\tChaos.Add( 1.5f );\n\t\t\t}\n\t\t},\n\t\t// Brad \u2014 after train \u002B combine practice, without multi-minute dead air.\n\t\tnew GrandmaGiftDef\n\t\t{\n\t\t\tId = \u0022lot_bully_fight\u0022,\n\t\t\tMinPlaytime = 110f,\n\t\t\tMinGapAfterPrior = 60f,\n\t\t\tRequiresGiftId = \u0022cold_wallet\u0022,\n\t\t\tSpeaker = \u0022Brad\u0022,\n\t\t\tPortraitSprite = \u0022ui_bully\u0022,\n\t\t\tThemeClass = \u0022theme-bully\u0022,\n\t\t\tExtraReady = () =\u003E\n\t\t\t\tStorySystem.HasTrainedOnce\n\t\t\t\t\u0026\u0026 BattleSystem.CanAffordAnyFight()\n\t\t\t\t\u0026\u0026 BattleSystem.Wins \u002B BattleSystem.Losses == 0\n\t\t\t\t// Practice combine first \u2014 risk two fish before garage receipts.\n\t\t\t\t\u0026\u0026 (StorySystem.HasCombinedOnce || TankSim.AbominationsCreated \u003E 0)\n\t\t\t\t\u0026\u0026 Economy.LifetimeEarned \u003E= 22,\n\t\t\tItemTitle = \u0022Fight\u0022,\n\t\t\tItemBody = \u0022Fight \u2192 Garage Guppies. \u00D0 entry. Brad talks receipts.\u0022,\n\t\t\tProp = GrandmaGiftProp.Fight,\n\t\t\tPopupButton = \u0022You\u0027re on\u0022,\n\t\t\tDogeReward = 16,\n\t\t\tKarmaReward = 1f,\n\t\t\tLines = new[]\n\t\t\t{\n\t\t\t\t\u0022Yo. Brad. Unit 4B. This is the garage circuit, not your hallway slap-fight fanfic. That porch glass? Homework. Wipe the smile off. I can hear it from here.\u0022,\n\t\t\t\t\u0022\u00D0 entry. One fish. Winner takes the pot. Your grandma already bet against me. That\u0027s rude. That\u0027s laminated. I laminate rude.\u0022,\n\t\t\t\t\u0022Fight \u2192 Garage Guppies. Bring a sweaty one. Or the ugly science project the window guy made. I keep receipts. I keep copies of the receipts.\u0022,\n\t\t\t},\n\t\t\tOnClaimed = () =\u003E\n\t\t\t{\n\t\t\t\tChaos.Add( 2f );\n\t\t\t\tTankSim.ShowBanner( \u0022Brad\u0027s garage is open. Make him laminate a loss. He will. That\u0027s his whole personality.\u0022 );\n\t\t\t}\n\t\t},\n\t\t// Adventure \u2014 last; after aboms \u002B a fight, still inside the fun window.\n\t\tnew GrandmaGiftDef\n\t\t{\n\t\t\tId = \u0022adventure_intro\u0022,\n\t\t\tMinPlaytime = 180f,\n\t\t\tMinGapAfterPrior = 90f,\n\t\t\tRequiresGiftId = \u0022cold_wallet\u0022,\n\t\t\tSpeaker = \u0022You\u0022,\n\t\t\tPortraitSprite = \u0022ui_you\u0022,\n\t\t\tThemeClass = \u0022you-theme\u0022,\n\t\t\tExtraReady = () =\u003E\n\t\t\t{\n\t\t\t\tvar practicedCombine = StorySystem.HasCombinedOnce || TankSim.AbominationsCreated \u003E 0;\n\t\t\t\tvar practicedFight = BattleSystem.Wins \u002B BattleSystem.Losses \u003E= 1;\n\t\t\t\treturn practicedCombine\n\t\t\t\t\t\u0026\u0026 practicedFight\n\t\t\t\t\t\u0026\u0026 TankSim.AbominationsCreated \u003E= 1\n\t\t\t\t\t\u0026\u0026 Economy.LifetimeEarned \u003E= 45\n\t\t\t\t\t\u0026\u0026 TankSim.TotalFishCount \u003E= 1\n\t\t\t\t\t\u0026\u0026 StorySystem.HasFedOnce;\n\t\t\t},\n\t\t\tItemTitle = \u0022Adventure Mode\u0022,\n\t\t\tItemBody = \u0022Fish menu \u2192 Flush. Die \u2192 faucet home. Survive \u2192 more maps.\u0022,\n\t\t\tProp = GrandmaGiftProp.Adventure,\n\t\t\tPopupButton = \u0022Alright\u0022,\n\t\t\tDogeReward = 24,\n\t\t\tKarmaReward = 0.5f,\n\t\t\tLines = new[]\n\t\t\t{\n\t\t\t\t\u0022The pipes under the lot go farther than the trailer. I heard 4B\u0027s toilet on the walk home. Yeah. That was a plant. You\u0027re welcome.\u0022,\n\t\t\t\t\u0022Garage was a box. This is the rest of Sunny Pines. Flush a trained fish. They die, kitchen faucet coughs them home hurt. She won\u0027t look up. That\u0027s love. That\u0027s also denial.\u0022,\n\t\t\t\t\u0022Survive and more maps open. Fish menu \u2192 Flush. The toilet is a dungeon now. That\u0027s our life. That\u0027s the lot. Don\u0027t tell marketing.\u0022,\n\t\t\t},\n\t\t\tOnClaimed = () =\u003E\n\t\t\t{\n\t\t\t\tChaos.Add( 2f );\n\t\t\t}\n\t\t},\n\t};\n\n\tpublic static bool HasReceived( string id ) =\u003E\n\t\t!string.IsNullOrEmpty( id ) \u0026\u0026 _received.Contains( id );\n\n\tpublic static string CurrentLine\n\t{\n\t\tget\n\t\t{\n\t\t\tif ( _active?.Lines is null || _line \u003C 0 || _line \u003E= _active.Lines.Length )\n\t\t\t\treturn \u0022\u0022;\n\t\t\treturn _active.Lines[_line];\n\t\t}\n\t}\n\n\tpublic static string VisibleText\n\t{\n\t\tget\n\t\t{\n\t\t\tvar full = CurrentLine;\n\t\t\tif ( string.IsNullOrEmpty( full ) )\n\t\t\t\treturn \u0022\u0022;\n\t\t\treturn full[..Math.Clamp( _visibleChars, 0, full.Length )];\n\t\t}\n\t}\n\n\tpublic static bool LineFullyTyped\n\t{\n\t\tget\n\t\t{\n\t\t\tvar full = CurrentLine;\n\t\t\treturn string.IsNullOrEmpty( full ) || _visibleChars \u003E= full.Length;\n\t\t}\n\t}\n\n\tpublic static bool IsLastLine =\u003E\n\t\t_active?.Lines is not null \u0026\u0026 _line \u003E= _active.Lines.Length - 1;\n\n\tpublic static string AdvanceHint\n\t{\n\t\tget\n\t\t{\n\t\t\tif ( _phase == GrandmaGiftPhase.ItemPopup )\n\t\t\t\treturn _active?.PopupButton ?? \u0022Got it!\u0022;\n\t\t\t// One click always moves the scene forward (skip type \u002B next, or claim).\n\t\t\tif ( ActiveProp == GrandmaGiftProp.Establishing )\n\t\t\t\treturn IsLastLine ? \u0022click \u00B7 porch\u0022 : \u0022click \u00B7 walk\u0022;\n\t\t\treturn IsLastLine ? \u0022Click \u00B7 done\u0022 : \u0022Click \u00B7 next\u0022;\n\t\t}\n\t}\n\n\tpublic static string ItemTitle =\u003E _active?.ItemTitle ?? \u0022\u0022;\n\tpublic static string ItemBody =\u003E _active?.ItemBody ?? \u0022\u0022;\n\tpublic static string PopupButton =\u003E _active?.PopupButton ?? \u0022Got it!\u0022;\n\n\t/// \u003Csummary\u003E\n\t/// Preformatted LCD text for gift UI. Never build \u0022\u00D0@expr\u0022 in Razor \u2014 S\u0026box prints it raw.\n\t/// \u003C/summary\u003E\n\tpublic static string WalletBalanceLabel =\u003E\n\t\t\u0022D\u0022 \u002B Economy.FormatDoge( Economy.Balance );\n\n\t// ---- Lifecycle ----\n\n\tpublic static void ResetForNewGame()\n\t{\n\t\t_received.Clear();\n\t\t_sessionPlay = 0f;\n\t\t_lastGiftClaimPlaytime = 0f;\n\t\t_triedTankRecovery = false;\n\t\tClearActive();\n\t}\n\n\tpublic static void Clear()\n\t{\n\t\t_sessionPlay = 0f;\n\t\t// Keep _lastGiftClaimPlaytime across soft clears mid-session; Load/Reset own it.\n\t\t_triedTankRecovery = false;\n\t\tClearActive();\n\t}\n\n\tstatic void ClearActive()\n\t{\n\t\t_phase = GrandmaGiftPhase.Idle;\n\t\t_active = null;\n\t\t_line = 0;\n\t\t_visibleChars = 0;\n\t\t_typeTimer = 0f;\n\t\t_version\u002B\u002B;\n\t}\n\n\tpublic static void Load( SaveData data )\n\t{\n\t\t_received.Clear();\n\t\t_sessionPlay = 0f;\n\t\t_lastGiftClaimPlaytime = MathF.Max( 0f, data?.LastGiftClaimPlaytime ?? 0f );\n\t\tClearActive();\n\t\tif ( data?.GrandmaGiftsReceived is null )\n\t\t{\n\t\t\t_version\u002B\u002B;\n\t\t\treturn;\n\t\t}\n\t\tforeach ( var id in data.GrandmaGiftsReceived )\n\t\t{\n\t\t\tif ( !string.IsNullOrWhiteSpace( id ) )\n\t\t\t\t_received.Add( id.Trim() );\n\t\t}\n\t\t// Old saves: already past tutorial \u2014 mark starter gifts done so they don\u0027t re-fire.\n\t\tif ( data.StoryDone || data.PlaytimeSeconds \u003E 90f || data.LifetimeEarned \u003E 40\n\t\t\t|| (data.FreshFish is { Count: \u003E 0 }) || (data.Fish is { Count: \u003E 0 }) )\n\t\t{\n\t\t\t_received.Add( \u0022lot_establishing\u0022 );\n\t\t\t_received.Add( \u0022fish_tank\u0022 );\n\t\t\t_received.Add( \u0022cold_wallet\u0022 );\n\t\t}\n\t\tif ( data.StoryDone || data.StoryCombinedOnce || data.AbominationsCreated \u003E 0 )\n\t\t\t_received.Add( \u0022janitor_fusion\u0022 );\n\t\tif ( data.StoryDone || data.BattleWins \u002B data.BattleLosses \u003E 0 )\n\t\t\t_received.Add( \u0022lot_bully_fight\u0022 );\n\t\t// Old / mid-empire saves: already flushing or deep in free play \u2014 skip re-intro.\n\t\tif ( data.StoryDone || data.AdventureFlushed || data.PlaytimeSeconds \u003E 600f\n\t\t\t|| (data.BattleWins \u002B data.BattleLosses \u003E= 2\n\t\t\t\t\u0026\u0026 (data.StoryCombinedOnce || data.AbominationsCreated \u003E 0)) )\n\t\t\t_received.Add( \u0022adventure_intro\u0022 );\n\n\t\t// If we seeded gifts on old saves without a claim stamp, don\u0027t force a long gap.\n\t\tif ( _lastGiftClaimPlaytime \u003C= 0f \u0026\u0026 _received.Count \u003E 0 )\n\t\t\t_lastGiftClaimPlaytime = MathF.Max( 0f, Economy.PlaytimeSeconds - DefaultLadderGap );\n\n\t\t// Recover: tank gift marked received but fish never spawned (pre-fix save / failed grant).\n\t\tif ( _received.Contains( \u0022fish_tank\u0022 ) \u0026\u0026 TankSim.TotalFishCount == 0 \u0026\u0026 TankSim.IsActive )\n\t\t\tTankSim.GrantTrailerLotTank();\n\n\t\t_version\u002B\u002B;\n\t}\n\n\tpublic static void WriteToSave( SaveData data )\n\t{\n\t\tif ( data is null )\n\t\t\treturn;\n\t\tdata.GrandmaGiftsReceived = _received.ToList();\n\t\tdata.LastGiftClaimPlaytime = _lastGiftClaimPlaytime;\n\t}\n\n\t// ---- Triggers ----\n\n\t/// \u003Csummary\u003E\n\t/// Call from play tick. After a little playtime, auto-fires ready gifts.\n\t/// \u003C/summary\u003E\n\tpublic static void Tick( float dt )\n\t{\n\t\tif ( dt \u003C= 0f || !TankSim.IsActive )\n\t\t\treturn;\n\n\t\tif ( _phase == GrandmaGiftPhase.Dialogue )\n\t\t{\n\t\t\tTickTypewriter( dt );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( _phase != GrandmaGiftPhase.Idle )\n\t\t\treturn;\n\n\t\t// Don\u0027t interrupt other full-screen UI \u2014 or a Golden/Rainbow stamp.\n\t\tif ( StorySystem.CardOpen || ShopOpenGuard() || BattleSystem.IsOpen\n\t\t\t|| TrainingSystem.TrainOpen || TrainingSystem.InjectOpen || MethRush.Open\n\t\t\t|| BuyReveal.IsActive )\n\t\t\treturn;\n\n\t\t_sessionPlay \u002B= dt;\n\t\tTryAutoTrigger();\n\n\t\t// Safety net (once per session): claimed tank but empty glass.\n\t\tif ( !_triedTankRecovery\n\t\t\t\u0026\u0026 _phase == GrandmaGiftPhase.Idle\n\t\t\t\u0026\u0026 _received.Contains( \u0022fish_tank\u0022 )\n\t\t\t\u0026\u0026 TankSim.TotalFishCount == 0 )\n\t\t{\n\t\t\t_triedTankRecovery = true;\n\t\t\tTankSim.GrantTrailerLotTank();\n\t\t}\n\t}\n\n\tstatic bool ShopOpenGuard() =\u003E Shop.UiOpen;\n\n\tstatic void TryAutoTrigger()\n\t{\n\t\ttry\n\t\t{\n\t\t\tforeach ( var gift in Catalog )\n\t\t\t{\n\t\t\t\tif ( !IsReady( gift ) )\n\t\t\t\t\tcontinue;\n\t\t\t\tBeginGift( gift );\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tcatch ( Exception e )\n\t\t{\n\t\t\tLog.Warning( $\u0022[NO-CHILLquarium] GrandmaGifts trigger failed: {e.Message}\u0022 );\n\t\t}\n\t}\n\n\tpublic static bool IsReady( GrandmaGiftDef gift )\n\t{\n\t\tif ( gift is null || string.IsNullOrEmpty( gift.Id ) )\n\t\t\treturn false;\n\t\tif ( _received.Contains( gift.Id ) )\n\t\t\treturn false;\n\t\tif ( _phase != GrandmaGiftPhase.Idle )\n\t\t\treturn false;\n\t\tif ( _sessionPlay \u003C gift.MinPlaytime \u0026\u0026 Economy.PlaytimeSeconds \u003C gift.MinPlaytime )\n\t\t\treturn false;\n\t\t// Space ladder intros \u2014 player should play between unlocks.\n\t\tif ( gift.MinGapAfterPrior \u003E 0f \u0026\u0026 _lastGiftClaimPlaytime \u003E 0f )\n\t\t{\n\t\t\tif ( Economy.PlaytimeSeconds \u003C _lastGiftClaimPlaytime \u002B gift.MinGapAfterPrior )\n\t\t\t\treturn false;\n\t\t}\n\t\tif ( !string.IsNullOrEmpty( gift.RequiresGiftId ) \u0026\u0026 !_received.Contains( gift.RequiresGiftId ) )\n\t\t\treturn false;\n\t\tif ( gift.ExtraReady is not null )\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tif ( !gift.ExtraReady() )\n\t\t\t\t\treturn false;\n\t\t\t}\n\t\t\tcatch ( Exception e )\n\t\t\t{\n\t\t\t\tLog.Warning( $\u0022[NO-CHILLquarium] GrandmaGifts ExtraReady ({gift.Id}): {e.Message}\u0022 );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t}\n\n\t/// \u003Csummary\u003ETrue when skill gates pass but the play-gap is still cooling down.\u003C/summary\u003E\n\tpublic static bool IsWaitingOnGiftGap( GrandmaGiftDef gift )\n\t{\n\t\tif ( gift is null || string.IsNullOrEmpty( gift.Id ) || _received.Contains( gift.Id ) )\n\t\t\treturn false;\n\t\tif ( gift.MinGapAfterPrior \u003C= 0f || _lastGiftClaimPlaytime \u003C= 0f )\n\t\t\treturn false;\n\t\tif ( Economy.PlaytimeSeconds \u003E= _lastGiftClaimPlaytime \u002B gift.MinGapAfterPrior )\n\t\t\treturn false;\n\t\t// Soft check: prior gift \u002B base playtime OK, ExtraReady not required (avoid false \u0022waiting\u0022).\n\t\tif ( !string.IsNullOrEmpty( gift.RequiresGiftId ) \u0026\u0026 !_received.Contains( gift.RequiresGiftId ) )\n\t\t\treturn false;\n\t\tif ( _sessionPlay \u003C gift.MinPlaytime \u0026\u0026 Economy.PlaytimeSeconds \u003C gift.MinPlaytime )\n\t\t\treturn false;\n\t\treturn true;\n\t}\n\n\t/// \u003Csummary\u003EForce a gift (debug / story hooks). No-op if already received or busy.\u003C/summary\u003E\n\tpublic static bool TryTrigger( string id )\n\t{\n\t\tif ( _phase != GrandmaGiftPhase.Idle )\n\t\t\treturn false;\n\t\tGrandmaGiftDef gift = null;\n\t\tfor ( var i = 0; i \u003C Catalog.Count; i\u002B\u002B )\n\t\t{\n\t\t\tif ( string.Equals( Catalog[i].Id, id, StringComparison.OrdinalIgnoreCase ) )\n\t\t\t{\n\t\t\t\tgift = Catalog[i];\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif ( gift is null || _received.Contains( gift.Id ) )\n\t\t\treturn false;\n\t\tBeginGift( gift );\n\t\treturn true;\n\t}\n\n\tstatic void BeginGift( GrandmaGiftDef gift )\n\t{\n\t\t_active = gift;\n\t\t_phase = GrandmaGiftPhase.Dialogue;\n\t\t_line = 0;\n\t\t_visibleChars = 0;\n\t\t_typeTimer = 0f;\n\t\t_version\u002B\u002B;\n\t\tGameAudio.PlayUi( GameAudio.Notice );\n\t}\n\n\tstatic void TickTypewriter( float dt )\n\t{\n\t\tif ( LineFullyTyped )\n\t\t\treturn;\n\t\t_typeTimer \u002B= dt;\n\t\tvar step = 1f / TypeCharsPerSecond;\n\t\twhile ( _typeTimer \u003E= step \u0026\u0026 !LineFullyTyped )\n\t\t{\n\t\t\t_typeTimer -= step;\n\t\t\t_visibleChars\u002B\u002B;\n\t\t\t_version\u002B\u002B;\n\t\t}\n\t}\n\n\t// ---- Input ----\n\n\t/// \u003Csummary\u003E\n\t/// One click = real progress. Skip typewriter and advance a line,\n\t/// or finish the scene (claim gift) on the last line \u2014 no extra gift-modal hop.\n\t/// \u003C/summary\u003E\n\tpublic static void Advance()\n\t{\n\t\tif ( _phase == GrandmaGiftPhase.ItemPopup )\n\t\t{\n\t\t\tClaimItem();\n\t\t\treturn;\n\t\t}\n\n\t\tif ( _phase != GrandmaGiftPhase.Dialogue || _active is null )\n\t\t\treturn;\n\n\t\t// Always show full current line first (skip typewriter).\n\t\tif ( !LineFullyTyped )\n\t\t{\n\t\t\t_visibleChars = CurrentLine.Length;\n\t\t\t_typeTimer = 0f;\n\t\t}\n\n\t\t// Same click: leave this line.\n\t\tif ( !IsLastLine )\n\t\t{\n\t\t\t_line\u002B\u002B;\n\t\t\t// Next line appears fully \u2014 fewer clicks, still readable.\n\t\t\t_visibleChars = CurrentLine.Length;\n\t\t\t_typeTimer = 0f;\n\t\t\t_version\u002B\u002B;\n\t\t\t// No click SFX per line \u2014 it spams on every dialogue tap.\n\t\t\treturn;\n\t\t}\n\n\t\t// Last line \u2192 claim immediately (skip ITEM GET modal).\n\t\t// Tank fish still spawn before HasTank is needed next frame.\n\t\tif ( _active.Prop == GrandmaGiftProp.Tank )\n\t\t\tTankSim.GrantTrailerLotTank();\n\t\tClaimItem();\n\t}\n\n\tstatic void ClaimItem()\n\t{\n\t\tvar gift = _active;\n\t\tif ( gift is null )\n\t\t{\n\t\t\tClearActive();\n\t\t\treturn;\n\t\t}\n\n\t\t_received.Add( gift.Id );\n\t\t// Stamp claim time so MinGapAfterPrior can breathe before the next intro.\n\t\t_lastGiftClaimPlaytime = MathF.Max( _lastGiftClaimPlaytime, Economy.PlaytimeSeconds );\n\n\t\t// Sitting through dialogue pays \u2014 base tip \u002B bonus for long scenes.\n\t\tvar doge = DialogueDogePayout( gift );\n\t\tvar karma = gift.KarmaReward;\n\t\tif ( doge \u003E 0 )\n\t\t\tEconomy.Add( doge );\n\t\tif ( karma != 0f )\n\t\t\tKarma.Add( karma );\n\n\t\ttry\n\t\t{\n\t\t\tgift.OnClaimed?.Invoke();\n\t\t}\n\t\tcatch ( Exception e )\n\t\t{\n\t\t\tLog.Warning( $\u0022[NO-CHILLquarium] Grandma gift claim failed ({gift.Id}): {e.Message}\u0022 );\n\t\t}\n\n\t\t// Banner after OnClaimed so tank/wallet grants don\u0027t bury the tip.\n\t\tif ( doge \u003E 0 )\n\t\t{\n\t\t\tvar tip = DialogueTipBanner( gift, doge );\n\t\t\tif ( !string.IsNullOrEmpty( tip ) )\n\t\t\t\tTankSim.ShowBanner( tip );\n\t\t\tif ( doge \u003E= 10 )\n\t\t\t\tGameAudio.PlayDogeBark();\n\t\t\telse\n\t\t\t\tGameAudio.PlayCoin();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tGameAudio.PlayUi( GameAudio.Confirm );\n\t\t}\n\n\t\tClearActive();\n\t\tSaveGame.TrySave( quiet: true );\n\t\t// Chain the next ready gift immediately (tank after the walk home).\n\t\tTryAutoTrigger();\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// \u00D0 for finishing a gift scene. Longer line counts pay more so long monologues feel worth it.\n\t/// \u003C/summary\u003E\n\tpublic static double DialogueDogePayout( GrandmaGiftDef gift )\n\t{\n\t\tif ( gift is null )\n\t\t\treturn 0;\n\t\tvar lines = gift.Lines?.Length ?? 0;\n\t\t// Every line past 4 is a small tip (long lectures pay better).\n\t\tvar longBonus = Math.Max( 0, lines - 4 ) * 1.5;\n\t\tvar basePay = gift.DogeReward;\n\t\tif ( basePay \u003C= 0 \u0026\u0026 lines \u003E 0 )\n\t\t\tbasePay = 2 \u002B lines; // fallback for future gifts\n\t\treturn Math.Max( 0, Math.Round( basePay \u002B longBonus, 1 ) );\n\t}\n\n\tstatic string DialogueTipBanner( GrandmaGiftDef gift, double doge )\n\t{\n\t\tvar pay = Economy.FormatDoge( doge );\n\t\treturn gift.Prop switch\n\t\t{\n\t\t\tGrandmaGiftProp.Establishing =\u003E $\u0022Chin up. \u002B\u00D0{pay} in pocket change.\u0022,\n\t\t\tGrandmaGiftProp.Tank =\u003E $\u0022Tank \u002B goldfish. Lecture tip \u002B\u00D0{pay}.\u0022,\n\t\t\tGrandmaGiftProp.Wallet =\u003E $\u0022Grandpa\u0027s tin. The box ticked. \u002B\u00D0{pay}.\u0022,\n\t\t\tGrandmaGiftProp.Fusion =\u003E $\u0022Window tip. Combine unlocked. \u002B\u00D0{pay}.\u0022,\n\t\t\tGrandmaGiftProp.Fight =\u003E $\u0022Brad opened the garage. Seed cash \u002B\u00D0{pay}.\u0022,\n\t\t\tGrandmaGiftProp.Adventure =\u003E $\u0022Adventure unlocked. Monologue tip \u002B\u00D0{pay}. Flush when bored.\u0022,\n\t\t\t_ =\u003E $\u0022\u002B\u00D0{pay} for listening.\u0022\n\t\t};\n\t}\n}\n"},{"Ident":"dogecoin.nochillquarium","Path":"ui/controlsheet/controlsheetgroup.cs.scss","FileName":"controlsheetgroup.cs.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":".controlgroup\r\n{\r\n\tborder-radius: 8px;\r\n\tpadding: 0.5rem;\r\n\tflex-direction: column;\r\n\tflex-shrink: 0;\r\n\r\n\t\u0026.hidden\r\n\t{\r\n\t\tdisplay: none;\r\n\t}\r\n}\r\n\r\n.controlgroup \u003E .header\r\n{\r\n\tfont-weight: 550;\r\n\tcolor: #fff;\r\n\tflex-shrink: 0;\r\n\tpadding: 8px 0;\r\n\ttext-shadow: 1px 1px 1px #0004;\r\n}\r\n\r\n.controlgroup \u003E .body\r\n{\r\n\tflex-direction: column;\r\n\tflex-shrink: 0;\r\n\tgap: 2px;\r\n\tpadding-left: 16px;\r\n\r\n\t\u0026.hidden\r\n\t{\r\n\t\tdisplay: none;\r\n\t}\r\n}"},{"Ident":"dogecoin.nochillquarium","Path":"ui/controls/slidercontrol.razor.scss","FileName":"slidercontrol.razor.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"\r\n.slidercontrol\r\n{\r\n    flex-direction: row;\r\n    min-width: 50px;\r\n    position: relative;\r\n    flex-shrink: 0;\r\n    flex-direction: row;\r\n    cursor: pointer;\r\n    gap: 8px;\r\n    flex-grow: 1;\r\n    align-items: center;\r\n    pointer-events: all;\r\n\r\n    \u003E .inner\r\n    {\r\n        flex-direction: column;\r\n        flex-shrink: 1;\r\n        flex-grow: 1;\r\n        min-height: 32px;\r\n        justify-content: center;\r\n\r\n        \u003E .values\r\n        {\r\n            width: 100%;\r\n            pointer-events: none;\r\n            font-size: 14px;\r\n            color: #aaa;\r\n\r\n            \u003E .left\r\n            {\r\n                flex-grow: 1;\r\n            }\r\n        }\r\n\r\n        \u003E .track\r\n        {\r\n            position: relative;\r\n            background-color: #888;\r\n            height: 7px;\r\n            margin: 8px;\r\n            align-items: center;\r\n            border-radius: 4px;\r\n\r\n            \u003E .track-active\r\n            {\r\n                background-color: #fff;\r\n                position: absolute;\r\n                height: 100%;\r\n                left: 0px;\r\n                border-radius: 4px;\r\n            }\r\n\r\n            \u003E .thumb\r\n            {\r\n                position: relative;\r\n                background-color: #fff;\r\n                border-radius: 100px;\r\n                width: 16px;\r\n                height: 16px;\r\n                transform: translateX( -50% );\r\n            }\r\n        }\r\n    }\r\n\r\n    \u003E .entry\r\n    {\r\n        flex-shrink: 0;\r\n        flex-grow: 0;\r\n        width: 50px;\r\n\r\n        \u003E numberentry\r\n        {\r\n            background-color: transparent;\r\n\r\n            \u003E .content-label\r\n            {\r\n                padding: 0 4px;\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n\r\n.slidercontrol .value-tooltip\r\n{\r\n    position: absolute;\r\n    bottom: 150%;\r\n    left: -8px;\r\n    z-index: 1000;\r\n    flex-direction: column;\r\n\r\n    \u003E .label\r\n    {       \r\n        background-color: black;\r\n        padding: 8px 12px;\r\n        border-radius: 8px;\r\n    }\r\n\r\n    \u003E.tail\r\n    {\r\n        bottom: -0px;\r\n        background-color: black;\r\n        width: 10px;\r\n        height: 10px;\r\n        transform: rotateZ(45 deg) translateX( 4px );\r\n        position: absolute;\r\n    }\r\n}"},{"Ident":"dogecoin.nochillquarium","Path":"mainshell.razor.scss","FileName":"mainshell.razor.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"MainShell\n{\n\t// Kill default grey Panel chrome \u2014 paint the whole surface.\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\tbottom: 0;\n\tjustify-content: center;\n\talign-items: center;\n\toverflow: hidden;\n\t// Project font is Assets/fonts/pressstart2p.ttf (Poppins is not packaged).\n\tfont-family: pressstart2p, \u0022Press Start 2P\u0022, Consolas, \u0022Courier New\u0022, monospace;\n\tcolor: #e8e8e8;\n\tpointer-events: all;\n\tbackground-color: #08060c;\n\n\t.shell\n\t{\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tbackground-color: #08060c;\n\t\toverflow: hidden;\n\t}\n\n\t.screen\n\t{\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tflex-direction: column;\n\t\toverflow: hidden;\n\t}\n\n\t// ----- WARNING (soft, not metal) -----\n\t.warning\n\t{\n\t\tbackground-color: #0a1a24;\n\t}\n\n\t.warn-frame\n\t{\n\t\tposition: relative;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tflex-shrink: 0;\n\t\tpadding: 32px 40px;\n\t\twidth: 580px;\n\t\tbackground-color: #0e1c28ee;\n\t\tborder: 3px solid #3a7a98;\n\t\tborder-radius: 0;\n\t}\n\n\t.warn-frame.pulse\n\t{\n\t\tanimation-name: warnpulse;\n\t\tanimation-duration: 1.4s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-timing-function: ease-in-out;\n\t}\n\n\t.warn-banner\n\t{\n\t\tfont-size: 22px;\n\t\tfont-weight: 700;\n\t\tcolor: #e8c040;\n\t\tletter-spacing: 1px;\n\t\tmargin-bottom: 18px;\n\t\ttext-align: center;\n\t\twhite-space: nowrap;\n\t}\n\n\t.warn-body\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tmargin-bottom: 16px;\n\t\twidth: 100%;\n\t}\n\n\t.warn-line\n\t{\n\t\tfont-size: 15px;\n\t\tcolor: #c0d8e8;\n\t\ttext-align: center;\n\t\tmargin-bottom: 6px;\n\t\twidth: 100%;\n\t}\n\n\t.warn-line.strong\n\t{\n\t\tfont-size: 15px;\n\t\tfont-weight: 700;\n\t\tcolor: #f0a060;\n\t\tmargin-top: 6px;\n\t\tmargin-bottom: 10px;\n\t}\n\n\t.warn-line.dim\n\t{\n\t\tfont-size: 13px;\n\t\tcolor: #7a9aac;\n\t\tmargin-top: 10px;\n\t}\n\n\t.warn-parody\n\t{\n\t\tfont-size: 12px;\n\t\tcolor: #6a8a9a;\n\t\tmargin-bottom: 22px;\n\t\ttext-align: center;\n\t}\n\n\t// ----- ACCESSIBILITY: Reduce Strobe -----\n\t// Softens frantic CSS pulses; DeathFeel/BoomFeel also scale shake/flash in code.\n\t.reduce-strobe .fish.meth,\n\t.reduce-strobe .meth-aura,\n\t.reduce-strobe .meth-eyes,\n\t.reduce-strobe .meth-sweat,\n\t.reduce-strobe .wd-shiver,\n\t.reduce-strobe .jacked-veins,\n\t.reduce-strobe .fish.stoned,\n\t.reduce-strobe .stoned-aura,\n\t.reduce-strobe .stoned-eyes,\n\t.reduce-strobe .stoned-spark\n\t{\n\t\tanimation-name: none;\n\t\tanimation-duration: 0s;\n\t}\n\n\t.reduce-strobe .meth-aura\n\t{\n\t\topacity: 0.25;\n\t}\n\n\t.reduce-strobe .stoned-aura\n\t{\n\t\topacity: 0.3;\n\t}\n\n\t.reduce-strobe .boom-shock\n\t{\n\t\tanimation-duration: 0.75s;\n\t\topacity: 0.35;\n\t\tborder-width: 2px;\n\t}\n\n\t.reduce-strobe .boom-white-flash,\n\t.reduce-strobe .death-flash\n\t{\n\t\t// Code already dims; cap CSS so overlays never full-screen blast\n\t\t// (opacity set inline \u2014 keep pointer-events none)\n\t}\n\n\t.reduce-strobe .warn-frame.pulse\n\t{\n\t\tanimation-name: none;\n\t}\n\n\t.reduce-strobe.ending.strobe-end .ending-frame,\n\t.reduce-strobe .ending.strobe-end .ending-frame\n\t{\n\t\tanimation-name: none;\n\t}\n\n\t.reduce-strobe .fish.just-taped .m80-prop\n\t{\n\t\tanimation-duration: 0.2s;\n\t}\n\n\t.reduce-strobe .m80-spark\n\t{\n\t\tanimation-name: none;\n\t\topacity: 0.4;\n\t}\n\n\t.settings-help\n\t{\n\t\tfont-size: 11px;\n\t\tcolor: #7a9aac;\n\t\ttext-align: center;\n\t\tmargin-top: 4px;\n\t\tmargin-bottom: 14px;\n\t\twidth: 100%;\n\t\tpadding: 0 8px;\n\t}\n\n\t// ----- ENDINGS -----\n\t.ending\n\t{\n\t\tbackground-color: #080c14;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t}\n\n\t.ending.end-good\n\t{\n\t\tbackground-color: #0a2030;\n\t}\n\n\t.ending.end-bad\n\t{\n\t\tbackground-color: #100808;\n\t}\n\n\t.ending.end-mixed\n\t{\n\t\tbackground-color: #101018;\n\t}\n\n\t.ending.strobe-end .ending-frame\n\t{\n\t\tanimation-name: warnpulse;\n\t\tanimation-duration: 0.55s;\n\t\tanimation-iteration-count: infinite;\n\t}\n\n\t.ending-frame\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\twidth: 640px;\n\t\tpadding: 36px 40px;\n\t\tbackground-color: #102030;\n\t\tborder: 3px solid #4a90b0;\n\t\tborder-radius: 0;\n\t}\n\n\t.ending.end-good .ending-frame\n\t{\n\t\tborder-color: #50a878;\n\t\tbackground-color: #143028;\n\t}\n\n\t.ending.end-bad .ending-frame\n\t{\n\t\tborder-color: #c04040;\n\t\tbackground-color: #281018;\n\t}\n\n\t.ending.end-mixed .ending-frame\n\t{\n\t\tborder-color: #a08840;\n\t\tbackground-color: #282018;\n\t}\n\n\t.ending-kicker\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 2px;\n\t\tcolor: #90b0c0;\n\t\tmargin-bottom: 10px;\n\t\ttext-align: center;\n\t}\n\n\t.ending-title\n\t{\n\t\tfont-size: 28px;\n\t\tfont-weight: 800;\n\t\tcolor: #f0f8ff;\n\t\tmargin-bottom: 8px;\n\t\ttext-align: center;\n\t}\n\n\t.ending-tagline\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 600;\n\t\tcolor: #a8c0d0;\n\t\tmargin-bottom: 18px;\n\t\ttext-align: center;\n\t\tmax-width: 520px;\n\t}\n\n\t.ending.end-good .ending-tagline\n\t{\n\t\tcolor: #90d0a8;\n\t}\n\n\t.ending.end-bad .ending-tagline\n\t{\n\t\tcolor: #e09090;\n\t}\n\n\t.ending.end-mixed .ending-tagline\n\t{\n\t\tcolor: #d0c080;\n\t}\n\n\t.ending-line\n\t{\n\t\tfont-size: 14px;\n\t\tfont-weight: 600;\n\t\tcolor: #e0eef8;\n\t\ttext-align: center;\n\t\tmargin-bottom: 18px;\n\t\tmin-height: 64px;\n\t\twidth: 100%;\n\t\tline-height: 22px;\n\t}\n\n\t.ending-meta\n\t{\n\t\tfont-size: 11px;\n\t\tcolor: #7090a0;\n\t\tmargin-bottom: 10px;\n\t\ttext-align: center;\n\t}\n\n\t.ending-hint\n\t{\n\t\tfont-size: 11px;\n\t\tcolor: #80a0b0;\n\t\tmargin-top: 10px;\n\t\ttext-align: center;\n\t}\n\n\t.ending-menu-btn\n\t{\n\t\tmargin-top: 16px;\n\t\tmin-width: 180px;\n\t\tjustify-content: center;\n\t}\n\n\t// ----- TITLE SCREEN (arcade / solid game menu) -----\n\t.menu\n\t{\n\t\tbackground-color: transparent;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t}\n\n\t.settings\n\t{\n\t\tbackground-color: transparent;\n\t}\n\n\t.menu-bg\n\t{\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #060a10;\n\t\toverflow: hidden;\n\t\tpointer-events: none;\n\t}\n\n\t.menu-wallpaper\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpointer-events: none;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.menu-bg-dim\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #040810;\n\t\topacity: 0.52;\n\t\tpointer-events: none;\n\t}\n\n\t.menu-bg-dim.heavy\n\t{\n\t\topacity: 0.68;\n\t}\n\n\t.menu-center\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\twidth: 100%;\n\t\tz-index: 5;\n\t\tfont-family: Press Start 2P, pressstart2p, Consolas, Courier New, monospace;\n\t\tpointer-events: all;\n\t}\n\n\t// Simple title \u002B buttons \u2014 no badge / tagline / footer clutter\n\t.menu-title\n\t{\n\t\twidth: 92%;\n\t\tmax-width: 1100px;\n\t\tfont-size: 52px;\n\t\tfont-weight: 400;\n\t\tcolor: #ffffff;\n\t\tletter-spacing: 2px;\n\t\tmargin-bottom: 12px;\n\t\ttext-align: center;\n\t\twhite-space: normal;\n\t\tflex-wrap: wrap;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\ttext-shadow:\n\t\t\t-3px -3px 0 #000, 0 -3px 0 #000, 3px -3px 0 #000,\n\t\t\t-3px 0 0 #000, 3px 0 0 #000,\n\t\t\t-3px 3px 0 #000, 0 3px 0 #000, 3px 3px 0 #000;\n\t}\n\n\t.menu-sub\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 400;\n\t\tcolor: #7a98a8;\n\t\tletter-spacing: 1px;\n\t\tmargin-bottom: 28px;\n\t\ttext-align: center;\n\t\ttext-shadow: -1px -1px 0 #000, 1px 1px 0 #000;\n\t}\n\n\t.menu-links\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t}\n\n\t.menu-link\n\t{\n\t\tfont-size: 16px;\n\t\tfont-weight: 400;\n\t\tcolor: #e8f4ff;\n\t\tletter-spacing: 2px;\n\t\tmargin-bottom: 12px;\n\t\tpadding: 12px 28px;\n\t\tcursor: pointer;\n\t\ttext-align: center;\n\t\tbackground-color: #143040;\n\t\tborder: 2px solid #3a6080;\n\t\tborder-radius: 0;\n\t\tborder-bottom: 4px solid #0a2030;\n\t\twidth: 260px;\n\t\tjustify-content: center;\n\t\ttext-shadow: -2px -2px 0 #000, 2px 2px 0 #000;\n\t}\n\n\t.menu-link.primary\n\t{\n\t\tfont-size: 18px;\n\t\tcolor: #102010;\n\t\tbackground-color: #e8c040;\n\t\tborder-color: #ffe080;\n\t\tborder-bottom-color: #806020;\n\t\tmargin-bottom: 18px;\n\t\tletter-spacing: 2px;\n\t\ttext-shadow: none;\n\t\tfont-weight: 400;\n\t}\n\n\t.menu-link.quiet\n\t{\n\t\tfont-size: 12px;\n\t\tcolor: #a0b8c8;\n\t\tbackground-color: #102838;\n\t\tborder-color: #2a5060;\n\t\tborder-bottom-color: #0a1820;\n\t\tmargin-bottom: 10px;\n\t\tpadding: 10px 20px;\n\t\tletter-spacing: 1px;\n\t\twidth: 220px;\n\t}\n\n\t.menu-link:hover\n\t{\n\t\tcolor: #ffffff;\n\t\tborder-color: #6ab0d0;\n\t\tbackground-color: #1a5068;\n\t}\n\n\t.menu-link.primary:hover\n\t{\n\t\tcolor: #102010;\n\t\tbackground-color: #ffe060;\n\t\tborder-color: #fff0a0;\n\t}\n\n\t.menu-link.quiet:hover\n\t{\n\t\tcolor: #e8f0f8;\n\t\tbackground-color: #1a4050;\n\t}\n\n\t.menu-link.danger\n\t{\n\t\tcolor: #e0a0a0;\n\t\tborder-color: #604040;\n\t\tborder-bottom-color: #301818;\n\t}\n\n\t.menu-link.danger:hover\n\t{\n\t\tcolor: #ff8080;\n\t\tbackground-color: #402028;\n\t\tborder-color: #a05050;\n\t}\n\n\t// Kept for Settings \u002B warning panels\n\t.menu-panel\n\t{\n\t\tposition: relative;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tflex-shrink: 0;\n\t\tpadding: 36px 40px;\n\t\twidth: 440px;\n\t\tbackground-color: #0e1c28;\n\t\tborder: 3px solid #2a5068;\n\t\tborder-radius: 0;\n\t\tz-index: 2;\n\t}\n\n\t.menu-panel.simple\n\t{\n\t\tpadding: 24px 28px 20px 28px;\n\t\twidth: 300px;\n\t\tbackground-color: #0e1c28ee;\n\t\tborder: 3px solid #2a5068;\n\t\tborder-radius: 0;\n\t}\n\n\t.kicker\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 2px;\n\t\tcolor: #6a9ab8;\n\t\tmargin-bottom: 12px;\n\t\twhite-space: nowrap;\n\t\ttext-transform: uppercase;\n\t}\n\n\t.title\n\t{\n\t\tfont-size: 36px;\n\t\tfont-weight: 700;\n\t\tcolor: #f0f8fc;\n\t\tletter-spacing: 0.5px;\n\t\ttext-transform: none;\n\t\ttext-align: center;\n\t\twhite-space: nowrap;\n\t}\n\n\t.menu-panel.simple .title\n\t{\n\t\tfont-size: 28px;\n\t\tmargin-bottom: 20px;\n\t}\n\n\t.title.small\n\t{\n\t\tfont-size: 22px;\n\t\tmargin-bottom: 16px;\n\t}\n\n\t.tagline\n\t{\n\t\tmargin-top: 10px;\n\t\tmargin-bottom: 28px;\n\t\tfont-size: 14px;\n\t\tcolor: #8ab0c4;\n\t\ttext-align: center;\n\t\twidth: 100%;\n\t}\n\n\t.menu-buttons\n\t{\n\t\tflex-direction: column;\n\t\talign-items: stretch;\n\t\twidth: 260px;\n\t}\n\n\t.menu-panel.simple .menu-buttons\n\t{\n\t\twidth: 220px;\n\t}\n\n\t.menu-foot\n\t{\n\t\tmargin-top: 22px;\n\t\tfont-size: 11px;\n\t\tcolor: #5a8090;\n\t}\n\n\t// ----- BUTTONS (GBA slab) -----\n\t.btn\n\t{\n\t\tpadding: 14px 20px;\n\t\tmargin-bottom: 10px;\n\t\tbackground-color: #1a4058;\n\t\tborder: 2px solid #4a8aaa;\n\t\tborder-radius: 0;\n\t\tfont-size: 15px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 0.5px;\n\t\tcolor: #e8f4fc;\n\t\ttext-align: center;\n\t\tcursor: pointer;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\twhite-space: nowrap;\n\t\ttext-transform: none;\n\t}\n\n\t.btn:hover\n\t{\n\t\tborder-color: #7ac0d8;\n\t\tbackground-color: #245870;\n\t\tcolor: #ffffff;\n\t}\n\n\t.btn.primary\n\t{\n\t\tbackground-color: #2a7090;\n\t\tborder-color: #6ab8d8;\n\t\tcolor: #ffffff;\n\t}\n\n\t.btn.primary:hover\n\t{\n\t\tbackground-color: #3588a8;\n\t\tborder-color: #90d0e8;\n\t}\n\n\t.btn.danger\n\t{\n\t\tbackground-color: #3a4850;\n\t\tborder-color: #6a7880;\n\t\tcolor: #c8d0d4;\n\t}\n\n\t.btn.danger:hover\n\t{\n\t\tbackground-color: #4a5860;\n\t\tborder-color: #889098;\n\t}\n\n\t.btn.disabled\n\t{\n\t\topacity: 0.4;\n\t\tborder-color: #3a5060;\n\t\tcolor: #708090;\n\t\tpointer-events: none;\n\t\tbackground-color: #152830;\n\t}\n\n\t.btn.slim\n\t{\n\t\tmin-width: 44px;\n\t\tmargin-bottom: 0;\n\t\tpadding: 8px 14px;\n\t\tborder-radius: 0;\n\t\tfont-size: 13px;\n\t}\n\n\t// ----- SETTINGS -----\n\t.settings\n\t{\n\t\tbackground-color: #0a1c28;\n\t}\n\n\t.settings-panel\n\t{\n\t\twidth: 460px;\n\t\talign-items: stretch;\n\t}\n\n\t.setting-row\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\tmargin-bottom: 16px;\n\t\twidth: 100%;\n\t}\n\n\t.setting-label\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 1px;\n\t\tcolor: #8ab0c4;\n\t\twhite-space: nowrap;\n\t}\n\n\t.slider-row\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t}\n\n\t.setting-value\n\t{\n\t\tmin-width: 56px;\n\t\ttext-align: center;\n\t\tfont-size: 14px;\n\t\tfont-weight: 700;\n\t\tcolor: #e0f0f8;\n\t\tmargin-left: 8px;\n\t\tmargin-right: 8px;\n\t}\n\n\t// ----- PLAYING (arcade cabinet frame) -----\n\t.playing\n\t{\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #060e14;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tpadding-top: 12px;\n\t\tpadding-bottom: 12px;\n\t\toverflow: hidden;\n\t\tflex-wrap: nowrap;\n\t}\n\n\t.play-room\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tpointer-events: none;\n\t\tz-index: 0;\n\t}\n\n\t.play-room-spr\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.play-room-dim\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #04101833;\n\t\tpointer-events: none;\n\t}\n\n\t.playing.nochill .play-room-dim\n\t{\n\t\tbackground-color: #18081040;\n\t}\n\n\t.tank-rig\n\t{\n\t\tposition: relative;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\twidth: 1280px;\n\t\tflex-shrink: 0;\n\t\tz-index: 1;\n\t}\n\n\t.tank-shadow\n\t{\n\t\tposition: absolute;\n\t\tleft: 24px;\n\t\tright: 24px;\n\t\tbottom: 118px;\n\t\theight: 16px;\n\t\tbackground-color: #100808;\n\t\topacity: 0.4;\n\t\tpointer-events: none;\n\t\tz-index: 2;\n\t}\n\n\t.tank-stand\n\t{\n\t\twidth: 1280px;\n\t\theight: 148px;\n\t\tflex-shrink: 0;\n\t\tmargin-top: -22px;\n\t\toverflow: hidden;\n\t\tpointer-events: none;\n\t\tz-index: 1;\n\t}\n\n\t.tank-stand-spr\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.playing.chill\n\t{\n\t\tbackground-color: #061018;\n\t}\n\n\t.playing.nochill\n\t{\n\t\tbackground-color: #10080c;\n\t}\n\n\t// Layout only \u2014 no outer cabinet bezel (tank is the frame)\n\t.play-bezel\n\t{\n\t\tposition: relative;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tpadding: 8px 12px;\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t\tborder-radius: 0;\n\t\tflex-shrink: 0;\n\t\tmax-height: 100%;\n\t\tz-index: 1;\n\t}\n\n\t.playing.nochill .play-bezel\n\t{\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t}\n\n\t.play-bezel-inner\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tflex-shrink: 0;\n\t}\n\n\t.playing.salt-view .tank-water\n\t{\n\t\tbackground-color: #0a3d5c;\n\t}\n\n\t.playing.salt-view.nochill .tank-water\n\t{\n\t\tbackground-color: #1a1520;\n\t}\n\n\t// Flat HUD row \u2014 fixed height so nothing below jumps\n\t.cq-topbar\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\twidth: 1280px;\n\t\theight: 72px;\n\t\tmin-height: 72px;\n\t\tmax-height: 72px;\n\t\tmargin-bottom: 4px;\n\t\tpadding: 4px 4px;\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t\tborder-radius: 0;\n\t\tflex-shrink: 0;\n\t\tflex-grow: 0;\n\t\toverflow: visible;\n\t}\n\n\t.playing.nochill .cq-topbar\n\t{\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t}\n\n\t.cq-top-left\n\t{\n\t\t// Empty balance weight for the right census so wallet stays centered\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tflex-shrink: 0;\n\t\tmin-width: 220px;\n\t\twidth: 220px;\n\t}\n\n\t.cq-top-center\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tflex-shrink: 0;\n\t}\n\n\t.cq-top-right\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: flex-end;\n\t\tflex-shrink: 0;\n\t\tmin-width: 220px;\n\t}\n\n\t// Coach \u002B quest chrome \u2014 FIXED height so tips never shove the tank.\n\t.cq-hint-row\n\t{\n\t\twidth: 1280px;\n\t\theight: 58px;\n\t\tmin-height: 58px;\n\t\tmax-height: 58px;\n\t\tflex-shrink: 0;\n\t\tflex-grow: 0;\n\t\tflex-direction: column;\n\t\tjustify-content: flex-start;\n\t\talign-items: center;\n\t\tmargin-bottom: 4px;\n\t\toverflow: hidden;\n\t\tpointer-events: none;\n\t}\n\n\t// Reserved lanes \u2014 empty or filled, same footprint.\n\t.cq-quest-slot\n\t{\n\t\theight: 34px;\n\t\tmin-height: 34px;\n\t\tmax-height: 34px;\n\t\tflex-shrink: 0;\n\t\tflex-grow: 0;\n\t\twidth: 100%;\n\t\tflex-direction: row;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\toverflow: hidden;\n\t\tpointer-events: all;\n\t\tz-index: 6;\n\t}\n\n\t.cq-hint-slot\n\t{\n\t\theight: 28px;\n\t\tmin-height: 28px;\n\t\tmax-height: 28px;\n\t\tflex-shrink: 0;\n\t\tflex-grow: 0;\n\t\twidth: 100%;\n\t\tflex-direction: row;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\toverflow: hidden;\n\t\tpointer-events: none;\n\t\topacity: 0;\n\t}\n\n\t.cq-hint-slot.lit\n\t{\n\t\topacity: 1;\n\t}\n\n\t.cq-quest\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tmax-width: 960px;\n\t\theight: 32px;\n\t\tmin-height: 32px;\n\t\tpadding: 5px 16px;\n\t\tmargin-bottom: 0;\n\t\tbackground-color: #141020;\n\t\tborder: 2px solid #5a3a80;\n\t\tborder-radius: 0;\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t\tbox-shadow: 0 0 12px #00000088;\n\t}\n\n\t.cq-quest:hover\n\t{\n\t\tborder-color: #90c0e8;\n\t\tbackground-color: #182838;\n\t}\n\n\t.cq-quest-next\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 900;\n\t\tletter-spacing: 1px;\n\t\tcolor: #0a1018;\n\t\tbackground-color: #ffd24a;\n\t\tpadding: 2px 6px;\n\t\tborder-radius: 0;\n\t\tmargin-right: 8px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.cq-quest.story .cq-quest-next\n\t{\n\t\tbackground-color: #c890ff;\n\t}\n\n\t.cq-quest.gift .cq-quest-next\n\t{\n\t\tbackground-color: #f0a050;\n\t}\n\n\t.cq-quest.expand .cq-quest-next\n\t{\n\t\tbackground-color: #60d090;\n\t}\n\n\t// Story objective \u2014 classic purple \u201Cdo this beat\u201D\n\t.cq-quest.story\n\t{\n\t\tbackground-color: #1a1028;\n\t\tborder-color: #7a4ab8;\n\t}\n\n\t// Free-play coach \u2014 cool blue so it reads as guidance, not lore\n\t.cq-quest.coach\n\t{\n\t\tbackground-color: #0e1824;\n\t\tborder-color: #3a6a90;\n\t}\n\n\t.cq-quest.coach .cq-quest-kicker\n\t{\n\t\tcolor: #7ec8e8;\n\t}\n\n\t.cq-quest.coach .cq-quest-text\n\t{\n\t\tcolor: #c8e4f4;\n\t}\n\n\t// Pending lot gift \u2014 warm so it feels like a character beat\n\t.cq-quest.gift\n\t{\n\t\tbackground-color: #241818;\n\t\tborder-color: #a07040;\n\t}\n\n\t.cq-quest.gift .cq-quest-kicker\n\t{\n\t\tcolor: #f0c080;\n\t}\n\n\t.cq-quest.gift .cq-quest-text\n\t{\n\t\tcolor: #ffe8c0;\n\t}\n\n\t// Expand / shop growth \u2014 green money nudge\n\t.cq-quest.expand\n\t{\n\t\tbackground-color: #102018;\n\t\tborder-color: #3a8060;\n\t}\n\n\t.cq-quest.expand .cq-quest-kicker\n\t{\n\t\tcolor: #80d0a0;\n\t}\n\n\t.cq-quest.expand .cq-quest-text\n\t{\n\t\tcolor: #c8f0d8;\n\t}\n\n\t.cq-quest-kicker\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 0.8px;\n\t\tcolor: #b898e8;\n\t\tmargin-right: 10px;\n\t\ttext-transform: uppercase;\n\t\tflex-shrink: 0;\n\t}\n\n\t.cq-quest-text\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 700;\n\t\tcolor: #e8d8ff;\n\t\twhite-space: nowrap;\n\t}\n\n\t.cq-hint\n\t{\n\t\tflex-shrink: 0;\n\t\tflex-grow: 0;\n\t\twidth: auto;\n\t\tmax-width: 720px;\n\t\tpadding: 5px 16px;\n\t\tbackground-color: #0c1a24;\n\t\tborder: 1px solid #2a4860;\n\t\tborder-radius: 999px;\n\t\tfont-size: 12px;\n\t\tfont-weight: 600;\n\t\tletter-spacing: 0.2px;\n\t\tcolor: #9ab8c8;\n\t\ttext-align: center;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\theight: 26px;\n\t\tmin-height: 26px;\n\t\tmax-height: 26px;\n\t\twhite-space: nowrap;\n\t\toverflow: hidden;\n\t\tpointer-events: none;\n\t}\n\n\t// Tank-local grab tip \u2014 absolute overlay, zero shell reflow.\n\t.decor-throw-tip\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttop: 10px;\n\t\tmargin-left: -110px;\n\t\twidth: 220px;\n\t\theight: 24px;\n\t\tmin-height: 24px;\n\t\tmax-height: 24px;\n\t\tpadding: 4px 10px;\n\t\tbackground-color: #101820;\n\t\tborder: 1px solid #6a5840;\n\t\tborder-radius: 999px;\n\t\tcolor: #f0d090;\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\ttext-align: center;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tz-index: 80;\n\t\tpointer-events: none;\n\t\topacity: 0.92;\n\t}\n\n\t.tank-glass.decor-holding\n\t{\n\t\t// no size change \u2014 class is for optional styling only\n\t}\n\n\t.cq-hint.focus\n\t{\n\t\tcolor: #ffe8a0;\n\t\tborder-color: #907828;\n\t\tbackground-color: #1a1c10;\n\t}\n\n\t.cq-hint.warn\n\t{\n\t\tcolor: #ffb8a0;\n\t\tborder-color: #903838;\n\t\tbackground-color: #1e1014;\n\t}\n\n\t.cq-hint.nudge\n\t{\n\t\tcolor: #a8ecc8;\n\t\tborder-color: #388858;\n\t\tbackground-color: #0c1e14;\n\t}\n\n\t.playing.nochill .cq-hint\n\t{\n\t\tbackground-color: #161014;\n\t\tborder-color: #483038;\n\t\tcolor: #c0a8b0;\n\t}\n\n\t// Top-bar laptop shortcut \u2014 fixed size so spritebox (100% fill) can\u0027t blow the layout\n\t.dw-top-icon\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tpadding: 4px 8px 5px 8px;\n\t\tmargin-right: 8px;\n\t\tbackground-color: #0e2018;\n\t\tborder: 2px solid #2a6048;\n\t\tborder-radius: 0;\n\t\tcursor: pointer;\n\t\tflex-shrink: 0;\n\t\tflex-grow: 0;\n\t\twidth: 54px;\n\t\theight: 54px;\n\t\toverflow: hidden;\n\t}\n\n\t.dw-top-icon:hover\n\t{\n\t\tborder-color: #50c878;\n\t\tbackground-color: #143028;\n\t}\n\n\t.dw-top-icon.on\n\t{\n\t\tborder-color: #80e0a0;\n\t\tbackground-color: #1a3828;\n\t}\n\n\t.playing.nochill .dw-top-icon\n\t{\n\t\tbackground-color: #1a1014;\n\t\tborder-color: #5a3038;\n\t}\n\n\t.dw-top-icon-box\n\t{\n\t\twidth: 32px;\n\t\theight: 32px;\n\t\tflex-shrink: 0;\n\t\toverflow: hidden;\n\t\tposition: relative;\n\t}\n\n\t.dw-top-sprite\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpointer-events: none;\n\t}\n\n\t.dw-top-fallback\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tbackground-color: #50c878;\n\t\tborder-radius: 4px;\n\t}\n\n\t.dw-top-label\n\t{\n\t\tfont-size: 8px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #70d0a0;\n\t\tmargin-top: 2px;\n\t\tflex-shrink: 0;\n\t\theight: auto;\n\t}\n\n\t// Live fish counts \u2014 GBA chip\n\t.cq-census\n\t{\n\t\tflex-direction: column;\n\t\tjustify-content: center;\n\t\tmin-width: 140px;\n\t\tmax-width: 200px;\n\t\tmax-height: 80px;\n\t\tpadding: 6px 10px;\n\t\tbackground-color: #0e1c28aa;\n\t\tborder: 2px solid #2a4860;\n\t\tborder-radius: 0;\n\t\tflex-shrink: 0;\n\t\toverflow: scroll;\n\t}\n\n\t.cq-census-alert\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 700;\n\t\tcolor: #ffb060;\n\t\tmargin-bottom: 3px;\n\t}\n\n\t.playing.nochill .cq-census\n\t{\n\t\tbackground-color: #181018aa;\n\t\tborder-color: #4a3038;\n\t}\n\n\t.cq-census-head\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.cq-census-icon-box\n\t{\n\t\twidth: 22px;\n\t\theight: 22px;\n\t\tflex-shrink: 0;\n\t\tmargin-right: 6px;\n\t\toverflow: hidden;\n\t\tposition: relative;\n\t}\n\n\t.cq-census-icon\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpointer-events: none;\n\t}\n\n\t.cq-census-title\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 1px;\n\t\tcolor: #6a9ab0;\n\t\ttext-transform: uppercase;\n\t\tflex-grow: 1;\n\t}\n\n\t.playing.nochill .cq-census-title\n\t{\n\t\tcolor: #b07080;\n\t}\n\n\t.cq-census-total\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 800;\n\t\tcolor: #e8f4ff;\n\t}\n\n\t.cq-census-rows\n\t{\n\t\tflex-direction: column;\n\t\twidth: 100%;\n\t}\n\n\t.cq-census-row\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t\tmargin-top: 2px;\n\t}\n\n\t.census-swatch\n\t{\n\t\twidth: 22px;\n\t\theight: 16px;\n\t\tborder-radius: 3px;\n\t\tmargin-right: 6px;\n\t\tflex-shrink: 0;\n\t\tborder: 1px solid #00000055;\n\t\toverflow: hidden;\n\t\tposition: relative;\n\t}\n\n\t.census-swatch.has-sprite\n\t{\n\t\tbackground-color: #0a1820;\n\t\tborder-color: #1a3040;\n\t}\n\n\t.census-swatch .swatch-sprite\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpointer-events: none;\n\t}\n\n\t.cq-census-name\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 600;\n\t\tcolor: #c0d8e8;\n\t\tflex-grow: 1;\n\t}\n\n\t.cq-census-n\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 800;\n\t\tcolor: #ffd24a;\n\t\tmargin-left: 6px;\n\t}\n\n\t.ach-chip\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tmin-width: 54px;\n\t\tmargin-right: 8px;\n\t\tpadding: 6px 8px;\n\t\tbackground-color: #1a1810;\n\t\tborder: 2px solid #6a5830;\n\t\tcursor: pointer;\n\t\tflex-shrink: 0;\n\t}\n\n\t.ach-chip:hover\n\t{\n\t\tborder-color: #d0b050;\n\t}\n\n\t.ach-chip.on\n\t{\n\t\tborder-color: #e8c060;\n\t\tbackground-color: #2a2410;\n\t}\n\n\t.ach-chip-kicker\n\t{\n\t\tfont-size: 8px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #d0b050;\n\t}\n\n\t.ach-chip-n\n\t{\n\t\tmargin-top: 3px;\n\t\tfont-size: 10px;\n\t\tfont-weight: 700;\n\t\tcolor: #f0e0b0;\n\t}\n\n\t.ach-toast\n\t{\n\t\tposition: absolute;\n\t\tleft: 24px;\n\t\tbottom: 28px;\n\t\twidth: 280px;\n\t\tflex-direction: column;\n\t\tpadding: 10px 12px;\n\t\tbackground-color: #14100cee;\n\t\tborder: 3px solid #c8a040;\n\t\tz-index: 80;\n\t\tpointer-events: none;\n\t}\n\n\t.ach-toast-kicker\n\t{\n\t\tfont-size: 8px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1.5px;\n\t\tcolor: #d0b050;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.ach-toast-name\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 800;\n\t\tcolor: #f8e8b0;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.ach-toast-punch\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 600;\n\t\tcolor: #c8b890;\n\t\tline-height: 14px;\n\t}\n\n\t.ach-board\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttop: 48%;\n\t\tmargin-left: -280px;\n\t\tmargin-top: -250px;\n\t\twidth: 560px;\n\t\tmax-height: 500px;\n\t\tflex-direction: column;\n\t\tpadding: 12px 14px;\n\t\tbackground-color: #141018;\n\t\tborder: 3px solid #6a5830;\n\t\tz-index: 170;\n\t\tpointer-events: all;\n\t}\n\n\t.ach-board-head\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tmargin-bottom: 10px;\n\t}\n\n\t.ach-board-title\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 800;\n\t\tcolor: #f0e0b0;\n\t\tflex-grow: 1;\n\t}\n\n\t.ach-board-meta\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tcolor: #d0b050;\n\t\tmargin-right: 10px;\n\t}\n\n\t.ach-board-scroll\n\t{\n\t\tflex-direction: column;\n\t\toverflow: scroll;\n\t\tmax-height: 420px;\n\t}\n\n\t.ach-row\n\t{\n\t\tflex-direction: row;\n\t\talign-items: flex-start;\n\t\tpadding: 7px 8px;\n\t\tmargin-bottom: 4px;\n\t\tbackground-color: #181410;\n\t\tborder: 2px solid #3a3020;\n\t}\n\n\t.ach-row.on\n\t{\n\t\tborder-color: #8a7030;\n\t\tbackground-color: #221c10;\n\t}\n\n\t.ach-row.off\n\t{\n\t\topacity: 0.72;\n\t}\n\n\t.ach-row-mark\n\t{\n\t\twidth: 16px;\n\t\tfont-size: 12px;\n\t\tcolor: #d0b050;\n\t\tmargin-right: 8px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.ach-row.off .ach-row-mark\n\t{\n\t\tcolor: #605040;\n\t}\n\n\t.ach-row-text\n\t{\n\t\tflex-direction: column;\n\t\tflex-grow: 1;\n\t}\n\n\t.ach-row-name\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 800;\n\t\tcolor: #e8d8b0;\n\t\tmargin-bottom: 3px;\n\t}\n\n\t.ach-row.off .ach-row-name\n\t{\n\t\tcolor: #908070;\n\t}\n\n\t.ach-row-hint\n\t{\n\t\tfont-size: 9px;\n\t\tcolor: #b0a080;\n\t\tline-height: 13px;\n\t}\n\n\t.cq-census-empty\n\t{\n\t\tfont-size: 11px;\n\t\tcolor: #6a8898;\n\t\tfont-style: italic;\n\t}\n\n\t.cq-top-chip\n\t{\n\t\tflex-direction: column;\n\t\tjustify-content: center;\n\t\tmin-width: 180px;\n\t\tmax-width: 320px;\n\t\tpadding: 6px 12px;\n\t\tbackground-color: #0e1c28aa;\n\t\tborder: 1px solid #2a4860;\n\t\tborder-radius: 10px;\n\t}\n\n\t.cq-top-chip.right\n\t{\n\t\talign-items: flex-end;\n\t\ttext-align: right;\n\t}\n\n\t.playing.nochill .cq-top-chip\n\t{\n\t\tbackground-color: #181018aa;\n\t\tborder-color: #4a3038;\n\t}\n\n\t.cq-top-main\n\t{\n\t\tfont-size: 15px;\n\t\tfont-weight: 700;\n\t\tcolor: #e8f4ff;\n\t}\n\n\t.cq-top-sub\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 600;\n\t\tcolor: #7a9aac;\n\t\tmargin-top: 2px;\n\t}\n\n\t.cq-top-alert\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 700;\n\t\tcolor: #f0a060;\n\t}\n\n\t.playing.nochill .cq-top-alert\n\t{\n\t\tcolor: #f08080;\n\t}\n\n\t// Kept for shop / other panels that still use pill chrome\n\t.cq-pill\n\t{\n\t\tflex-direction: column;\n\t\tpadding: 10px 16px;\n\t\tmin-width: 200px;\n\t\tbackground-color: #122836;\n\t\tborder: 2px solid #2a5068;\n\t\tborder-radius: 10px;\n\t}\n\n\t.cq-pill-label\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 2px;\n\t\tcolor: #6a9ab8;\n\t\ttext-transform: uppercase;\n\t}\n\n\t.cq-pill-title\n\t{\n\t\tfont-size: 16px;\n\t\tfont-weight: 700;\n\t\tcolor: #e8f4ff;\n\t\tmargin-top: 2px;\n\t}\n\n\t.cq-pill-sub\n\t{\n\t\tfont-size: 11px;\n\t\tcolor: #7a9aac;\n\t\tmargin-top: 3px;\n\t}\n\n\t// ----- Dogecoin cold wallet (GBA-style hardware; no real brands) -----\n\t.cold-chip\n\t{\n\t\tposition: relative;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\toverflow: visible;\n\t\tflex-shrink: 0;\n\t\tcursor: pointer;\n\t}\n\n\t.cold-chip.help-open .cold-chip-shell\n\t{\n\t\tborder-color: #c8a040;\n\t}\n\n\t.cold-chip-hint\n\t{\n\t\tfont-size: 8px;\n\t\tfont-weight: 600;\n\t\tletter-spacing: 0.3px;\n\t\tcolor: #6a8898;\n\t\tmargin-top: 3px;\n\t\ttext-align: center;\n\t\twidth: 100%;\n\t\tjustify-content: center;\n\t\t// Two short lines: from Grandma \u00B7 tap for tips\n\t\theight: 20px;\n\t\tflex-shrink: 0;\n\t\twhite-space: normal;\n\t}\n\n\t.cold-chip:hover .cold-chip-hint\n\t{\n\t\tcolor: #a0c0d0;\n\t}\n\n\t.cold-chip-shell\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\t// Fixed shell \u2014 never grows with digit count\n\t\tbackground-color: #222830;\n\t\tborder: 1px solid #1a1e24;\n\t\tborder-radius: 10px;\n\t\tpadding: 0;\n\t\toverflow: hidden;\n\t\twidth: 210px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.cold-chip-body\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: flex-start;\n\t\tpadding: 6px 10px 6px 10px;\n\t\twidth: 210px;\n\t\tflex-shrink: 0;\n\t\tbackground-color: #2c343e;\n\t}\n\n\t.cold-chip-brand\n\t{\n\t\tfont-size: 7px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 0.6px;\n\t\tcolor: #8a929c;\n\t\tmargin-bottom: 4px;\n\t\ttext-align: center;\n\t\twidth: 100%;\n\t\tjustify-content: center;\n\t\theight: 12px;\n\t\tflex-shrink: 0;\n\t\twhite-space: nowrap;\n\t}\n\n\t.cold-chip-lcd\n\t{\n\t\t// Outer LCD bezel \u2014 fixed\n\t\twidth: 188px;\n\t\tpadding: 3px;\n\t\tbackground-color: #1a2010;\n\t\tborder: 2px solid #0c1008;\n\t\tborder-radius: 4px;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tflex-shrink: 0;\n\t}\n\n\t.cold-chip-lcd-glass\n\t{\n\t\t// Fixed viewport grid \u2014 both lines share the same center axis\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tpadding: 4px 6px;\n\t\tbackground-color: #0a280a;\n\t\tborder: 1px solid #306230;\n\t\tborder-radius: 2px;\n\t\twidth: 176px;\n\t\theight: 52px;\n\t\toverflow: hidden;\n\t\tflex-shrink: 0;\n\t}\n\n\t.cold-chip-lcd.lit .cold-chip-lcd-glass\n\t{\n\t\tbackground-color: #0e3010;\n\t\tborder-color: #50a040;\n\t}\n\n\t.cold-chip-lcd-row\n\t{\n\t\t// Full-width rows so both lines center on the same axis\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tflex-wrap: nowrap;\n\t\twidth: 100%;\n\t\tflex-shrink: 0;\n\t\toverflow: hidden;\n\t}\n\n\t.cold-chip-lcd-row.bal\n\t{\n\t\theight: 30px;\n\t\tmin-height: 30px;\n\t\tmax-height: 30px;\n\t}\n\n\t// Income /s \u2014 tiny, still centered under balance\n\t.cold-chip-lcd-row.rate\n\t{\n\t\theight: 14px;\n\t\tmin-height: 14px;\n\t\tmax-height: 14px;\n\t\tmargin-top: 0;\n\t\topacity: 0.72;\n\t}\n\n\t.wallet-lcd-line\n\t{\n\t\tflex-shrink: 0;\n\t\timage-rendering: pixelated;\n\t\tpointer-events: none;\n\t\t// Stay centered in the row regardless of texture width\n\t\talign-self: center;\n\t}\n\n\t.wallet-lcd-line.sm\n\t{\n\t\topacity: 0.9;\n\t}\n\n\t.cold-chip-controls\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tmargin-top: 5px;\n\t\twidth: 100%;\n\t\theight: 12px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.cold-chip-btn\n\t{\n\t\twidth: 14px;\n\t\theight: 8px;\n\t\tmargin-left: 4px;\n\t\tmargin-right: 4px;\n\t\tbackground-color: #1a1e24;\n\t\tborder: 1px solid #0a0c10;\n\t\tborder-radius: 2px;\n\t}\n\n\t.cold-chip-led\n\t{\n\t\twidth: 6px;\n\t\theight: 6px;\n\t\tmargin-left: 10px;\n\t\tborder-radius: 50%;\n\t\tbackground-color: #3a2020;\n\t\tborder: 1px solid #1a1010;\n\t}\n\n\t.cold-chip-led.on\n\t{\n\t\tbackground-color: #50e060;\n\t\tborder-color: #206030;\n\t}\n\n\t.cold-chip.pulse .cold-chip-shell\n\t{\n\t\tborder-color: #4a6a40;\n\t}\n\n\t.cold-chip.pulse .cold-chip-brand\n\t{\n\t\tcolor: #b0c8a0;\n\t}\n\n\t.playing.nochill .cold-chip-shell\n\t{\n\t\tbackground-color: #2a2428;\n\t\tborder-color: #1a1014;\n\t}\n\n\t.playing.nochill .cold-chip-body\n\t{\n\t\tbackground-color: #383038;\n\t}\n\n\t.playing.nochill .cold-chip-rail\n\t{\n\t\tbackground-color: #403848;\n\t}\n\n\t// Training / meth \u002Bstat pops \u2014 text only, one color per lane\n\t.stat-float\n\t{\n\t\tposition: absolute;\n\t\twidth: auto;\n\t\theight: auto;\n\t\tmin-width: 0;\n\t\tmargin-left: 0;\n\t\tfont-size: 14px;\n\t\tfont-weight: 800;\n\t\tpadding: 0;\n\t\tborder: 0;\n\t\tborder-radius: 0;\n\t\tbackground-color: transparent;\n\t\tpointer-events: none;\n\t\tz-index: 45;\n\t\twhite-space: nowrap;\n\t\ttext-align: center;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\toverflow: visible;\n\t\t// Soft outline so text stays readable over water\n\t\ttext-shadow:\n\t\t\t-1px -1px 0 #000000,\n\t\t\t1px -1px 0 #000000,\n\t\t\t-1px 1px 0 #000000,\n\t\t\t1px 1px 0 #000000,\n\t\t\t0 0 6px #000000aa;\n\t}\n\n\t// Fallback multi-stat line (weed / legacy)\n\t.stat-float.combo\n\t{\n\t\twidth: auto;\n\t\tfont-size: 13px;\n\t\tletter-spacing: 0.4px;\n\t\tcolor: #fff0a0;\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t}\n\n\t// END \u2014 gold (left lane)\n\t.stat-float.end\n\t{\n\t\tcolor: #ffe08a;\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t}\n\n\t// SPD \u2014 cyan (center, slightly higher)\n\t.stat-float.spd\n\t{\n\t\tcolor: #a8e8ff;\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t}\n\n\t// AGI \u2014 green (right lane)\n\t.stat-float.agi\n\t{\n\t\tcolor: #c8ff90;\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t}\n\n\t.stat-float.weed\n\t{\n\t\tcolor: #c8ff90;\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t\twidth: auto;\n\t\tmargin-left: -70px;\n\t}\n\n\t.stat-float.weed-down\n\t{\n\t\tcolor: #ffc090;\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t\twidth: auto;\n\t\tmargin-left: -50px;\n\t}\n\n\t.stat-float.grow\n\t{\n\t\tcolor: #a8ffc8;\n\t\tfont-size: 13px;\n\t}\n\n\t.stat-float.stage\n\t{\n\t\tcolor: #ffd24a;\n\t\tfont-size: 16px;\n\t\tletter-spacing: 0.6px;\n\t}\n\n\t.stat-float.sell\n\t{\n\t\tcolor: #ffe080;\n\t\tfont-size: 15px;\n\t}\n\n\t.stat-float.rare\n\t{\n\t\tcolor: #f090e0;\n\t\tfont-size: 15px;\n\t\tletter-spacing: 0.5px;\n\t}\n\n\t.stat-float.fed\n\t{\n\t\tcolor: #e8c060;\n\t\tfont-size: 12px;\n\t}\n\n\t.playing.nochill .stat-float.grow { color: #90e8b0; }\n\t.playing.nochill .stat-float.stage { color: #ffb040; }\n\t.playing.nochill .stat-float.sell { color: #ffd070; }\n\t.playing.nochill .stat-float.rare { color: #ff70c0; }\n\n\t.playing.nochill .stat-float.combo\n\t{\n\t\tcolor: #ffe8b0;\n\t}\n\n\t.playing.nochill .stat-float.end\n\t{\n\t\tcolor: #ffd080;\n\t}\n\n\t.playing.nochill .stat-float.spd\n\t{\n\t\tcolor: #f0c0c8;\n\t}\n\n\t.playing.nochill .stat-float.agi\n\t{\n\t\tcolor: #e0f0a0;\n\t}\n\n\t.playing.nochill .stat-float.weed\n\t{\n\t\tcolor: #d0f0a0;\n\t}\n\n\t.playing.nochill .stat-float.weed-down\n\t{\n\t\tcolor: #f0b090;\n\t}\n\n\t// Gym panel mirror of last rep gains\n\t.train-gain-pops\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 800;\n\t\tcolor: #ffd24a;\n\t\ttext-align: center;\n\t\tmargin-bottom: 10px;\n\t\tpadding: 6px 4px;\n\t\tbackground-color: #1a2810;\n\t\tborder: 2px solid #80a040;\n\t\tborder-radius: 6px;\n\t\tflex-shrink: 0;\n\t\theight: auto;\n\t\twhite-space: normal;\n\t}\n\n\t.playing.nochill .train-gain-pops\n\t{\n\t\tcolor: #ffd0a0;\n\t\tbackground-color: #201018;\n\t\tborder-color: #a06040;\n\t}\n\n\t// Dogecoin \u00B1\u00D0 pops \u2014 rise from wallet, no layout shift\n\t.doge-float-layer\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 100%;\n\t\tmargin-top: 4px;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tpointer-events: none;\n\t\tz-index: 45;\n\t\toverflow: visible;\n\t}\n\n\t.doge-float\n\t{\n\t\tfont-size: 15px;\n\t\tfont-weight: 800;\n\t\tpadding: 3px 10px;\n\t\tmargin-bottom: 2px;\n\t\tborder-radius: 8px;\n\t\tpointer-events: none;\n\t\tflex-shrink: 0;\n\t}\n\n\t.doge-float.earn\n\t{\n\t\tcolor: #ffe08a;\n\t\tbackground-color: #1a3020;\n\t\tborder: 1px solid #50a060;\n\t}\n\n\t.doge-float.spend\n\t{\n\t\tcolor: #f0b0b0;\n\t\tfont-size: 13px;\n\t\tfont-weight: 700;\n\t\tbackground-color: #301818;\n\t\tborder: 1px solid #804040;\n\t}\n\n\t.doge-float.funny\n\t{\n\t\tfont-size: 16px;\n\t\tcolor: #fff0a0;\n\t\tbackground-color: #2a2410;\n\t\tborder: 2px solid #e8c040;\n\t}\n\n\t.playing.nochill .doge-float.earn\n\t{\n\t\tbackground-color: #1a2818;\n\t\tborder-color: #60a050;\n\t}\n\n\t.playing.nochill .doge-float.spend\n\t{\n\t\tbackground-color: #281014;\n\t}\n\n\t// Legacy money classes kept so old themes don\u0027t hard-fail\n\t.cq-money\n\t{\n\t\tposition: relative;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tpadding: 6px 24px;\n\t\tbackground-color: #102a38;\n\t\tborder: 2px solid #3a7088;\n\t\tborder-radius: 12px;\n\t\tmin-width: 180px;\n\t}\n\n\t.cq-money-value\n\t{\n\t\tfont-size: 26px;\n\t\tfont-weight: 800;\n\t\tcolor: #ffd24a;\n\t}\n\n\t.cq-money-rate\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 600;\n\t\tcolor: #7cbc6a;\n\t\tmargin-top: 1px;\n\t}\n\n\t// Thin mood line \u2014 fixed height\n\t.cq-mood-bar\n\t{\n\t\tposition: relative;\n\t\twidth: 1280px;\n\t\theight: 4px;\n\t\tmin-height: 4px;\n\t\tmax-height: 4px;\n\t\tmargin-bottom: 8px;\n\t\tbackground-color: #0a1820;\n\t\tborder: 0;\n\t\tborder-radius: 2px;\n\t\toverflow: hidden;\n\t\tflex-shrink: 0;\n\t\tflex-grow: 0;\n\t\topacity: 0.4;\n\t}\n\n\t.cq-mood-bar.lit\n\t{\n\t\topacity: 0.9;\n\t}\n\n\t.cq-mood-bar.dim .cq-mood-fill\n\t{\n\t\topacity: 0.25;\n\t}\n\n\t.cq-mood-fill\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #5a9ab8;\n\t}\n\n\t.playing.nochill .cq-mood-fill\n\t{\n\t\tbackground-color: #c43c3c;\n\t}\n\n\t.cq-mood-mark\n\t{\n\t\tposition: absolute;\n\t\tleft: 40%;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\twidth: 2px;\n\t\tbackground-color: #ffd24a;\n\t\topacity: 0.55;\n\t}\n\n\t// Floating game notice \u2014 true overlay; never participates in flex layout\n\t.cq-notice\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\t// Sit just under the top bar / switcher, over the tank rim\n\t\ttop: 118px;\n\t\tmargin-left: -280px;\n\t\twidth: 560px;\n\t\t// Fixed chrome so long banners don\u0027t reflow siblings\n\t\tmin-height: 34px;\n\t\tmax-height: 52px;\n\t\tpadding: 7px 14px;\n\t\tbackground-color: #0e2838;\n\t\tborder: 1px solid #3a7088;\n\t\tborder-radius: 8px;\n\t\tcolor: #c8e0ec;\n\t\tfont-size: 12px;\n\t\tfont-weight: 600;\n\t\ttext-align: center;\n\t\tz-index: 90;\n\t\tpointer-events: none;\n\t\topacity: 0.92;\n\t\toverflow: hidden;\n\t\t// Don\u0027t grow the play column when toast mounts\n\t\tflex-shrink: 0;\n\t}\n\n\t.cq-notice.status\n\t{\n\t\topacity: 0.8;\n\t\tborder-color: #4a6070;\n\t\tcolor: #a8c0cc;\n\t}\n\n\t.cq-notice.urgent\n\t{\n\t\tbackground-color: #281018;\n\t\tborder-color: #a04048;\n\t\tcolor: #f0c0c0;\n\t\topacity: 0.95;\n\t}\n\n\t.playing.nochill .cq-notice\n\t{\n\t\tbackground-color: #1a1014;\n\t\tborder-color: #704048;\n\t}\n\n\t// Legacy class kept for safety\n\t.cq-toast\n\t{\n\t\t// redirected style \u2014 prefer .cq-notice\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttop: 118px;\n\t\tmargin-left: -280px;\n\t\twidth: 560px;\n\t\tpadding: 7px 14px;\n\t\tbackground-color: #0e2838;\n\t\tborder: 1px solid #3a7088;\n\t\tborder-radius: 8px;\n\t\tcolor: #c8e0ec;\n\t\tfont-size: 12px;\n\t\tfont-weight: 600;\n\t\ttext-align: center;\n\t\tz-index: 40;\n\t\tpointer-events: none;\n\t}\n\n\t.cq-toast.urgent\n\t{\n\t\tbackground-color: #281018;\n\t\tborder-color: #a04048;\n\t\tcolor: #f0c0c0;\n\t}\n\n\t.cq-stage-row\n\t{\n\t\tposition: relative;\n\t\tflex-direction: row;\n\t\talign-items: flex-start;\n\t\tjustify-content: center;\n\t\tflex-shrink: 0;\n\t\twidth: 1280px;\n\t}\n\n\t.cq-stage\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tflex-shrink: 0;\n\t\tposition: relative;\n\t}\n\n\t// ----- Fish context menu (inside tank-water, same coords as fish) -----\n\t.fish-menu-layer\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tpointer-events: none;\n\t\tz-index: 50;\n\t}\n\n\t.fish-menu-backdrop\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tpointer-events: all;\n\t\tbackground-color: #00000040;\n\t\tz-index: 50;\n\t}\n\n\t.fish-menu\n\t{\n\t\tposition: absolute;\n\t\twidth: 228px;\n\t\tmin-height: 0;\n\t\theight: auto;\n\t\tpadding: 10px 10px 8px 10px;\n\t\tbackground-color: #143848;\n\t\tborder: 2px solid #5ab8d8;\n\t\tborder-bottom: 4px solid #0a2838;\n\t\tborder-right: 3px solid #0a2838;\n\t\tborder-radius: 0;\n\t\tflex-direction: column;\n\t\talign-items: stretch;\n\t\tflex-shrink: 0;\n\t\tpointer-events: all;\n\t\tz-index: 80;\n\t}\n\n\t.playing.nochill .fish-menu\n\t{\n\t\tbackground-color: #1a1018;\n\t\tborder-color: #8b4a5a;\n\t}\n\n\t.fish-menu-title\n\t{\n\t\tfont-size: 14px;\n\t\tfont-weight: 800;\n\t\tcolor: #e8f4ff;\n\t\ttext-align: center;\n\t\tflex-shrink: 0;\n\t\theight: auto;\n\t}\n\n\t// Click to open type-to-rename\n\t.fish-menu-title.clickable\n\t{\n\t\tcursor: pointer;\n\t\tpadding: 5px 8px;\n\t\tborder-radius: 6px;\n\t\tborder: 1px solid #3a7088;\n\t\tbackground-color: #0e2838;\n\t}\n\n\t.fish-menu-title.clickable:hover\n\t{\n\t\tborder-color: #70c8e8;\n\t\tbackground-color: #1a4860;\n\t\tcolor: #ffffff;\n\t}\n\n\t.fish-menu.renaming\n\t{\n\t\twidth: 248px;\n\t}\n\n\t// Sandbox.UI.TextEntry field\n\t.fish-rename-entry\n\t{\n\t\twidth: 100%;\n\t\tmin-height: 32px;\n\t\theight: 32px;\n\t\tmargin: 4px 0 8px 0;\n\t\tpadding: 4px 8px;\n\t\tbackground-color: #0a2030;\n\t\tborder: 2px solid #50a0c8;\n\t\tborder-radius: 8px;\n\t\tfont-size: 13px;\n\t\tfont-weight: 700;\n\t\tcolor: #e8f8ff;\n\t\tflex-shrink: 0;\n\t\twhite-space: nowrap;\n\t\tcursor: text;\n\t\tpointer-events: all;\n\t}\n\n\t.playing.nochill .fish-rename-entry\n\t{\n\t\tbackground-color: #140c10;\n\t\tborder-color: #a06070;\n\t\tcolor: #f0e0e8;\n\t}\n\n\t.fish-rename-row\n\t{\n\t\tflex-direction: row;\n\t\twidth: 100%;\n\t\tflex-shrink: 0;\n\t\theight: auto;\n\t\tjustify-content: space-between;\n\t}\n\n\t.fish-rename-row .fish-menu-btn.slim\n\t{\n\t\tpadding: 7px 6px;\n\t\tmargin: 0 2px 0 2px;\n\t\tfont-size: 11px;\n\t\tflex-grow: 1;\n\t\tmin-width: 0;\n\t\tborder-bottom-width: 2px;\n\t}\n\n\t.fish-menu-sub\n\t{\n\t\tfont-size: 10px;\n\t\tcolor: #7a9aac;\n\t\ttext-align: center;\n\t\tmargin-bottom: 4px;\n\t\tmargin-top: 2px;\n\t\tflex-shrink: 0;\n\t\theight: auto;\n\t}\n\n\t.fish-menu-stats-line\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 700;\n\t\tcolor: #a0d0e8;\n\t\ttext-align: center;\n\t\tmargin-bottom: 4px;\n\t\tpadding: 4px 0;\n\t\tbackground-color: #0a2030;\n\t\tborder-radius: 4px;\n\t\tflex-shrink: 0;\n\t\theight: auto;\n\t}\n\n\t.playing.nochill .fish-menu-stats-line\n\t{\n\t\tcolor: #d0a0a8;\n\t\tbackground-color: #120a10;\n\t}\n\n\t.fish-menu-stat-bars\n\t{\n\t\tflex-direction: column;\n\t\twidth: 100%;\n\t\tgap: 3px;\n\t\tmargin-bottom: 8px;\n\t\tpadding: 0 2px;\n\t\tflex-shrink: 0;\n\t\theight: auto;\n\t}\n\n\t.fish-stat-bar\n\t{\n\t\tposition: relative;\n\t\twidth: 100%;\n\t\theight: 5px;\n\t\tbackground-color: #0a1820;\n\t\tborder-radius: 2px;\n\t\toverflow: hidden;\n\t\tborder: 1px solid #1a3040;\n\t}\n\n\t.fish-stat-fill\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\tborder-radius: 2px;\n\t}\n\n\t.fish-stat-bar.end .fish-stat-fill\n\t{\n\t\tbackground-color: #50c878;\n\t}\n\n\t.fish-stat-bar.spd .fish-stat-fill\n\t{\n\t\tbackground-color: #50b0ff;\n\t}\n\n\t.fish-stat-bar.agi .fish-stat-fill\n\t{\n\t\tbackground-color: #e0a040;\n\t}\n\n\t.fish-menu-btn\n\t{\n\t\tpadding: 10px 8px;\n\t\tmargin-bottom: 5px;\n\t\tbackground-color: #1c4a62;\n\t\tborder: 2px solid #3a7088;\n\t\tborder-bottom: 4px solid #0e2838;\n\t\tborder-radius: 0;\n\t\tfont-size: 13px;\n\t\tfont-weight: 700;\n\t\tcolor: #e8f4ff;\n\t\ttext-align: center;\n\t\tcursor: pointer;\n\t\tflex-shrink: 0;\n\t\theight: auto;\n\t\tmin-height: 0;\n\t\twhite-space: nowrap;\n\t\toverflow: hidden;\n\t\tjustify-content: center;\n\t}\n\n\t.fish-menu-btn:hover\n\t{\n\t\tborder-color: #6ab0d0;\n\t\tbackground-color: #245870;\n\t\tborder-bottom-color: #143848;\n\t}\n\n\t.fish-menu-btn.primary\n\t{\n\t\tbackground-color: #1a6040;\n\t\tborder-color: #40a070;\n\t\tborder-bottom-color: #0a3020;\n\t\tcolor: #e8ffe8;\n\t\tfont-weight: 800;\n\t}\n\n\t.fish-menu-btn.primary:hover\n\t{\n\t\tbackground-color: #247848;\n\t\tborder-color: #60c890;\n\t\tborder-bottom-color: #104828;\n\t}\n\n\t.fish-menu-btn.danger\n\t{\n\t\tbackground-color: #4a2028;\n\t\tborder-color: #a04048;\n\t\tcolor: #ffd0d0;\n\t}\n\n\t.fish-menu-btn.quiet\n\t{\n\t\tbackground-color: #1a303c;\n\t\tborder-color: #3a5060;\n\t\tcolor: #9ab0c0;\n\t\tfont-weight: 600;\n\t\tmargin-bottom: 0;\n\t}\n\n\t.fish-menu-btn.locked\n\t{\n\t\tbackground-color: #142028;\n\t\tborder-color: #2a3840;\n\t\tborder-bottom-color: #0a1014;\n\t\tcolor: #6a7880;\n\t\tfont-weight: 600;\n\t\topacity: 0.85;\n\t}\n\n\t.fish-menu-btn.locked:hover\n\t{\n\t\tborder-color: #4a5860;\n\t\tbackground-color: #1a2830;\n\t\tcolor: #9aa8b0;\n\t}\n\n\t.fish-menu-btn.warn\n\t{\n\t\tbackground-color: #4a3820;\n\t\tborder-color: #a08040;\n\t\tborder-bottom-color: #2a2010;\n\t\tcolor: #ffe8c0;\n\t}\n\n\t.fish-menu-btn.warn:hover\n\t{\n\t\tbackground-color: #5a4828;\n\t\tborder-color: #c0a050;\n\t}\n\n\t.fish-menu-growth\n\t{\n\t\twidth: 100%;\n\t\theight: 6px;\n\t\tmargin: 4px 0 6px 0;\n\t\tbackground-color: #0a1820;\n\t\tborder: 1px solid #2a4050;\n\t\tborder-radius: 3px;\n\t\toverflow: hidden;\n\t\tflex-shrink: 0;\n\t}\n\n\t.fish-menu-growth-fill\n\t{\n\t\theight: 100%;\n\t\tbackground-color: #40c890;\n\t\tborder-radius: 2px;\n\t}\n\n\t.fish-menu-tip\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 700;\n\t\tcolor: #ffd24a;\n\t\tmargin: 2px 0 6px 0;\n\t\tline-height: 1.25;\n\t\tflex-shrink: 0;\n\t}\n\n\t.battle-result-actions\n\t{\n\t\tflex-direction: row;\n\t\tgap: 8px;\n\t\tmargin-top: 10px;\n\t\twidth: 100%;\n\t\tjustify-content: center;\n\t\tflex-shrink: 0;\n\t}\n\n\t.lot-run-stakes-bar\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tgap: 6px;\n\t\twidth: 100%;\n\t\tmargin-bottom: 8px;\n\t\tpadding: 6px 8px;\n\t\tbackground-color: #0a141c;\n\t\tborder: 1px solid #2a4858;\n\t\tborder-radius: 6px;\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tcolor: #8ab0c0;\n\t\tflex-shrink: 0;\n\t}\n\n\t.lot-run-bag\n\t{\n\t\tcolor: #ffd24a;\n\t}\n\n\t.lot-run-stakes-sep\n\t{\n\t\tcolor: #4a6878;\n\t}\n\n\t.lot-run-room-stakes\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tcolor: #c0e0a0;\n\t\tmargin: 6px 0 10px 0;\n\t\tpadding: 6px 8px;\n\t\tbackground-color: #142018;\n\t\tborder: 1px solid #3a6040;\n\t\tborder-radius: 6px;\n\t\tline-height: 1.3;\n\t\tflex-shrink: 0;\n\t}\n\n\t.fish.menu-on.has-sprite .fish-sprite\n\t{\n\t\t// No box \u2014 menu is the tell.\n\t}\n\n\t.fish.menu-on:not( .has-sprite ) .fish-body\n\t{\n\t\tborder: 2px solid #ffd24a;\n\t}\n\n\t// ----- Right-side inventory (food, m80, juice, meth\u2026) -----\n\t.inv-panel\n\t{\n\t\tflex-direction: column;\n\t\twidth: 120px;\n\t\tmargin-left: 10px;\n\t\tpadding: 6px 4px;\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t\tborder-radius: 0;\n\t\tflex-shrink: 0;\n\t\tflex-grow: 0;\n\t\talign-self: flex-start;\n\t\tmax-height: 640px;\n\t\toverflow: scroll;\n\t}\n\n\t.playing.nochill .inv-panel\n\t{\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t}\n\n\t.inv-title\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1.5px;\n\t\tcolor: #6a9ab0;\n\t\ttext-align: center;\n\t\tmargin-bottom: 6px;\n\t\ttext-transform: uppercase;\n\t\topacity: 0.85;\n\t}\n\n\t.playing.nochill .inv-title\n\t{\n\t\tcolor: #b07080;\n\t}\n\n\t.inv-slots\n\t{\n\t\tflex-direction: column;\n\t\talign-items: stretch;\n\t\twidth: 100%;\n\t}\n\n\t.inv-slot\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tpadding: 8px 6px 7px 6px;\n\t\tmargin-bottom: 6px;\n\t\tbackground-color: #0e1c28cc;\n\t\tborder: 2px solid #2a4860;\n\t\tborder-radius: 0;\n\t\tcursor: pointer;\n\t\tposition: relative;\n\t}\n\n\t.inv-slot:hover\n\t{\n\t\tborder-color: #50a0c8;\n\t\tbackground-color: #143848ee;\n\t}\n\n\t.inv-slot.on\n\t{\n\t\tborder-color: #d0a040;\n\t\tbackground-color: #2a2410ee;\n\t}\n\n\t.inv-slot.armed\n\t{\n\t\tborder-color: #e04040;\n\t\tbackground-color: #301018ee;\n\t}\n\n\t.inv-slot.hot\n\t{\n\t\tborder-color: #50c070;\n\t}\n\n\t.inv-slot.empty\n\t{\n\t\topacity: 0.45;\n\t}\n\n\t.inv-slot.kick\n\t{\n\t\tborder-color: #705040;\n\t}\n\n\t.inv-slot.crime\n\t{\n\t\tborder-color: #804050;\n\t}\n\n\t.inv-glyph\n\t{\n\t\twidth: 36px;\n\t\theight: 28px;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tcolor: #e8c070;\n\t\tfont-size: 11px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t}\n\n\t.crime-sheet\n\t{\n\t\tposition: absolute;\n\t\tright: 138px;\n\t\ttop: 18px;\n\t\twidth: 280px;\n\t\tflex-direction: column;\n\t\tpadding: 10px 10px 8px 10px;\n\t\tbackground-color: #100810ee;\n\t\tborder: 3px solid #804050;\n\t\tz-index: 45;\n\t}\n\n\t.crime-sheet-title\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1.5px;\n\t\tcolor: #e07070;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.crime-sheet-kicker\n\t{\n\t\tfont-size: 9px;\n\t\tcolor: #b09080;\n\t\tmargin-bottom: 8px;\n\t\tline-height: 13px;\n\t}\n\n\t.crime-row\n\t{\n\t\tflex-direction: column;\n\t\tpadding: 7px 8px;\n\t\tmargin-bottom: 5px;\n\t\tbackground-color: #201018;\n\t\tborder: 2px solid #603040;\n\t\tcursor: pointer;\n\t}\n\n\t.crime-row:hover\n\t{\n\t\tborder-color: #e07070;\n\t\tbackground-color: #301820;\n\t}\n\n\t.crime-row.quiet\n\t{\n\t\topacity: 0.7;\n\t\tcursor: default;\n\t}\n\n\t.crime-row-name\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 700;\n\t\tcolor: #f0d0c0;\n\t\tmargin-bottom: 3px;\n\t}\n\n\t.crime-row-meta\n\t{\n\t\tfont-size: 8px;\n\t\tcolor: #a08078;\n\t\tline-height: 12px;\n\t}\n\n\t.crime-sheet-close\n\t{\n\t\tmargin-top: 4px;\n\t\talign-self: flex-end;\n\t}\n\n\t.playing.nochill .inv-slot\n\t{\n\t\tbackground-color: #180e14;\n\t\tborder-color: #503038;\n\t}\n\n\t// Laptop under tools \u2014 open shop (fixed box; spritebox fills 100%)\n\t.dw-laptop\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tmargin-top: 6px;\n\t\tpadding: 8px 6px 8px 6px;\n\t\tbackground-color: #0e1c28cc;\n\t\tborder: 1px solid #2a7050;\n\t\tborder-radius: 10px;\n\t\tcursor: pointer;\n\t\tflex-shrink: 0;\n\t\tflex-grow: 0;\n\t\twidth: 112px;\n\t\toverflow: hidden;\n\t}\n\n\t.dw-laptop:hover\n\t{\n\t\tborder-color: #50c878;\n\t\tbackground-color: #142820ee;\n\t}\n\n\t.dw-laptop.on\n\t{\n\t\tborder-color: #90e8b0;\n\t\tbackground-color: #1a3828ee;\n\t}\n\n\t.playing.nochill .dw-laptop\n\t{\n\t\tbackground-color: #181018cc;\n\t\tborder-color: #5a3038;\n\t}\n\n\t.dw-laptop-box\n\t{\n\t\twidth: 48px;\n\t\theight: 48px;\n\t\tflex-shrink: 0;\n\t\toverflow: hidden;\n\t\tposition: relative;\n\t\tmargin-bottom: 6px;\n\t}\n\n\t.dw-laptop-sprite\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpointer-events: none;\n\t}\n\n\t.dw-laptop-fallback\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tbackground-color: #40a060;\n\t\tborder-radius: 6px;\n\t}\n\n\t.dw-laptop-label\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 0.5px;\n\t\tcolor: #90e8b0;\n\t\ttext-align: center;\n\t\ttext-transform: uppercase;\n\t\tflex-shrink: 0;\n\t\theight: auto;\n\t\twhite-space: nowrap;\n\t}\n\n\t.dw-laptop-sub\n\t{\n\t\tfont-size: 8px;\n\t\tfont-weight: 600;\n\t\tcolor: #5a9070;\n\t\tmargin-top: 2px;\n\t\ttext-align: center;\n\t\tflex-shrink: 0;\n\t\theight: auto;\n\t}\n\n\t.inv-icon-wrap\n\t{\n\t\tposition: relative;\n\t\twidth: 48px;\n\t\theight: 48px;\n\t\tmargin-bottom: 4px;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t}\n\n\t.inv-icon\n\t{\n\t\twidth: 48px;\n\t\theight: 48px;\n\t\tpointer-events: none;\n\t}\n\n\t.inv-qty\n\t{\n\t\tposition: absolute;\n\t\tright: -4px;\n\t\tbottom: -2px;\n\t\tmin-width: 16px;\n\t\tpadding: 1px 4px;\n\t\tbackground-color: #102838;\n\t\tborder: 1px solid #4a90b0;\n\t\tborder-radius: 6px;\n\t\tfont-size: 10px;\n\t\tfont-weight: 800;\n\t\tcolor: #ffd24a;\n\t\ttext-align: center;\n\t}\n\n\t.inv-label\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tcolor: #e0f0ff;\n\t\ttext-align: center;\n\t\tflex-wrap: wrap;\n\t\tjustify-content: center;\n\t\tmax-width: 110px;\n\t}\n\n\t.inv-label.food-name\n\t{\n\t\tfont-size: 10px;\n\t\tline-height: 12px;\n\t\tmin-height: 24px;\n\t\talign-items: center;\n\t}\n\n\t.inv-meta\n\t{\n\t\tfont-size: 9px;\n\t\tcolor: #7a9aac;\n\t\tmargin-top: 1px;\n\t\ttext-align: center;\n\t}\n\n\t.inv-actions\n\t{\n\t\tflex-direction: row;\n\t\tjustify-content: center;\n\t\tmargin-top: 5px;\n\t\twidth: 100%;\n\t}\n\n\t.inv-chip\n\t{\n\t\tpadding: 3px 7px;\n\t\tmargin-left: 2px;\n\t\tmargin-right: 2px;\n\t\tbackground-color: #1c4a62;\n\t\tborder: 1px solid #4a9abc;\n\t\tborder-radius: 6px;\n\t\tfont-size: 10px;\n\t\tfont-weight: 700;\n\t\tcolor: #e8f4ff;\n\t\tcursor: pointer;\n\t}\n\n\t.inv-chip.quiet\n\t{\n\t\tbackground-color: #1a303c;\n\t\tborder-color: #3a5868;\n\t\tcolor: #a0b8c8;\n\t}\n\n\t.inv-foot\n\t{\n\t\tmargin-top: 4px;\n\t\tfont-size: 9px;\n\t\tcolor: #5a7888;\n\t\ttext-align: center;\n\t}\n\n\t// Tank type switcher \u2014 centered above the glass (aquarium-game convention)\n\t.tank-switcher\n\t{\n\t\tflex-direction: row;\n\t\talign-items: stretch;\n\t\tjustify-content: center;\n\t\tmargin-top: 4px;\n\t\tmargin-bottom: 8px;\n\t\tpadding: 4px;\n\t\tbackground-color: #0e2434;\n\t\tborder: 2px solid #2a5068;\n\t\tborder-radius: 0;\n\t\tflex-shrink: 0;\n\t}\n\n\t.playing.nochill .tank-switcher\n\t{\n\t\tbackground-color: #181018;\n\t\tborder-color: #503038;\n\t}\n\n\t.tank-switch\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tmin-width: 88px;\n\t\tmin-height: 34px;\n\t\tpadding: 8px 20px;\n\t\tmargin-left: 2px;\n\t\tmargin-right: 2px;\n\t\tbackground-color: #163848;\n\t\tborder: 2px solid transparent;\n\t\tborder-radius: 0;\n\t\tcursor: pointer;\n\t\tfont-size: 13px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 0.6px;\n\t\tcolor: #c0dce8;\n\t\ttext-align: center;\n\t}\n\n\t.tank-switch:hover\n\t{\n\t\tbackground-color: #1e5068;\n\t}\n\n\t.tank-switch.on\n\t{\n\t\tbackground-color: #2a88b0;\n\t\tborder-color: #80d0f0;\n\t\tcolor: #ffffff;\n\t}\n\n\t.playing.nochill .tank-switch.on\n\t{\n\t\tbackground-color: #803040;\n\t\tborder-color: #e07080;\n\t}\n\n\t.tank-switch.locked\n\t{\n\t\topacity: 0.5;\n\t\tbackground-color: #122838;\n\t}\n\n\n\n\n\t// Bottom dock \u2014 situational actions\n\t.cq-dock\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\twidth: 1280px;\n\t\tmargin-top: 8px;\n\t\tpadding: 10px 14px;\n\t\tbackground-color: #0e2030;\n\t\tborder: 2px solid #2a5068;\n\t\tborder-bottom: 4px solid #0a1520;\n\t\tborder-radius: 12px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.playing.nochill .cq-dock\n\t{\n\t\tbackground-color: #1a1218;\n\t\tborder-color: #5a3038;\n\t}\n\n\t.cq-dock-row\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\twidth: 100%;\n\t\tflex-wrap: wrap;\n\t}\n\n\t.cq-dock-spacer\n\t{\n\t\twidth: 16px;\n\t\theight: 8px;\n\t}\n\n\t.cq-btn\n\t{\n\t\tpadding: 11px 18px;\n\t\tmargin-left: 5px;\n\t\tmargin-right: 5px;\n\t\tmargin-bottom: 0;\n\t\tbackground-color: #1c4a62;\n\t\tborder: 2px solid #4a9abc;\n\t\tborder-bottom: 4px solid #0e2838;\n\t\tborder-radius: 0;\n\t\tfont-size: 13px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 0.3px;\n\t\tcolor: #f0f8fc;\n\t\ttext-align: center;\n\t\tcursor: pointer;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\twhite-space: nowrap;\n\t}\n\n\t.cq-btn:hover\n\t{\n\t\tbackground-color: #286a88;\n\t\tborder-color: #7ad0f0;\n\t\tborder-bottom-color: #143848;\n\t}\n\n\t.cq-btn.primary\n\t{\n\t\tbackground-color: #2a80a8;\n\t\tborder-color: #70c8e8;\n\t\tborder-bottom-color: #104060;\n\t\tcolor: #ffffff;\n\t}\n\n\t.cq-btn.warn\n\t{\n\t\tbackground-color: #5a4850;\n\t\tborder-color: #c09090;\n\t\tcolor: #fff0f0;\n\t}\n\n\t.cq-btn.danger\n\t{\n\t\tbackground-color: #a04048;\n\t\tborder-color: #e08080;\n\t\tcolor: #ffffff;\n\t}\n\n\t.cq-btn.quiet\n\t{\n\t\tbackground-color: #1a3848;\n\t\tborder-color: #4a6878;\n\t\tcolor: #b0c8d4;\n\t}\n\n\t.cq-btn.slim\n\t{\n\t\tpadding: 7px 14px;\n\t\tmin-width: 56px;\n\t\tfont-size: 12px;\n\t\tborder-radius: 0;\n\t}\n\n\t.cq-btn.off\n\t{\n\t\t// Still clickable so handlers can show \u0022need more \u00D0\u0022 feedback\n\t\topacity: 0.45;\n\t\tpointer-events: all;\n\t}\n\n\t.cq-btn.on\n\t{\n\t\tborder-color: #ffd24a;\n\t\tbackground-color: #3a6870;\n\t}\n\n\t.cq-tab\n\t{\n\t\tpadding: 9px 16px;\n\t\tmargin-left: 4px;\n\t\tmargin-right: 4px;\n\t\tbackground-color: #1a4054;\n\t\tborder: 2px solid #3a7088;\n\t\tborder-radius: 0;\n\t\tfont-size: 12px;\n\t\tfont-weight: 700;\n\t\tcolor: #b0d0e0;\n\t\tcursor: pointer;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t}\n\n\t.cq-tab.on\n\t{\n\t\tbackground-color: #2a88b0;\n\t\tborder-color: #80d0f0;\n\t\tcolor: #ffffff;\n\t}\n\n\t.cq-tab.off\n\t{\n\t\topacity: 0.4;\n\t\tpointer-events: none;\n\t}\n\n\t.cq-shop-tabs\n\t{\n\t\tflex-direction: row;\n\t\tmargin-bottom: 10px;\n\t\tflex-shrink: 0;\n\t\tflex-wrap: wrap;\n\t}\n\n\t.shop-row.owned .shop-col.name\n\t{\n\t\tcolor: #ffd24a;\n\t}\n\n\t// AI battle panel \u2014 must sit ABOVE .shop-backdrop (z 70) or clicks never reach it\n\t.battle-panel\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttop: 50%;\n\t\tmargin-left: -340px;\n\t\tmargin-top: -280px;\n\t\twidth: 680px;\n\t\tmax-height: 560px;\n\t\tpadding: 14px 16px;\n\t\tbackground-color: #0c1a24;\n\t\tborder: 3px solid #3a80a0;\n\t\tborder-radius: 0;\n\t\tflex-direction: column;\n\t\tz-index: 81;\n\t\tpointer-events: all;\n\t\toverflow: hidden;\n\t}\n\n\t.playing.nochill .battle-panel\n\t{\n\t\tbackground-color: #161018;\n\t\tborder-color: #6a3840;\n\t}\n\n\t.battle-head\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\twidth: 100%;\n\t\tmargin-bottom: 8px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.battle-head-title\n\t{\n\t\tfont-size: 18px;\n\t\tfont-weight: 800;\n\t\tcolor: #e8f4ff;\n\t\tletter-spacing: 0.5px;\n\t}\n\n\t.battle-msg\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 600;\n\t\tcolor: #8ab0c0;\n\t\tmargin-bottom: 8px;\n\t\tpadding: 0;\n\t\tbackground-color: transparent;\n\t\tborder-radius: 0;\n\t\theight: auto;\n\t\tflex-shrink: 0;\n\t}\n\n\t.battle-templates\n\t{\n\t\tflex-direction: column;\n\t\twidth: 100%;\n\t\toverflow: scroll;\n\t\tmax-height: 220px;\n\t\tflex-shrink: 1;\n\t\tpointer-events: all;\n\t\tpadding-right: 4px;\n\t}\n\n\t.battle-row\n\t{\n\t\tflex-direction: column;\n\t\talign-items: stretch;\n\t\twidth: 100%;\n\t\tpadding: 10px 12px;\n\t\tmargin-bottom: 5px;\n\t\tbackground-color: #0a1822;\n\t\tborder: 1px solid #2a5060;\n\t\tborder-radius: 8px;\n\t\tcursor: pointer;\n\t\tflex-shrink: 0;\n\t\theight: auto;\n\t\tmin-height: 40px;\n\t\tpointer-events: all;\n\t}\n\n\t.battle-row-name,\n\t.battle-row-meta\n\t{\n\t\tpointer-events: none;\n\t}\n\n\t.battle-row.on\n\t{\n\t\tborder-color: #50c070;\n\t\tbackground-color: #143828;\n\t}\n\n\t.battle-row.champ\n\t{\n\t\tborder-color: #a08020;\n\t\tbackground-color: #1a1810;\n\t}\n\n\t.battle-row.champ.on\n\t{\n\t\tborder-color: #e0c040;\n\t\tbackground-color: #2a2410;\n\t}\n\n\t.battle-row.locked\n\t{\n\t\topacity: 0.5;\n\t\tpointer-events: all;\n\t}\n\n\t.battle-row:hover\n\t{\n\t\tborder-color: #4a90b0;\n\t\tbackground-color: #123040;\n\t}\n\n\t.battle-row.on:hover\n\t{\n\t\tborder-color: #60d080;\n\t\tbackground-color: #184830;\n\t}\n\n\t.battle-row-name\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 700;\n\t\tcolor: #e8f4ff;\n\t\theight: auto;\n\t\tflex-shrink: 0;\n\t\twhite-space: nowrap;\n\t}\n\n\t.battle-row-meta\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 600;\n\t\tcolor: #7a9aac;\n\t\tmargin-top: 2px;\n\t\theight: auto;\n\t\tflex-shrink: 0;\n\t\twhite-space: nowrap;\n\t\toverflow: hidden;\n\t}\n\n\t.battle-row.locked .battle-row-meta\n\t{\n\t\tcolor: #a08060;\n\t\twhite-space: normal;\n\t}\n\n\t.battle-roster\n\t{\n\t\tflex-direction: column;\n\t\twidth: 100%;\n\t\tmargin-top: 10px;\n\t\tflex-shrink: 0;\n\t\tpadding-top: 10px;\n\t\tborder-top: 1px solid #1a3848;\n\t}\n\n\t.battle-roster-label\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 700;\n\t\tcolor: #90b8c8;\n\t\tmargin-bottom: 6px;\n\t\theight: auto;\n\t}\n\n\t.battle-roster-row\n\t{\n\t\tflex-direction: row;\n\t\tflex-wrap: wrap;\n\t\twidth: 100%;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.battle-start-row\n\t{\n\t\tflex-direction: row;\n\t\tjustify-content: center;\n\t\twidth: 100%;\n\t\tmargin-top: 12px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.battle-start-row .cq-btn.big\n\t{\n\t\tmin-width: 200px;\n\t\tpadding-top: 12px;\n\t\tpadding-bottom: 12px;\n\t\tfont-size: 15px;\n\t\tfont-weight: 800;\n\t\tjustify-content: center;\n\t\tpointer-events: all;\n\t}\n\n\t.battle-start-hint\n\t{\n\t\twidth: 100%;\n\t\tmargin-top: 6px;\n\t\tfont-size: 11px;\n\t\tfont-weight: 600;\n\t\tcolor: #e0a060;\n\t\ttext-align: center;\n\t\tflex-shrink: 0;\n\t\theight: auto;\n\t}\n\n\t.battle-chip\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tpadding: 6px 10px;\n\t\tmargin-right: 6px;\n\t\tmargin-bottom: 6px;\n\t\tbackground-color: #0a2030;\n\t\tborder: 2px solid #3a6070;\n\t\tborder-radius: 8px;\n\t\tfont-size: 11px;\n\t\tcolor: #c8dce8;\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t}\n\n\t.battle-chip.on\n\t{\n\t\tborder-color: #50c070;\n\t\tbackground-color: #1a4030;\n\t\tcolor: #e8ffe8;\n\t}\n\n\t.battle-chip.hurt\n\t{\n\t\tborder-color: #804040;\n\t\tbackground-color: #281818;\n\t\tcolor: #c09090;\n\t\topacity: 0.75;\n\t}\n\n\t.battle-chip span,\n\t.battle-chip .legend-swatch\n\t{\n\t\tpointer-events: none;\n\t}\n\n\t// Visual fight stage (tank-like mini arena)\n\t.battle-stage-wrap\n\t{\n\t\twidth: 100%;\n\t\tmargin-top: 4px;\n\t\tmargin-bottom: 8px;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t}\n\n\t.battle-stage\n\t{\n\t\tposition: relative;\n\t\twidth: 620px;\n\t\theight: 210px;\n\t\t// visible so name/hp under fish aren\u0027t clipped\n\t\toverflow: visible;\n\t\tborder-radius: 12px;\n\t\tborder: 3px solid #3a7088;\n\t\tbackground-color: #0a2840;\n\t\tflex-shrink: 0;\n\t}\n\n\t.playing.nochill .battle-stage\n\t{\n\t\tborder-color: #6a3040;\n\t\tbackground-color: #140a12;\n\t}\n\n\t.battle-stage.live\n\t{\n\t\tborder-color: #50a0c0;\n\t}\n\n\t.battle-water\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #0c3048;\n\t\topacity: 0.95;\n\t\tborder-radius: 10px;\n\t}\n\n\t.playing.nochill .battle-water\n\t{\n\t\tbackground-color: #1a1018;\n\t}\n\n\t.battle-bubbles\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 0;\n\t\tbottom: 40px;\n\t\topacity: 0.25;\n\t\tbackground-color: #40a0c020;\n\t}\n\n\t.battle-gravel\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\theight: 22px;\n\t\tbackground-color: #3a3020;\n\t\tborder-top: 2px solid #5a4830;\n\t}\n\n\t.playing.nochill .battle-gravel\n\t{\n\t\tbackground-color: #2a1818;\n\t\tborder-top-color: #4a2828;\n\t}\n\n\t.battle-lane\n\t{\n\t\tposition: absolute;\n\t\ttop: 6px;\n\t\tfont-size: 10px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #80b0c8;\n\t\topacity: 0.7;\n\t\tpointer-events: none;\n\t}\n\n\t.battle-lane.you-lane\n\t{\n\t\tleft: 10px;\n\t\tcolor: #70d090;\n\t}\n\n\t.battle-lane.ai-lane\n\t{\n\t\tright: 10px;\n\t\tcolor: #e07070;\n\t}\n\n\t.battle-flash\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #fff8e0;\n\t\tpointer-events: none;\n\t\tz-index: 20;\n\t\tborder-radius: 10px;\n\t}\n\n\t.battle-actor\n\t{\n\t\tposition: absolute;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tpointer-events: none;\n\t\tz-index: 5;\n\t}\n\n\t.battle-actor.lunge\n\t{\n\t\tz-index: 12;\n\t}\n\n\t.battle-actor.hit\n\t{\n\t\tz-index: 11;\n\t}\n\n\t.battle-actor.down\n\t{\n\t\topacity: 0.45;\n\t\tz-index: 2;\n\t}\n\n\t.battle-actor.win\n\t{\n\t\tz-index: 8;\n\t}\n\n\t.battle-actor-sprite\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t}\n\n\t.battle-actor-body\n\t{\n\t\twidth: 100%;\n\t\theight: 70%;\n\t\tborder-radius: 50%;\n\t\tborder: 2px solid #ffffff40;\n\t}\n\n\t.battle-actor-fin\n\t{\n\t\tposition: absolute;\n\t\tright: -4px;\n\t\ttop: 30%;\n\t\twidth: 30%;\n\t\theight: 40%;\n\t\tborder-radius: 40%;\n\t\topacity: 0.85;\n\t}\n\n\t.battle-actor.foe .battle-actor-fin\n\t{\n\t\tright: auto;\n\t\tleft: -4px;\n\t}\n\n\t.battle-actor-jacked\n\t{\n\t\tposition: absolute;\n\t\tleft: -4px;\n\t\tright: -4px;\n\t\ttop: -4px;\n\t\tbottom: 14px;\n\t\tborder: 2px solid #e0c040;\n\t\tborder-radius: 8px;\n\t\topacity: 0.55;\n\t\tpointer-events: none;\n\t}\n\n\t.battle-actor-name\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\tbottom: -14px;\n\t\tmargin-left: -40px;\n\t\twidth: 80px;\n\t\tfont-size: 9px;\n\t\tfont-weight: 700;\n\t\tcolor: #e8f4ff;\n\t\ttext-align: center;\n\t\ttext-shadow: 0 1px 2px #000000cc;\n\t\twhite-space: nowrap;\n\t\toverflow: hidden;\n\t}\n\n\t.battle-actor.foe .battle-actor-name\n\t{\n\t\tcolor: #ffd0d0;\n\t}\n\n\t.battle-actor-hp\n\t{\n\t\tposition: absolute;\n\t\tleft: 10%;\n\t\tright: 10%;\n\t\tbottom: -22px;\n\t\theight: 5px;\n\t\tbackground-color: #102028;\n\t\tborder-radius: 3px;\n\t\tborder: 1px solid #2a4858;\n\t\toverflow: hidden;\n\t}\n\n\t.battle-actor-hp-fill\n\t{\n\t\theight: 100%;\n\t\tbackground-color: #40c060;\n\t}\n\n\t.battle-actor-hp-fill.ai\n\t{\n\t\tbackground-color: #e05050;\n\t}\n\n\t.battle-fx\n\t{\n\t\tposition: absolute;\n\t\ttransform: translate( -50%, -100% );\n\t\tfont-size: 13px;\n\t\tfont-weight: 800;\n\t\tpadding: 2px 8px;\n\t\tborder-radius: 6px;\n\t\tpointer-events: none;\n\t\tz-index: 25;\n\t\twhite-space: nowrap;\n\t\ttext-shadow: 0 1px 2px #000000aa;\n\t}\n\n\t.battle-fx.hit\n\t{\n\t\tcolor: #ffe0a0;\n\t\tbackground-color: #301818;\n\t\tborder: 1px solid #a05040;\n\t}\n\n\t.battle-fx.crit\n\t{\n\t\tcolor: #fff060;\n\t\tfont-size: 15px;\n\t\tbackground-color: #402010;\n\t\tborder: 2px solid #e0a020;\n\t}\n\n\t.battle-fx.dodge\n\t{\n\t\tcolor: #a0e8ff;\n\t\tbackground-color: #102838;\n\t\tborder: 1px solid #40a0c8;\n\t}\n\n\t.battle-fx.ko\n\t{\n\t\tcolor: #ff8080;\n\t\tfont-size: 16px;\n\t\tbackground-color: #280808;\n\t\tborder: 2px solid #c04040;\n\t}\n\n\t.battle-log\n\t{\n\t\tflex-direction: column;\n\t\twidth: 100%;\n\t\tmax-height: 88px;\n\t\toverflow: scroll;\n\t\tpadding: 8px 10px;\n\t\tbackground-color: #0a1820;\n\t\tborder-radius: 8px;\n\t\tborder: 1px solid #2a4050;\n\t}\n\n\t.battle-log-line\n\t{\n\t\tfont-size: 11px;\n\t\tcolor: #a0c0d0;\n\t\tmargin-bottom: 2px;\n\t}\n\n\t.battle-result\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t\tmargin-top: 10px;\n\t\tpadding: 12px;\n\t\tborder-radius: 10px;\n\t}\n\n\t.battle-result.win\n\t{\n\t\tbackground-color: #143828;\n\t\tborder: 2px solid #40a060;\n\t}\n\n\t.battle-result.lose\n\t{\n\t\tbackground-color: #381818;\n\t\tborder: 2px solid #a04040;\n\t}\n\n\t.battle-result-title\n\t{\n\t\tfont-size: 18px;\n\t\tfont-weight: 800;\n\t\tcolor: #f0f8ff;\n\t}\n\n\t.battle-result-body\n\t{\n\t\tfont-size: 12px;\n\t\tcolor: #b0c8d8;\n\t\tmargin-top: 4px;\n\t\ttext-align: center;\n\t}\n\n\t.fish.battle-pick .fish-body\n\t{\n\t\tborder: 2px solid #50e080;\n\t}\n\n\t// Fish Gym \u2014 centered popup (above dim backdrop)\n\t.train-panel\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttop: 50%;\n\t\tmargin-left: -220px;\n\t\tmargin-top: -200px;\n\t\twidth: 440px;\n\t\tpadding: 18px 20px 16px 20px;\n\t\tbackground-color: #0e2434;\n\t\tborder: 3px solid #3a90b0;\n\t\tborder-radius: 0;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tz-index: 81;\n\t\tpointer-events: all;\n\t\tflex-shrink: 0;\n\t}\n\n\t.playing.nochill .train-panel\n\t{\n\t\tbackground-color: #1a1018;\n\t\tborder-color: #8b4a5a;\n\t}\n\n\t.train-panel-kicker\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1.5px;\n\t\tcolor: #6ab8d8;\n\t\ttext-transform: uppercase;\n\t\tmargin-bottom: 4px;\n\t\ttext-align: center;\n\t}\n\n\t.playing.nochill .train-panel-kicker\n\t{\n\t\tcolor: #c08090;\n\t}\n\n\t.train-panel-title\n\t{\n\t\tfont-size: 22px;\n\t\tfont-weight: 800;\n\t\tcolor: #e8f4ff;\n\t\tmargin-bottom: 8px;\n\t\ttext-align: center;\n\t\twhite-space: nowrap;\n\t}\n\n\t// Post-set gains card (after 3 reps)\n\t.train-panel.train-result\n\t{\n\t\tmargin-top: -120px;\n\t\tborder-color: #40a060;\n\t\tbackground-color: #0e2818;\n\t}\n\n\t.playing.nochill .train-panel.train-result\n\t{\n\t\tborder-color: #70a040;\n\t\tbackground-color: #182010;\n\t}\n\n\t.train-result-line\n\t{\n\t\twidth: 100%;\n\t\tfont-size: 16px;\n\t\tfont-weight: 800;\n\t\tcolor: #b8f0c8;\n\t\ttext-align: center;\n\t\tpadding: 14px 12px;\n\t\tmargin-top: 4px;\n\t\tbackground-color: #0a2010;\n\t\tborder: 2px solid #3a8050;\n\t\tborder-radius: 10px;\n\t\theight: auto;\n\t\tflex-shrink: 0;\n\t\twhite-space: normal;\n\t}\n\n\t.playing.nochill .train-result-line\n\t{\n\t\tcolor: #e0f0a0;\n\t\tbackground-color: #141808;\n\t\tborder-color: #607030;\n\t}\n\n\t// Single-line stats\n\t.train-side-stats-line\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 700;\n\t\tcolor: #a0d0e8;\n\t\tletter-spacing: 0.3px;\n\t\tpadding: 6px 12px;\n\t\tbackground-color: #0a2030;\n\t\tborder: 1px solid #2a5068;\n\t\tborder-radius: 8px;\n\t\tmargin-bottom: 6px;\n\t\theight: auto;\n\t\tflex-shrink: 0;\n\t\twhite-space: nowrap;\n\t\ttext-align: center;\n\t\tjustify-content: center;\n\t\twidth: auto;\n\t}\n\n\t.playing.nochill .train-side-stats-line\n\t{\n\t\tcolor: #e0b0b8;\n\t\tbackground-color: #120a10;\n\t\tborder-color: #5a3038;\n\t}\n\n\t.train-side-sub\n\t{\n\t\tfont-size: 12px;\n\t\tcolor: #8ab0c4;\n\t\tmargin-bottom: 10px;\n\t\tmargin-top: 2px;\n\t\ttext-align: center;\n\t\tflex-shrink: 0;\n\t\theight: auto;\n\t}\n\n\t.playing.nochill .train-side-sub\n\t{\n\t\tcolor: #b08890;\n\t}\n\n\t.train-game-label\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tcolor: #ffd24a;\n\t\tletter-spacing: 0.5px;\n\t\tmargin-bottom: 8px;\n\t\ttext-transform: uppercase;\n\t}\n\n\t.train-reps\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tmargin-bottom: 10px;\n\t}\n\n\t.train-rep-dot\n\t{\n\t\twidth: 12px;\n\t\theight: 12px;\n\t\tborder-radius: 50%;\n\t\tmargin-left: 5px;\n\t\tmargin-right: 5px;\n\t\tbackground-color: #1a3040;\n\t\tborder: 2px solid #3a6080;\n\t}\n\n\t.train-rep-dot.done\n\t{\n\t\tbackground-color: #40b060;\n\t\tborder-color: #60d080;\n\t}\n\n\t.train-rep-dot.current\n\t{\n\t\tbackground-color: #ffd24a;\n\t\tborder-color: #ffe080;\n\t\twidth: 14px;\n\t\theight: 14px;\n\t}\n\n\t.train-msg\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 700;\n\t\tcolor: #c8e8a0;\n\t\tmargin-bottom: 10px;\n\t\tmargin-top: 8px;\n\t\ttext-align: center;\n\t\tmin-height: 32px;\n\t\theight: auto;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tflex-shrink: 0;\n\t\tpadding: 8px 6px;\n\t\tbackground-color: #0a2030;\n\t\tborder-radius: 6px;\n\t\twhite-space: normal;\n\t}\n\n\t.train-msg.hit\n\t{\n\t\tcolor: #ffd24a;\n\t\tfont-weight: 800;\n\t\tfont-size: 12px;\n\t\tborder: 1px solid #806030;\n\t}\n\n\t.train-msg span\n\t{\n\t\twhite-space: normal;\n\t\ttext-align: center;\n\t\theight: auto;\n\t}\n\n\t.train-msg b\n\t{\n\t\tcolor: #c8e8a0;\n\t\tfont-weight: 700;\n\t}\n\n\t.train-track\n\t{\n\t\tposition: relative;\n\t\twidth: 100%;\n\t\theight: 44px;\n\t\tbackground-color: #0a2030;\n\t\tborder: 2px solid #3a7088;\n\t\tborder-radius: 8px;\n\t\toverflow: hidden;\n\t\tmargin-bottom: 10px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.playing.nochill .train-track\n\t{\n\t\tbackground-color: #120a10;\n\t\tborder-color: #6a3040;\n\t}\n\n\t.train-zone\n\t{\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\t// Bright green so the hit window is obvious\n\t\tbackground-color: #40c060;\n\t\topacity: 0.92;\n\t\tborder-left: 1px solid #80f0a0;\n\t\tborder-right: 1px solid #80f0a0;\n\t}\n\n\t.train-marker\n\t{\n\t\tposition: absolute;\n\t\ttop: 2px;\n\t\tbottom: 2px;\n\t\t// Centered on left% (matches 0\u20131 hit logic)\n\t\twidth: 8px;\n\t\tmargin-left: -4px;\n\t\tbackground-color: #ffd24a;\n\t\tborder-radius: 3px;\n\t\tborder: 1px solid #fff8c0;\n\t\tz-index: 2;\n\t}\n\n\t.train-score\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tfont-size: 11px;\n\t\tcolor: #7a9aac;\n\t\tmargin-bottom: 12px;\n\t}\n\n\t.train-score b\n\t{\n\t\tcolor: #e0f0ff;\n\t\tfont-weight: 700;\n\t}\n\n\t.train-score-sep\n\t{\n\t\tmargin-left: 6px;\n\t\tmargin-right: 6px;\n\t\tcolor: #4a6878;\n\t}\n\n\t.train-actions\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\twidth: 100%;\n\t\tmargin-top: 4px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.train-actions .cq-btn\n\t{\n\t\tmargin-left: 6px;\n\t\tmargin-right: 6px;\n\t\tmargin-bottom: 0;\n\t\tjustify-content: center;\n\t\tmin-width: 100px;\n\t}\n\n\t.train-actions .cq-btn.big\n\t{\n\t\tpadding-top: 12px;\n\t\tpadding-bottom: 12px;\n\t\tpadding-left: 28px;\n\t\tpadding-right: 28px;\n\t\tfont-size: 16px;\n\t\tfont-weight: 800;\n\t\tmin-width: 140px;\n\t}\n\n\t// Steroid inject \u2014 absolute center above dim\n\t.inject-panel\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttop: 50%;\n\t\tmargin-left: -220px;\n\t\tmargin-top: -130px;\n\t\twidth: 440px;\n\t\tpadding: 20px 22px;\n\t\tbackground-color: #123040;\n\t\tborder: 2px solid #4a9abc;\n\t\tborder-radius: 14px;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tz-index: 81;\n\t\tpointer-events: all;\n\t\tflex-shrink: 0;\n\t}\n\n\t.playing.nochill .inject-panel\n\t{\n\t\tbackground-color: #1a1018;\n\t\tborder-color: #8b4a5a;\n\t}\n\n\t.inject-title\n\t{\n\t\tfont-size: 22px;\n\t\tfont-weight: 800;\n\t\tcolor: #e8f4ff;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.inject-msg\n\t{\n\t\tfont-size: 13px;\n\t\tcolor: #8ab0c4;\n\t\tmargin-top: 4px;\n\t\tmargin-bottom: 14px;\n\t\ttext-align: center;\n\t}\n\n\t.inject-track\n\t{\n\t\tposition: relative;\n\t\twidth: 100%;\n\t\theight: 36px;\n\t\tbackground-color: #0a2030;\n\t\tborder: 2px solid #3a7088;\n\t\tborder-radius: 10px;\n\t\toverflow: hidden;\n\t\tmargin-bottom: 16px;\n\t}\n\n\t.inject-zone\n\t{\n\t\tposition: absolute;\n\t\tleft: 42%;\n\t\twidth: 20%;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #3a9050;\n\t\topacity: 0.75;\n\t}\n\n\t.inject-marker\n\t{\n\t\tposition: absolute;\n\t\ttop: 2px;\n\t\tbottom: 2px;\n\t\twidth: 6px;\n\t\tmargin-left: -3px;\n\t\tbackground-color: #ffd24a;\n\t\tborder-radius: 2px;\n\t}\n\n\t.fish.jacked .fish-body\n\t{\n\t\tborder: 2px solid #e04040;\n\t}\n\n\t// Overfeed chonk \u2014 fat sheets carry the look; CSS backs up placeholder fish.\n\t.fish.fat .fish-body\n\t{\n\t\tborder-radius: 50%;\n\t}\n\n\t.fish.fat-2 .fish-body,\n\t.fish.fat-3 .fish-body\n\t{\n\t\tborder-radius: 50%;\n\t}\n\n\t.fish.fat-3 .fish-belly\n\t{\n\t\topacity: 0.95;\n\t\theight: 55%;\n\t}\n\n\t// Dedicated fat sheet already has the belly \u2014 light bounce, no extra blur.\n\t.fish.has-sprite.fat-2 .fish-sprite,\n\t.fish.has-sprite.fat-3 .fish-sprite\n\t{\n\t\tborder-radius: 0;\n\t}\n\n\t// Stage-3 meatball: slight extra squish on the draw box.\n\t.fish.has-sprite.fat-3\n\t{\n\t\t// scale still from DrawScale; class is for future VFX hooks\n\t}\n\n\t// Rare breed: pregnant chonk glow \u002B court dance tell.\n\t.fish.pregnant .pregnant-glow\n\t{\n\t\tposition: absolute;\n\t\tleft: 8%;\n\t\ttop: 18%;\n\t\tright: 8%;\n\t\tbottom: 12%;\n\t\tborder-radius: 50%;\n\t\tbackground-color: #f0a0c8;\n\t\topacity: 0.22;\n\t\tpointer-events: none;\n\t\tz-index: 1;\n\t}\n\n\t.fish.courting .fish-vis\n\t{\n\t\t// hearts already sell the court; no outline box\n\t}\n\n\t.tank-egg\n\t{\n\t\tposition: absolute;\n\t\twidth: 14px;\n\t\theight: 18px;\n\t\tmargin-left: -7px;\n\t\tmargin-top: -9px;\n\t\tborder-radius: 50% 50% 48% 48%;\n\t\tbackground-color: #f0e0c0;\n\t\tborder: 2px solid #c8a060;\n\t\tpointer-events: none;\n\t\tz-index: 11;\n\t}\n\n\t.tank-egg.ripe\n\t{\n\t\tbackground-color: #f0d090;\n\t\tborder-color: #d09040;\n\t}\n\n\t.tank-egg.hatching\n\t{\n\t\tbackground-color: #e8b878;\n\t\tborder-color: #e07040;\n\t}\n\n\t.breed-fx\n\t{\n\t\tposition: absolute;\n\t\tpointer-events: none;\n\t\tz-index: 14;\n\t\tmargin-left: -6px;\n\t\tmargin-top: -6px;\n\t}\n\n\t.breed-fx.heart\n\t{\n\t\twidth: 12px;\n\t\theight: 12px;\n\t\tbackground-color: #f05090;\n\t\tborder-radius: 50% 50% 0 50%;\n\t\ttransform: rotate( -45deg );\n\t\tborder: 1px solid #a02050;\n\t}\n\n\t.breed-fx.bubble\n\t{\n\t\twidth: 8px;\n\t\theight: 8px;\n\t\tborder-radius: 50%;\n\t\tbackground-color: #c0e8f8;\n\t\tborder: 1px solid #70b0d0;\n\t\topacity: 0.75;\n\t}\n\n\t.breed-fx.hatch\n\t{\n\t\twidth: 6px;\n\t\theight: 6px;\n\t\tborder-radius: 50%;\n\t\tbackground-color: #fff0a0;\n\t\tborder: 1px solid #e0a040;\n\t}\n\n\t.mate-ring\n\t{\n\t\tposition: absolute;\n\t\tmargin-left: -50%;\n\t\tmargin-top: -50%;\n\t\tborder: 2px solid #f080b0;\n\t\tborder-radius: 50%;\n\t\tpointer-events: none;\n\t\tz-index: 10;\n\t\tbackground-color: transparent;\n\t}\n\n\t.lay-flash\n\t{\n\t\tposition: absolute;\n\t\tmargin-left: -50%;\n\t\tmargin-top: -50%;\n\t\tborder-radius: 50%;\n\t\tborder: 2px solid #f0c080;\n\t\tbackground-color: #ffe8b0;\n\t\tpointer-events: none;\n\t\tz-index: 10;\n\t}\n\n\t// Jacked tint over pixel sprites (red eyes vibe without new sheets yet)\n\t.fish.has-sprite.jacked .fish-sprite\n\t{\n\t\t// veins overlay sells jacked; no rectangle\n\t}\n\n\t.fish.has-sprite.meth .fish-sprite\n\t{\n\t\t// already has meth overlays; keep sprite readable\n\t}\n\n\t// --- Growth stages (size is DrawScale; CSS sells guppy softness) ---\n\t.fish.growth-0 .fish-sprite\n\t{\n\t\t// guppy: softer silhouette\n\t\tborder-radius: 45%;\n\t\topacity: 0.96;\n\t}\n\n\t.fish.growth-1 .fish-sprite\n\t{\n\t\tborder-radius: 30%;\n\t}\n\n\t// Rarity: menu color \u002B tank glow. No box outline around the pixel art.\n\n\t.fish-menu-sub.rarity-uncommon { color: #7ad8b0; }\n\t.fish-menu-sub.rarity-rare { color: #7ab8ff; }\n\t.fish-menu-sub.rarity-paintedrare { color: #f090e0; }\n\t.fish-menu-sub.rarity-legendary { color: #ffd24a; }\n\t.fish-menu-sub.rarity-mythical { color: #d0a0ff; }\n\n\t// GBA halo: four 4px squares, not a soft oval.\n\t.fish .rarity-halo\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tpointer-events: none;\n\t\tz-index: 1;\n\t}\n\n\t.fish .rarity-halo .h\n\t{\n\t\tposition: absolute;\n\t\twidth: 4px;\n\t\theight: 4px;\n\t\tpointer-events: none;\n\t}\n\n\t.fish .rarity-halo .n { top: 6%; left: 50%; margin-left: -2px; }\n\t.fish .rarity-halo .s { bottom: 8%; left: 50%; margin-left: -2px; }\n\t.fish .rarity-halo .e { right: 8%; top: 50%; margin-top: -2px; }\n\t.fish .rarity-halo .w { left: 8%; top: 50%; margin-top: -2px; }\n\n\t.fish.rarity-uncommon .rarity-halo .h { background-color: #7ad8b0; }\n\t.fish.rarity-rare .rarity-halo .h { background-color: #7ab8ff; }\n\t.fish.rarity-paintedrare .rarity-halo .h { background-color: #f090e0; }\n\t.fish.rarity-legendary .rarity-halo .h\n\t{\n\t\tbackground-color: #ffd24a;\n\t\tanimation: goldpulse 0.8s linear infinite;\n\t}\n\t.fish.rarity-mythical .rarity-halo .h\n\t{\n\t\tanimation: gba-rainbow 0.8s linear infinite;\n\t}\n\n\t.fish .rarity-paint\n\t{\n\t\tposition: absolute;\n\t\twidth: 7px;\n\t\theight: 7px;\n\t\tborder-radius: 50%;\n\t\tpointer-events: none;\n\t\tz-index: 3;\n\t\topacity: 0.85;\n\t}\n\n\t.fish .rarity-paint.a\n\t{\n\t\tleft: 18%;\n\t\ttop: 28%;\n\t\tbackground-color: #ff60c8;\n\t}\n\n\t.fish .rarity-paint.b\n\t{\n\t\tright: 16%;\n\t\tbottom: 24%;\n\t\tbackground-color: #40e0ff;\n\t}\n\n\t// Shop preview: tiny guppy-scale swatch\n\t.legend-swatch.swatch-guppy\n\t{\n\t\t// slightly smaller art reads as \u201Cyou buy babies\u201D\n\t}\n\n\t.legend-swatch.swatch-guppy .swatch-sprite\n\t{\n\t\t// scale handled by panel size; keep sprite filling\n\t}\n\n\t.jacked-veins\n\t{\n\t\tposition: absolute;\n\t\tleft: 10%;\n\t\tright: 10%;\n\t\ttop: 30%;\n\t\theight: 3px;\n\t\tbackground-color: #c02040;\n\t\topacity: 0.7;\n\t\tpointer-events: none;\n\t\tz-index: 4;\n\t}\n\n\t.playing.remove-mode .cq-dock\n\t{\n\t\tborder-color: #c06060;\n\t}\n\n\t// Post-fight injury \u2014 reuses sick look \u002B cooldown badge\n\t.fight-hurt-badge\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\tbottom: -12px;\n\t\tmargin-left: -16px;\n\t\tmin-width: 32px;\n\t\tpadding: 1px 4px;\n\t\tbackground-color: #402018;\n\t\tborder: 1px solid #a05030;\n\t\tborder-radius: 4px;\n\t\tfont-size: 9px;\n\t\tfont-weight: 800;\n\t\tcolor: #ffb080;\n\t\ttext-align: center;\n\t\tpointer-events: none;\n\t\tz-index: 8;\n\t}\n\n\t.fish.fight-hurt.has-sprite .fish-sprite\n\t{\n\t\topacity: 0.72;\n\t\tfilter: saturate( 0.55 );\n\t}\n\n\t// Progressive death look (sick-1 \u2026 sick-4)\n\t.fish.sick-1 .fish-body\n\t{\n\t\topacity: 0.85;\n\t\tborder: 1px solid #7a9a50;\n\t}\n\n\t.fish.sick-2 .fish-body\n\t{\n\t\topacity: 0.65;\n\t\tborder: 1px solid #8a8040;\n\t}\n\n\t.fish.sick-3 .fish-body\n\t{\n\t\topacity: 0.45;\n\t\tborder: 1px solid #a05020;\n\t}\n\n\t.fish.sick-4 .fish-body\n\t{\n\t\topacity: 0.28;\n\t\tborder: 2px solid #c41e1e;\n\t}\n\n\t.fish.meth .fish-body\n\t{\n\t\tborder: 2px solid #ff40a0;\n\t\t// Slightly stretched / wired look\n\t\topacity: 1;\n\t}\n\n\t.fish.meth\n\t{\n\t\t// Tiny scale pulse via animation (tweaking)\n\t\tanimation-name: methpulse;\n\t\tanimation-duration: 0.18s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-timing-function: steps( 2, end );\n\t}\n\n\t// ----- Weed gummies high (trippy, slow) -----\n\t.fish.stoned\n\t{\n\t\tanimation-name: stonedwobble;\n\t\tanimation-duration: 1.6s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-timing-function: ease-in-out;\n\t}\n\n\t.fish.stoned.has-sprite .fish-sprite\n\t{\n\t\t// Soft green-purple haze on the sprite panel\n\t\tborder: 2px solid #80e060;\n\t\topacity: 0.92;\n\t}\n\n\t.fish.stoned .fish-body\n\t{\n\t\tborder: 2px solid #90e050;\n\t\topacity: 0.95;\n\t}\n\n\t.stoned-aura\n\t{\n\t\tposition: absolute;\n\t\tleft: -8px;\n\t\ttop: -8px;\n\t\tright: -8px;\n\t\tbottom: -8px;\n\t\tborder: 2px solid #70d050;\n\t\tborder-radius: 50%;\n\t\topacity: 0.45;\n\t\tpointer-events: none;\n\t\tz-index: 2;\n\t\tanimation-name: stonedaura;\n\t\tanimation-duration: 1.2s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-timing-function: ease-in-out;\n\t}\n\n\t.stoned-aura.ring2\n\t{\n\t\tleft: -14px;\n\t\ttop: -14px;\n\t\tright: -14px;\n\t\tbottom: -14px;\n\t\tborder-color: #c060e0;\n\t\topacity: 0.3;\n\t\tanimation-duration: 1.8s;\n\t}\n\n\t.stoned-eyes\n\t{\n\t\tposition: absolute;\n\t\tright: 12%;\n\t\ttop: 20%;\n\t\twidth: 14px;\n\t\theight: 10px;\n\t\tbackground-color: #a0ff60;\n\t\tborder: 1px solid #ffffff;\n\t\tborder-radius: 4px;\n\t\tpointer-events: none;\n\t\tz-index: 6;\n\t\topacity: 0.9;\n\t}\n\n\t.stoned-spark\n\t{\n\t\tposition: absolute;\n\t\twidth: 5px;\n\t\theight: 5px;\n\t\tborder-radius: 50%;\n\t\tpointer-events: none;\n\t\tz-index: 7;\n\t\topacity: 0.85;\n\t\tanimation-name: stonedspark;\n\t\tanimation-duration: 0.9s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-timing-function: ease-in-out;\n\t}\n\n\t.stoned-spark.a\n\t{\n\t\tleft: 10%;\n\t\ttop: -4px;\n\t\tbackground-color: #80ff60;\n\t}\n\n\t.stoned-spark.b\n\t{\n\t\tright: 8%;\n\t\ttop: 40%;\n\t\tbackground-color: #e080ff;\n\t\tanimation-duration: 1.1s;\n\t}\n\n\t.stoned-spark.c\n\t{\n\t\tleft: 40%;\n\t\tbottom: -2px;\n\t\tbackground-color: #60e0ff;\n\t\tanimation-duration: 1.3s;\n\t}\n\n\t.meth-eyes\n\t{\n\t\tposition: absolute;\n\t\tright: 14%;\n\t\ttop: 18%;\n\t\twidth: 12px;\n\t\theight: 8px;\n\t\tbackground-color: #ff1040;\n\t\tborder: 1px solid #ffffff;\n\t\tborder-radius: 2px;\n\t\tpointer-events: none;\n\t\tz-index: 5;\n\t\t// Dilated pupils energy\n\t\tbox-shadow: 0 0 6px #ff2060;\n\t}\n\n\t.meth-aura\n\t{\n\t\tposition: absolute;\n\t\tleft: -4px;\n\t\ttop: -4px;\n\t\tright: -4px;\n\t\tbottom: -4px;\n\t\tborder: 1px solid #ff60c0;\n\t\topacity: 0.55;\n\t\tpointer-events: none;\n\t\tz-index: 3;\n\t\tanimation-name: methaura;\n\t\tanimation-duration: 0.22s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-timing-function: steps( 2, end );\n\t}\n\n\t.meth-sweat\n\t{\n\t\tposition: absolute;\n\t\tleft: 20%;\n\t\ttop: -6px;\n\t\twidth: 4px;\n\t\theight: 6px;\n\t\tbackground-color: #a0d0ff;\n\t\topacity: 0.8;\n\t\tpointer-events: none;\n\t\tz-index: 5;\n\t\tborder-radius: 2px;\n\t}\n\n\t.hud-stat.addiction-hud\n\t{\n\t\tmargin-top: 3px;\n\t\tfont-size: 11px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #ff80c0;\n\t}\n\n\t// Hooked but sober \u2014 dull, needy\n\t.fish.hooked-1 .fish-body\n\t{\n\t\tborder: 1px solid #a06080;\n\t}\n\n\t.fish.hooked-2 .fish-body,\n\t.fish.hooked-3 .fish-body\n\t{\n\t\tborder: 1px solid #c04080;\n\t}\n\n\t.fish.hooked-4 .fish-body\n\t{\n\t\tborder: 2px solid #e02070;\n\t}\n\n\t// Withdrawal \u2014 pale, sinking vibe\n\t.fish.withdrawing .fish-body\n\t{\n\t\topacity: 0.55;\n\t\tborder: 2px solid #606080;\n\t}\n\n\t.wd-shiver\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\tborder: 1px dashed #8090b0;\n\t\topacity: 0.5;\n\t\tpointer-events: none;\n\t\tz-index: 4;\n\t\tanimation-name: wdshiver;\n\t\tanimation-duration: 0.35s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-timing-function: steps( 2, end );\n\t}\n\n\t// Tiny \u0022X\u0022 eyes when things get bad\n\t.sick-x\n\t{\n\t\tposition: absolute;\n\t\tright: 14%;\n\t\ttop: 20%;\n\t\twidth: 10px;\n\t\theight: 10px;\n\t\tpointer-events: none;\n\t\tz-index: 5;\n\t\tfont-size: 9px;\n\t\tfont-weight: 800;\n\t\tcolor: #201808;\n\t\ttext-align: center;\n\t}\n\n\t.fish.sick-2 .sick-x,\n\t.fish.sick-3 .sick-x,\n\t.fish.sick-4 .sick-x\n\t{\n\t\t// drawn as two lines via borders\n\t\tborder-left: 2px solid #101008;\n\t\tborder-top: 2px solid #101008;\n\t\twidth: 7px;\n\t\theight: 7px;\n\t\t// rotate-ish using asymmetric box \u2014 keep simple as dark blot\n\t\tbackground-color: #101008;\n\t\topacity: 0.85;\n\t\tborder-radius: 1px;\n\t}\n\n\t.fish.sick-4 .sick-x\n\t{\n\t\tbackground-color: #c41e1e;\n\t\topacity: 1;\n\t}\n\n\t.hud-stat.death-warn\n\t{\n\t\tmargin-top: 4px;\n\t\tfont-size: 12px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 2px;\n\t\tcolor: #ff6040;\n\t}\n\n\t.playing.dying-hard .hud-stat.death-warn\n\t{\n\t\tcolor: #ff2020;\n\t}\n\n\t// Tank-wide distress flash \u002B vignette\n\t.death-flash\n\t{\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #c41e1e;\n\t\tpointer-events: none;\n\t\tz-index: 8;\n\t}\n\n\t.boom-white-flash\n\t{\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #ffffff;\n\t\tpointer-events: none;\n\t\tz-index: 9;\n\t}\n\n\t// Centered on fish via inline left/top; margins keep the ring expanding around that point.\n\t.boom-shock\n\t{\n\t\tposition: absolute;\n\t\twidth: 80px;\n\t\theight: 80px;\n\t\tmargin-left: -40px;\n\t\tmargin-top: -40px;\n\t\tborder: 4px solid #ffd040;\n\t\tborder-radius: 50%;\n\t\tpointer-events: none;\n\t\tz-index: 10;\n\t\topacity: 0.7;\n\t\tanimation-name: boomshock;\n\t\tanimation-duration: 0.45s;\n\t\tanimation-timing-function: ease-out;\n\t\tanimation-iteration-count: 1;\n\t}\n\n\t.tank-glass.booming\n\t{\n\t\tborder-color: #ff4020;\n\t}\n\n\t.death-vignette\n\t{\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tpointer-events: none;\n\t\tz-index: 7;\n\t\tborder: 0 solid #000000;\n\t\topacity: 0;\n\t}\n\n\t.death-vignette.death-stage-1\n\t{\n\t\topacity: 0.15;\n\t\tborder: 8px solid #1a1008;\n\t}\n\n\t.death-vignette.death-stage-2\n\t{\n\t\topacity: 0.28;\n\t\tborder: 14px solid #180c08;\n\t}\n\n\t.death-vignette.death-stage-3\n\t{\n\t\topacity: 0.4;\n\t\tborder: 22px solid #140808;\n\t}\n\n\t.death-vignette.death-stage-4\n\t{\n\t\topacity: 0.55;\n\t\tborder: 30px solid #100404;\n\t}\n\n\t.tank-glass.death-stage-3\n\t{\n\t\tborder-color: #a04020;\n\t}\n\n\t.tank-glass.death-stage-4\n\t{\n\t\tborder-color: #c41e1e;\n\t}\n\n\t.playing.dying .tank-water\n\t{\n\t\t// slight sick tint under the water\n\t\tbackground-color: #0d3248;\n\t}\n\n\t.playing.dying-hard .tank-water\n\t{\n\t\tbackground-color: #1a2030;\n\t}\n\n\t.playing.nochill .tank-water\n\t{\n\t\tbackground-color: #1a1018;\n\t}\n\n\t.playing.nochill .tank-glass\n\t{\n\t\tborder-color: #8b1a1a;\n\t}\n\n\t.playing.nochill .hud-kicker\n\t{\n\t\tcolor: #c43c3c;\n\t}\n\n\t.chaos-bar\n\t{\n\t\tposition: relative;\n\t\twidth: 1280px;\n\t\theight: 10px;\n\t\tmargin-bottom: 8px;\n\t\tbackground-color: #1a2430;\n\t\tborder: 2px solid #2a4050;\n\t\tflex-shrink: 0;\n\t\toverflow: hidden;\n\t}\n\n\t.chaos-fill\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #c41e1e;\n\t}\n\n\t.chaos-mark\n\t{\n\t\tposition: absolute;\n\t\tleft: 40%;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\twidth: 2px;\n\t\tbackground-color: #ffd24a;\n\t\topacity: 0.85;\n\t}\n\n\t.playing.nochill .chaos-bar\n\t{\n\t\tborder-color: #5a2020;\n\t\tbackground-color: #18080c;\n\t}\n\n\t.tank-chrome\n\t{\n\t\tposition: relative;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tflex-shrink: 0;\n\t}\n\n\t.tank-hud\n\t{\n\t\tflex-direction: row;\n\t\tjustify-content: space-between;\n\t\talign-items: flex-end;\n\t\twidth: 1280px;\n\t\tmargin-bottom: 8px;\n\t}\n\n\t.hud-left\n\t{\n\t\tflex-direction: column;\n\t\tmin-width: 180px;\n\t}\n\n\t.hud-center\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tflex-grow: 1;\n\t}\n\n\t.doge-label\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 3px;\n\t\tcolor: #c9a227;\n\t}\n\n\t.doge-value\n\t{\n\t\tfont-size: 32px;\n\t\tfont-weight: 800;\n\t\tcolor: #ffd24a;\n\t\tletter-spacing: 1px;\n\t}\n\n\t.doge-rate\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 700;\n\t\tcolor: #8bc34a;\n\t\tmargin-top: 2px;\n\t}\n\n\t.hud-kicker\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 3px;\n\t\tcolor: #3a8ab0;\n\t}\n\n\t.hud-title\n\t{\n\t\tfont-size: 24px;\n\t\tfont-weight: 800;\n\t\tcolor: #e8f4ff;\n\t\tletter-spacing: 1px;\n\t}\n\n\t.hud-right\n\t{\n\t\tflex-direction: column;\n\t\talign-items: flex-end;\n\t\tmin-width: 180px;\n\t}\n\n\t.hud-stat\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 700;\n\t\tcolor: #c0d8e8;\n\t\tletter-spacing: 1px;\n\t}\n\n\t.hud-stat.dim\n\t{\n\t\tfont-size: 10px;\n\t\tcolor: #5a7080;\n\t\tmargin-top: 4px;\n\t}\n\n\t// --- Fusion flash (multi-color strobe on combine) ---\n\t.fusion-flash\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tpointer-events: none;\n\t\tz-index: 55;\n\t\tbackground-color: #ffffff;\n\t}\n\n\t.fusion-flash.fusion-white\n\t{\n\t\tbackground-color: #f8fcff;\n\t}\n\n\t.fusion-flash.fusion-mag\n\t{\n\t\tbackground-color: #e040c0;\n\t}\n\n\t.fusion-flash.fusion-toxic\n\t{\n\t\tbackground-color: #40f080;\n\t}\n\n\t// Behind fish (z 12) so the new abom reads on top of the birth ring.\n\t.fusion-ring\n\t{\n\t\tposition: absolute;\n\t\tmargin-left: -50%;\n\t\tmargin-top: -50%;\n\t\tborder: 3px solid #e8a0ff;\n\t\tborder-radius: 50%;\n\t\tpointer-events: none;\n\t\tz-index: 10;\n\t}\n\n\t.fusion-spark\n\t{\n\t\tposition: absolute;\n\t\twidth: 18px;\n\t\theight: 18px;\n\t\tmargin-left: -9px;\n\t\tmargin-top: -9px;\n\t\tbackground-color: #ffffff;\n\t\tborder: 2px solid #c060ff;\n\t\tborder-radius: 50%;\n\t\tpointer-events: none;\n\t\tz-index: 11;\n\t}\n\n\t.tank-glass.fusing\n\t{\n\t\tborder-color: #c060ff;\n\t}\n\n\t.tank-glass.combine-pick\n\t{\n\t\tborder-color: #e09040;\n\t}\n\n\t.fish.combine-a\n\t{\n\t\t// first parent selected for fuse\n\t\tborder: 2px solid #ffb040;\n\t}\n\n\t// Monster abomination idle thrash\n\t// Lovecraftian tentacle beast (monster \u002B normal fuse)\n\t.fish.tentacle-beast .fish-body\n\t{\n\t\tborder-radius: 42% 48% 40% 55%;\n\t\tbackground-color: #1a3040;\n\t\tborder: 2px solid #60e0a0;\n\t}\n\n\t.fish.tentacle-beast .tentacle-aura\n\t{\n\t\tposition: absolute;\n\t\tleft: -12%;\n\t\ttop: -10%;\n\t\tright: -12%;\n\t\tbottom: -14%;\n\t\tborder-radius: 50%;\n\t\tbackground-color: #204858;\n\t\topacity: 0.35;\n\t\tpointer-events: none;\n\t\tz-index: 0;\n\t}\n\n\t.fish.tentacle-beast .tentacle-arm\n\t{\n\t\tposition: absolute;\n\t\twidth: 18%;\n\t\theight: 42%;\n\t\tborder-radius: 40% 40% 55% 55%;\n\t\tbackground-color: #2a4858;\n\t\tborder: 1px solid #50c090;\n\t\tpointer-events: none;\n\t\tz-index: 1;\n\t}\n\n\t.fish.tentacle-beast .tentacle-arm.t1\n\t{\n\t\tleft: -6%;\n\t\ttop: 28%;\n\t\ttransform: rotate( -35deg );\n\t}\n\n\t.fish.tentacle-beast .tentacle-arm.t2\n\t{\n\t\tleft: 8%;\n\t\tbottom: -12%;\n\t\theight: 38%;\n\t\ttransform: rotate( 12deg );\n\t}\n\n\t.fish.tentacle-beast .tentacle-arm.t3\n\t{\n\t\tright: 6%;\n\t\tbottom: -10%;\n\t\theight: 40%;\n\t\ttransform: rotate( -18deg );\n\t}\n\n\t.fish.tentacle-beast .tentacle-arm.t4\n\t{\n\t\tright: -8%;\n\t\ttop: 20%;\n\t\ttransform: rotate( 42deg );\n\t}\n\n\t.fish.tentacle-beast .tentacle-arm.t5\n\t{\n\t\tleft: 38%;\n\t\ttop: -16%;\n\t\theight: 32%;\n\t\twidth: 14%;\n\t\ttransform: rotate( 8deg );\n\t}\n\n\t.fish.tentacle-beast .tentacle-eye\n\t{\n\t\tposition: absolute;\n\t\twidth: 10px;\n\t\theight: 10px;\n\t\tborder-radius: 50%;\n\t\tbackground-color: #90ffe0;\n\t\tborder: 1px solid #104030;\n\t\tpointer-events: none;\n\t\tz-index: 3;\n\t}\n\n\t.fish.tentacle-beast .tentacle-eye.a\n\t{\n\t\tleft: 28%;\n\t\ttop: 34%;\n\t}\n\n\t.fish.tentacle-beast .tentacle-eye.b\n\t{\n\t\tleft: 48%;\n\t\ttop: 28%;\n\t\twidth: 12px;\n\t\theight: 12px;\n\t}\n\n\t.fish.tentacle-beast .tentacle-eye.c\n\t{\n\t\tleft: 62%;\n\t\ttop: 42%;\n\t\twidth: 8px;\n\t\theight: 8px;\n\t}\n\n\t.fish.monster\n\t{\n\t\t// continuous wrongness\n\t}\n\n\t.fish.monster .monster-aura\n\t{\n\t\tposition: absolute;\n\t\tleft: -12%;\n\t\ttop: -12%;\n\t\tright: -12%;\n\t\tbottom: -12%;\n\t\tborder: 2px solid #a040c0;\n\t\tborder-radius: 40%;\n\t\topacity: 0.45;\n\t\tpointer-events: none;\n\t\tanimation-name: monster-pulse;\n\t\tanimation-duration: 0.55s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-direction: alternate;\n\t}\n\n\t.fish.monster .monster-glitch\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tpointer-events: none;\n\t\topacity: 0.25;\n\t\tbackground-color: #8020a0;\n\t\tanimation-name: monster-glitch;\n\t\tanimation-duration: 0.18s;\n\t\tanimation-iteration-count: infinite;\n\t}\n\n\t.fish.monster .monster-eye-extra\n\t{\n\t\tposition: absolute;\n\t\twidth: 8px;\n\t\theight: 8px;\n\t\tbackground-color: #fff8e0;\n\t\tborder: 1px solid #201010;\n\t\tborder-radius: 50%;\n\t\tpointer-events: none;\n\t\tz-index: 4;\n\t\tanimation-name: monster-eye-blink;\n\t\tanimation-duration: 0.9s;\n\t\tanimation-iteration-count: infinite;\n\t}\n\n\t.fish.monster .monster-eye-extra.a\n\t{\n\t\tleft: 55%;\n\t\ttop: 18%;\n\t}\n\n\t.fish.monster .monster-eye-extra.b\n\t{\n\t\tleft: 22%;\n\t\ttop: 55%;\n\t\twidth: 6px;\n\t\theight: 6px;\n\t\tanimation-duration: 0.55s;\n\t}\n\n\t.fish.monster.has-sprite .fish-sprite\n\t{\n\t\tanimation-name: monster-thrash;\n\t\tanimation-duration: 0.28s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-timing-function: linear;\n\t}\n\n\t@keyframes monster-pulse\n\t{\n\t\tfrom { opacity: 0.25; }\n\t\tto { opacity: 0.65; }\n\t}\n\n\t@keyframes monster-glitch\n\t{\n\t\t0% { opacity: 0.05; left: 0; }\n\t\t40% { opacity: 0.35; left: 2px; }\n\t\t70% { opacity: 0.1; left: -2px; }\n\t\t100% { opacity: 0.2; left: 0; }\n\t}\n\n\t@keyframes monster-eye-blink\n\t{\n\t\t0% { opacity: 1; }\n\t\t80% { opacity: 1; }\n\t\t85% { opacity: 0.15; }\n\t\t100% { opacity: 1; }\n\t}\n\n\t@keyframes monster-thrash\n\t{\n\t\t0% { transform: translate( 0px, 0px ) rotate( 0deg ); }\n\t\t25% { transform: translate( 1px, -1px ) rotate( -2deg ); }\n\t\t50% { transform: translate( -1px, 1px ) rotate( 2deg ); }\n\t\t75% { transform: translate( 1px, 1px ) rotate( -1deg ); }\n\t\t100% { transform: translate( 0px, 0px ) rotate( 0deg ); }\n\t}\n\n\t// Monster \u002B meth: even worse thrash, slime, extra eye\n\t.fish.monster-meth .monster-aura\n\t{\n\t\tborder-color: #ff40c0;\n\t\topacity: 0.75;\n\t\tanimation-duration: 0.28s;\n\t}\n\n\t.fish.monster-meth .monster-glitch\n\t{\n\t\tbackground-color: #e02080;\n\t\topacity: 0.4;\n\t\tanimation-duration: 0.1s;\n\t}\n\n\t.fish.monster-meth.has-sprite .fish-sprite\n\t{\n\t\tanimation-name: monster-meth-thrash;\n\t\tanimation-duration: 0.16s;\n\t\tanimation-iteration-count: infinite;\n\t\tborder: 2px solid #ff60c0;\n\t}\n\n\t.fish.monster-meth .monster-meth-slime\n\t{\n\t\tposition: absolute;\n\t\tleft: 10%;\n\t\tright: 10%;\n\t\tbottom: 5%;\n\t\theight: 28%;\n\t\tbackground-color: #60c040;\n\t\topacity: 0.35;\n\t\tborder-radius: 40%;\n\t\tpointer-events: none;\n\t\tz-index: 2;\n\t\tanimation-name: monster-slime;\n\t\tanimation-duration: 0.7s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-direction: alternate;\n\t}\n\n\t.fish.monster-meth .monster-meth-vein\n\t{\n\t\tposition: absolute;\n\t\twidth: 3px;\n\t\theight: 40%;\n\t\tbackground-color: #c02060;\n\t\topacity: 0.7;\n\t\tpointer-events: none;\n\t\tz-index: 3;\n\t}\n\n\t.fish.monster-meth .monster-meth-vein.a\n\t{\n\t\tleft: 30%;\n\t\ttop: 20%;\n\t\ttransform: rotate( 18deg );\n\t}\n\n\t.fish.monster-meth .monster-meth-vein.b\n\t{\n\t\tright: 28%;\n\t\ttop: 25%;\n\t\theight: 50%;\n\t\ttransform: rotate( -22deg );\n\t}\n\n\t.fish.monster-meth .monster-eye-extra.c\n\t{\n\t\tleft: 70%;\n\t\ttop: 40%;\n\t\twidth: 10px;\n\t\theight: 10px;\n\t\tbackground-color: #ff4060;\n\t\tanimation-duration: 0.35s;\n\t}\n\n\t@keyframes monster-meth-thrash\n\t{\n\t\t0% { transform: translate( 0px, 0px ) rotate( 0deg ); }\n\t\t20% { transform: translate( 3px, -2px ) rotate( -4deg ); }\n\t\t40% { transform: translate( -3px, 2px ) rotate( 5deg ); }\n\t\t60% { transform: translate( 2px, 3px ) rotate( -3deg ); }\n\t\t80% { transform: translate( -2px, -2px ) rotate( 3deg ); }\n\t\t100% { transform: translate( 0px, 0px ) rotate( 0deg ); }\n\t}\n\n\t@keyframes monster-slime\n\t{\n\t\tfrom { opacity: 0.2; height: 22%; }\n\t\tto { opacity: 0.45; height: 34%; }\n\t}\n\n\t// Meth-rush glass cracks \u2014 clean transparent line sprites over water\n\t.tank-cracks\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tpointer-events: none;\n\t\tz-index: 38;\n\t\toverflow: hidden;\n\t}\n\n\t.crack-spr\n\t{\n\t\tposition: absolute;\n\t\tpointer-events: none;\n\t\topacity: 0.72;\n\t}\n\n\t.crack-sprite\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t}\n\n\t.crack-spr.c1\n\t{\n\t\tleft: 12%;\n\t\ttop: 6%;\n\t\twidth: 160px;\n\t\theight: 200px;\n\t}\n\n\t.crack-spr.c2\n\t{\n\t\tleft: 38%;\n\t\ttop: 28%;\n\t\twidth: 220px;\n\t\theight: 132px;\n\t}\n\n\t.crack-spr.c3\n\t{\n\t\tright: 10%;\n\t\ttop: 10%;\n\t\twidth: 200px;\n\t\theight: 200px;\n\t}\n\n\t.crack-spr.c4\n\t{\n\t\tleft: 8%;\n\t\tbottom: 14%;\n\t\twidth: 140px;\n\t\theight: 140px;\n\t}\n\n\t.crack-spr.c5\n\t{\n\t\tleft: 52%;\n\t\ttop: 8%;\n\t\twidth: 140px;\n\t\theight: 180px;\n\t\topacity: 0.55;\n\t}\n\n\t.crack-spr.spider\n\t{\n\t\tleft: 42%;\n\t\ttop: 38%;\n\t\twidth: 220px;\n\t\theight: 220px;\n\t\topacity: 0.85;\n\t}\n\n\t.tank-glass.cracks-3 .crack-spr,\n\t.tank-glass.cracks-4 .crack-spr,\n\t.tank-glass.cracks-5 .crack-spr\n\t{\n\t\topacity: 0.88;\n\t}\n\n\t.tank-glass.cracks-5\n\t{\n\t\tborder-color: #6a8090;\n\t}\n\n\t.tank-glass.cracks-5 .crack-spr.spider\n\t{\n\t\topacity: 0.95;\n\t}\n\n\t.tank-glass.meth-rush\n\t{\n\t\tborder-color: #e05090;\n\t}\n\n\t// Meth rush minigame panel\n\t.meth-rush-panel\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttop: 50%;\n\t\tmargin-left: -220px;\n\t\tmargin-top: -160px;\n\t\twidth: 440px;\n\t\tpadding: 20px 22px;\n\t\tbackground-color: #1a1020;\n\t\tborder: 3px solid #e04080;\n\t\tborder-radius: 0;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tz-index: 81;\n\t\tpointer-events: all;\n\t}\n\n\t.meth-rush-kicker\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 2px;\n\t\tcolor: #ff60a0;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.meth-rush-title\n\t{\n\t\tfont-size: 20px;\n\t\tfont-weight: 800;\n\t\tcolor: #ffe0f0;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.meth-rush-sub\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 600;\n\t\tcolor: #c090a8;\n\t\tmargin-bottom: 12px;\n\t\ttext-align: center;\n\t}\n\n\t.meth-rush-msg\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 700;\n\t\tcolor: #ffd080;\n\t\tmargin-bottom: 12px;\n\t\tmin-height: 20px;\n\t\ttext-align: center;\n\t}\n\n\t.meth-rush-track\n\t{\n\t\tmargin-bottom: 10px;\n\t}\n\n\t.meth-rush-score\n\t{\n\t\tflex-direction: row;\n\t\tfont-size: 12px;\n\t\tcolor: #b090a0;\n\t\tmargin-bottom: 14px;\n\t}\n\n\t// One clean aquarium frame \u2014 GBA glass, not a flat teal slab\n\t.tank-glass\n\t{\n\t\tposition: relative;\n\t\tflex-direction: column;\n\t\twidth: 1280px;\n\t\tflex-shrink: 0;\n\t\tborder: 5px solid #3a88a8;\n\t\tborder-top-color: #7ec8e0;\n\t\tborder-left-color: #68b8d0;\n\t\tborder-right-color: #1a4860;\n\t\tborder-bottom-color: #0e2838;\n\t\tbackground-color: #061018;\n\t\tborder-radius: 2px;\n\t\toverflow: hidden;\n\t\tcursor: pointer;\n\t\tz-index: 3;\n\t\tbox-shadow: 4px 10px 0 #0a1014, 0 18px 22px #00000099;\n\t}\n\n\t.tank-rim\n\t{\n\t\twidth: 100%;\n\t\theight: 10px;\n\t\tbackground-color: #1a3848;\n\t\tflex-shrink: 0;\n\t}\n\n\t.tank-rim.top\n\t{\n\t\theight: 12px;\n\t\tbackground-color: #4a90a8;\n\t\tborder-bottom: 2px solid #2a6078;\n\t}\n\n\t.tank-rim.bottom\n\t{\n\t\theight: 14px;\n\t\tbackground-color: #142830;\n\t\tborder-top: 2px solid #0a1820;\n\t}\n\n\t.tank-base\n\t{\n\t\theight: 10px;\n\t\twidth: 100%;\n\t\tbackground-color: #0c181c;\n\t\tflex-shrink: 0;\n\t\tborder-top: 1px solid #243840;\n\t}\n\n\t.tank-water\n\t{\n\t\tposition: relative;\n\t\twidth: 1280px;\n\t\theight: 620px;\n\t\t// Deep aquarium \u2014 wallpaper teal, not flat navy\n\t\tbackground-color: #063048;\n\t\toverflow: hidden;\n\t\tflex-shrink: 0;\n\t}\n\n\t.tank-backdrop\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tz-index: 0;\n\t\tpointer-events: none;\n\t\toverflow: hidden;\n\t}\n\n\t.tank-backdrop-spr\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.tank-water.has-backdrop .tank-depth\n\t{\n\t\topacity: 0.12;\n\t}\n\n\t.tank-water.has-backdrop .tank-caustic\n\t{\n\t\topacity: 0.04;\n\t}\n\n\t// Fish card must not get sheared by the glass when a fish is on the rim.\n\t.tank-glass.menu-open\n\t{\n\t\toverflow: visible;\n\t}\n\n\t.tank-glass.menu-open .tank-water\n\t{\n\t\toverflow: visible;\n\t}\n\n\t// Full-tank click catcher under fish \u2014 closes menu without eating fish clicks\n\t.tank-water-hit\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tz-index: 1;\n\t\tpointer-events: all;\n\t\tcursor: default;\n\t}\n\n\t.playing.chill .tank-glass\n\t{\n\t\tborder-color: #48a0c0;\n\t\tbackground-color: #061820;\n\t\tborder-radius: 4px;\n\t}\n\n\t.playing.chill .tank-rim\n\t{\n\t\tbackground-color: #2a5868;\n\t}\n\n\t.playing.chill .tank-rim.top\n\t{\n\t\tbackground-color: #58a0b8;\n\t}\n\n\t.playing.chill .tank-gravel\n\t{\n\t\tbackground-color: #3a2a28;\n\t}\n\n\t// Depth bands \u2014 stacked solids (s\u0026box has no gradients)\n\t.tank-depth\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tpointer-events: none;\n\t\tz-index: 0;\n\t}\n\n\t.tank-depth.d1\n\t{\n\t\ttop: 0;\n\t\theight: 160px;\n\t\tbackground-color: #0a5878;\n\t\topacity: 0.42;\n\t}\n\n\t.tank-depth.d2\n\t{\n\t\tbottom: 48px;\n\t\theight: 220px;\n\t\tbackground-color: #021820;\n\t\topacity: 0.45;\n\t}\n\n\t.tank-caustic\n\t{\n\t\tposition: absolute;\n\t\tleft: 80px;\n\t\tright: 80px;\n\t\ttop: 70px;\n\t\theight: 18px;\n\t\tbackground-color: #40c8e8;\n\t\topacity: 0.08;\n\t\tpointer-events: none;\n\t\tz-index: 0;\n\t\tanimation-name: tankcaustic;\n\t\tanimation-duration: 4.2s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-timing-function: ease-in-out;\n\t}\n\n\t.tank-shine\n\t{\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\twidth: 220px;\n\t\theight: 100%;\n\t\tbackground-color: #68d0f0;\n\t\topacity: 0.07;\n\t\tpointer-events: none;\n\t\tz-index: 2;\n\t}\n\n\t.tank-scan\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\tpointer-events: none;\n\t\tz-index: 25;\n\t\toverflow: hidden;\n\t}\n\n\t.tank-scan .gba-scan-line\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\theight: 2px;\n\t\tbackground-color: #02060c;\n\t\topacity: 0.22;\n\t\tpointer-events: none;\n\t}\n\n\t.playing.nochill .tank-scan .gba-scan-line\n\t{\n\t\tbackground-color: #100408;\n\t\topacity: 0.28;\n\t}\n\n\t.tank-glass-lip\n\t{\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\twidth: 14px;\n\t\tpointer-events: none;\n\t\tz-index: 4;\n\t\tbackground-color: #70d8f0;\n\t\topacity: 0.18;\n\t}\n\n\t.tank-glass-lip.left\n\t{\n\t\tleft: 0;\n\t}\n\n\t.tank-glass-lip.right\n\t{\n\t\tright: 0;\n\t\twidth: 18px;\n\t\tbackground-color: #102838;\n\t\topacity: 0.38;\n\t}\n\n\t.tank-bubbles\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\tpointer-events: none;\n\t\tz-index: 2;\n\t}\n\n\t.tank-bubbles .bubble\n\t{\n\t\tposition: absolute;\n\t\tborder: 2px solid #a8e8ff;\n\t\tbackground-color: #80d0e8;\n\t\topacity: 0.35;\n\t\tborder-radius: 50%;\n\t\tanimation-name: tankbubble;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-timing-function: linear;\n\t}\n\n\t.tank-bubbles .bubble.b1 { left: 70px;  bottom: 36px; width: 8px;  height: 8px;  animation-duration: 5.2s; animation-delay: 0s; }\n\t.tank-bubbles .bubble.b2 { left: 92px;  bottom: 50px; width: 5px;  height: 5px;  animation-duration: 6.4s; animation-delay: 1.1s; }\n\t.tank-bubbles .bubble.b3 { left: 118px; bottom: 28px; width: 10px; height: 10px; animation-duration: 4.6s; animation-delay: 0.4s; }\n\t.tank-bubbles .bubble.b4 { left: 980px; bottom: 40px; width: 7px;  height: 7px;  animation-duration: 5.8s; animation-delay: 0.8s; }\n\t.tank-bubbles .bubble.b5 { left: 1008px; bottom: 56px; width: 5px; height: 5px;  animation-duration: 6.8s; animation-delay: 1.8s; }\n\t.tank-bubbles .bubble.b6 { left: 1040px; bottom: 32px; width: 9px; height: 9px;  animation-duration: 4.9s; animation-delay: 0.2s; }\n\t.tank-bubbles .bubble.b7 { left: 420px; bottom: 30px; width: 6px;  height: 6px;  animation-duration: 7.1s; animation-delay: 2.2s; }\n\t.tank-bubbles .bubble.b8 { left: 640px; bottom: 44px; width: 4px;  height: 4px;  animation-duration: 5.5s; animation-delay: 1.4s; }\n\t.tank-bubbles .bubble.b9 { left: 1180px; bottom: 38px; width: 8px; height: 8px;  animation-duration: 6.0s; animation-delay: 0.6s; }\n\n\t.tank-gravel\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\theight: 42px;\n\t\tbackground-color: #2e2420;\n\t\tpointer-events: none;\n\t\tz-index: 3;\n\t}\n\n\t.tank-gravel.has-sprite\n\t{\n\t\tbackground-color: transparent;\n\t\toverflow: hidden;\n\t}\n\n\t.tank-gravel-spr\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.tank-gravel .pebble\n\t{\n\t\tposition: absolute;\n\t\tbottom: 4px;\n\t\tbackground-color: #4a3a34;\n\t\tborder-radius: 40%;\n\t\tpointer-events: none;\n\t}\n\n\t.tank-gravel .pebble.p1 { left: 18px;   width: 28px; height: 14px; background-color: #5a4034; }\n\t.tank-gravel .pebble.p2 { left: 160px;  width: 36px; height: 12px; background-color: #3a3028; }\n\t.tank-gravel .pebble.p3 { left: 340px;  width: 22px; height: 16px; background-color: #6a5040; }\n\t.tank-gravel .pebble.p4 { left: 520px;  width: 40px; height: 13px; background-color: #4a382c; }\n\t.tank-gravel .pebble.p5 { left: 720px;  width: 26px; height: 15px; background-color: #5a4438; }\n\t.tank-gravel .pebble.p6 { left: 900px;  width: 34px; height: 12px; background-color: #3e3228; }\n\t.tank-gravel .pebble.p7 { left: 1080px; width: 30px; height: 16px; background-color: #624838; }\n\t.tank-gravel .pebble.p8 { left: 1220px; width: 40px; height: 14px; background-color: #4a3a30; }\n\n\t.playing.nochill .tank-glass\n\t{\n\t\tborder-color: #6a2030;\n\t\tbackground-color: #080408;\n\t}\n\n\t.playing.nochill .tank-water\n\t{\n\t\tbackground-color: #100810;\n\t}\n\n\t.playing.nochill .tank-depth.d1\n\t{\n\t\tbackground-color: #401018;\n\t\topacity: 0.35;\n\t}\n\n\t.playing.nochill .tank-depth.d2\n\t{\n\t\tbackground-color: #080208;\n\t\topacity: 0.55;\n\t}\n\n\t.playing.nochill .tank-caustic\n\t{\n\t\tbackground-color: #e04040;\n\t\topacity: 0.06;\n\t}\n\n\t.playing.nochill .tank-shine\n\t{\n\t\tbackground-color: #e06070;\n\t\topacity: 0.05;\n\t}\n\n\t.playing.nochill .tank-glass-lip\n\t{\n\t\tbackground-color: #e05060;\n\t\topacity: 0.1;\n\t}\n\n\t.playing.nochill .tank-gravel\n\t{\n\t\tbackground-color: #1a1010;\n\t}\n\n\t.playing.nochill .tank-bubbles .bubble\n\t{\n\t\tborder-color: #e08090;\n\t\tbackground-color: #a04050;\n\t\topacity: 0.22;\n\t}\n\n\t@keyframes tankbubble\n\t{\n\t\t0% { bottom: 36px; opacity: 0.05; }\n\t\t12% { opacity: 0.4; }\n\t\t100% { bottom: 560px; opacity: 0; }\n\t}\n\n\t@keyframes tankcaustic\n\t{\n\t\t0% { top: 64px; opacity: 0.05; }\n\t\t50% { top: 88px; opacity: 0.12; }\n\t\t100% { top: 64px; opacity: 0.05; }\n\t}\n\n\t// Habitat prop overlay (flavor silhouettes)\n\t.hab-prop\n\t{\n\t\tposition: absolute;\n\t\tpointer-events: none;\n\t\tz-index: 1;\n\t\topacity: 0;\n\t}\n\n\t// ----- HABITAT THEMES -----\n\t.tank-glass.hab-glass .tank-water\n\t{\n\t\t// default aquarium look\n\t}\n\n\t.tank-glass.hab-deluxe\n\t{\n\t\tborder-color: #6ab0d0;\n\t\tborder-radius: 6px;\n\t}\n\n\t.tank-glass.hab-deluxe .tank-rim\n\t{\n\t\tbackground-color: #4a7898;\n\t\theight: 12px;\n\t}\n\n\t.tank-glass.hab-deluxe .tank-water\n\t{\n\t\tbackground-color: #0c4568;\n\t}\n\n\t.tank-glass.hab-deluxe .hab-prop\n\t{\n\t\topacity: 0.2;\n\t\tright: 24px;\n\t\ttop: 40px;\n\t\twidth: 48px;\n\t\theight: 48px;\n\t\tbackground-color: #80c8e8;\n\t\tborder-radius: 4px;\n\t}\n\n\t// Bathtub \u2014 porcelain pink rim, soapy water\n\t.tank-glass.hab-bathtub\n\t{\n\t\tborder-color: #c8a0b0;\n\t\tborder-radius: 18px;\n\t}\n\n\t.tank-glass.hab-bathtub .tank-rim\n\t{\n\t\tbackground-color: #e8d0d8;\n\t\theight: 14px;\n\t}\n\n\t.tank-glass.hab-bathtub .tank-rim.top\n\t{\n\t\tbackground-color: #f0e0e8;\n\t\tborder-radius: 16px 16px 0 0;\n\t}\n\n\t.tank-glass.hab-bathtub .tank-rim.bottom\n\t{\n\t\tbackground-color: #d8b8c4;\n\t\tborder-radius: 0 0 16px 16px;\n\t}\n\n\t.tank-glass.hab-bathtub .tank-water\n\t{\n\t\tbackground-color: #3a6a88;\n\t}\n\n\t.tank-glass.hab-bathtub .tank-gravel\n\t{\n\t\tbackground-color: #b0a090;\n\t\theight: 18px;\n\t}\n\n\t.tank-glass.hab-bathtub .hab-prop\n\t{\n\t\topacity: 0.55;\n\t\tright: 36px;\n\t\ttop: 28px;\n\t\twidth: 36px;\n\t\theight: 36px;\n\t\tbackground-color: #e85070;\n\t\tborder-radius: 50%;\n\t}\n\n\t// Kiddie pool \u2014 loud plastic blue\n\t.tank-glass.hab-kiddie\n\t{\n\t\tborder-color: #40a0e0;\n\t\tborder-radius: 24px;\n\t}\n\n\t.tank-glass.hab-kiddie .tank-rim\n\t{\n\t\tbackground-color: #58b8f0;\n\t\theight: 16px;\n\t}\n\n\t.tank-glass.hab-kiddie .tank-rim.top\n\t{\n\t\tbackground-color: #70c8f8;\n\t}\n\n\t.tank-glass.hab-kiddie .tank-water\n\t{\n\t\tbackground-color: #1a80b8;\n\t}\n\n\t.tank-glass.hab-kiddie .tank-gravel\n\t{\n\t\tbackground-color: #d0b060;\n\t\theight: 14px;\n\t}\n\n\t.tank-glass.hab-kiddie .hab-prop\n\t{\n\t\topacity: 0.4;\n\t\tleft: 28px;\n\t\ttop: 24px;\n\t\twidth: 50px;\n\t\theight: 20px;\n\t\tbackground-color: #f0d040;\n\t\tborder-radius: 8px;\n\t}\n\n\t// Neighbor pool \u2014 suburban tile green\n\t.tank-glass.hab-neighbor\n\t{\n\t\tborder-color: #2a8a70;\n\t\tborder-radius: 4px;\n\t}\n\n\t.tank-glass.hab-neighbor .tank-rim\n\t{\n\t\tbackground-color: #d0c8b8;\n\t\theight: 12px;\n\t}\n\n\t.tank-glass.hab-neighbor .tank-water\n\t{\n\t\tbackground-color: #0a5a70;\n\t}\n\n\t.tank-glass.hab-neighbor .tank-gravel\n\t{\n\t\tbackground-color: #90a8a0;\n\t\theight: 20px;\n\t}\n\n\t.tank-glass.hab-neighbor .hab-prop\n\t{\n\t\topacity: 0.35;\n\t\tright: 48px;\n\t\tbottom: 48px;\n\t\twidth: 60px;\n\t\theight: 40px;\n\t\tbackground-color: #ffffff;\n\t\tborder-radius: 2px;\n\t}\n\n\t// Sewage duct \u2014 brown-green grime\n\t.tank-glass.hab-sewage\n\t{\n\t\tborder-color: #5a5030;\n\t\tborder-radius: 4px;\n\t}\n\n\t.tank-glass.hab-sewage .tank-rim\n\t{\n\t\tbackground-color: #4a4030;\n\t\theight: 10px;\n\t}\n\n\t.tank-glass.hab-sewage .tank-water\n\t{\n\t\tbackground-color: #2a3820;\n\t}\n\n\t.tank-glass.hab-sewage .tank-gravel\n\t{\n\t\tbackground-color: #3a3420;\n\t\theight: 26px;\n\t}\n\n\t// Storm drain \u2014 gray runoff\n\t.tank-glass.hab-storm\n\t{\n\t\tborder-color: #506070;\n\t\tborder-radius: 2px;\n\t}\n\n\t.tank-glass.hab-storm .tank-rim\n\t{\n\t\tbackground-color: #3a4850;\n\t\theight: 8px;\n\t}\n\n\t.tank-glass.hab-storm .tank-water\n\t{\n\t\tbackground-color: #1a2830;\n\t}\n\n\t.tank-glass.hab-storm .tank-gravel\n\t{\n\t\tbackground-color: #2a3038;\n\t\theight: 18px;\n\t}\n\n\t// Abandoned spa \u2014 lavender chlorine\n\t.tank-glass.hab-spa\n\t{\n\t\tborder-color: #8060a0;\n\t\tborder-radius: 14px;\n\t}\n\n\t.tank-glass.hab-spa .tank-rim\n\t{\n\t\tbackground-color: #c0b0d0;\n\t\theight: 12px;\n\t}\n\n\t.tank-glass.hab-spa .tank-water\n\t{\n\t\tbackground-color: #3a2860;\n\t}\n\n\t.tank-glass.hab-spa .tank-gravel\n\t{\n\t\tbackground-color: #b0a0c0;\n\t\theight: 16px;\n\t}\n\n\t// Community fountain\n\t.tank-glass.hab-fountain\n\t{\n\t\tborder-color: #80a0c0;\n\t\tborder-radius: 50%;\n\t}\n\n\t.tank-glass.hab-fountain .tank-rim\n\t{\n\t\tbackground-color: #a0b8c8;\n\t\theight: 14px;\n\t}\n\n\t.tank-glass.hab-fountain .tank-water\n\t{\n\t\tbackground-color: #2060a0;\n\t}\n\n\t.tank-glass.hab-fountain .tank-gravel\n\t{\n\t\tbackground-color: #90a0b0;\n\t\theight: 20px;\n\t}\n\n\t// Stolen hot tub\n\t.tank-glass.hab-hottub\n\t{\n\t\tborder-color: #c07040;\n\t\tborder-radius: 20px;\n\t}\n\n\t.tank-glass.hab-hottub .tank-rim\n\t{\n\t\tbackground-color: #d09050;\n\t\theight: 14px;\n\t}\n\n\t.tank-glass.hab-hottub .tank-water\n\t{\n\t\tbackground-color: #4080a0;\n\t}\n\n\t.tank-glass.hab-hottub .tank-gravel\n\t{\n\t\tbackground-color: #806050;\n\t\theight: 12px;\n\t}\n\n\t// Lake \u2014 murky natural green\n\t.tank-glass.hab-lake\n\t{\n\t\tborder-color: #3a6040;\n\t\tborder-radius: 8px;\n\t}\n\n\t.tank-glass.hab-lake .tank-rim\n\t{\n\t\tbackground-color: #4a6840;\n\t\theight: 8px;\n\t}\n\n\t.tank-glass.hab-lake .tank-water\n\t{\n\t\tbackground-color: #1a4838;\n\t}\n\n\t.tank-glass.hab-lake .tank-gravel\n\t{\n\t\tbackground-color: #3a5030;\n\t\theight: 32px;\n\t}\n\n\t.tank-glass.hab-lake .tank-shine\n\t{\n\t\tbackground-color: #2a7050;\n\t\topacity: 0.25;\n\t}\n\n\t// Gulf \u2014 warm gulf blue\n\t.tank-glass.hab-gulf\n\t{\n\t\tborder-color: #2080a0;\n\t\tborder-radius: 2px;\n\t}\n\n\t.tank-glass.hab-gulf .tank-rim\n\t{\n\t\tbackground-color: #c8b080;\n\t\theight: 10px;\n\t}\n\n\t.tank-glass.hab-gulf .tank-water\n\t{\n\t\tbackground-color: #0a6888;\n\t}\n\n\t.tank-glass.hab-gulf .tank-gravel\n\t{\n\t\tbackground-color: #c8a868;\n\t\theight: 22px;\n\t}\n\n\t// Pixel fish sprites (Assets/ui/sprites)\n\t.fish.has-sprite\n\t{\n\t\t// body chrome hidden; sprite fills the box\n\t}\n\n\t.fish.has-sprite .stat-spd-trail,\n\t.fish.has-sprite .stat-agi-fin,\n\t.fish.has-sprite .stat-end-plate,\n\t.fish.has-sprite .stat-end-band\n\t{\n\t\tdisplay: none;\n\t}\n\n\t.fish.has-sprite .wd-shiver\n\t{\n\t\tborder: 0;\n\t\tbackground-color: transparent;\n\t}\n\n\t.spritebox\n\t{\n\t\tpointer-events: none;\n\t}\n\n\t.fish-sprite\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpointer-events: none;\n\t\tz-index: 1;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.spritebox.mirror-x\n\t{\n\t\ttransform: scaleX( -1 );\n\t}\n\n\n\n\t.decor-sprite-fill\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpointer-events: none;\n\t}\n\n\t.swatch-sprite\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpointer-events: none;\n\t}\n\n\t.legend-swatch.has-sprite\n\t{\n\t\tposition: relative;\n\t\toverflow: hidden;\n\t\tbackground-color: transparent;\n\t}\n\n\t// Facing flip is on .fish.flip (parent scaleX) \u2014 sprite inherits.\n\n\t.fish.has-sprite .fish-body\n\t{\n\t\topacity: 0;\n\t}\n\n\t.fish.has-sprite.selected\n\t{\n\t\t// Tape prop is the tell \u2014 no hitbox rectangle.\n\t}\n\n\t// Shop / legend mini preview\n\t.legend-swatch.has-sprite\n\t{\n\t\tbackground-repeat: no-repeat;\n\t\tbackground-position: center;\n\t\tbackground-size: contain;\n\t\tborder: 1px solid #2a4858;\n\t}\n\n\t// Cocaine sharks (ocean hazard)\n\t.shark\n\t{\n\t\tposition: absolute;\n\t\tpointer-events: none;\n\t\tz-index: 6;\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t}\n\n\t.shark-sprite\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpointer-events: none;\n\t\tz-index: 1;\n\t}\n\n\t.shark.flip .shark-sprite\n\t{\n\t\ttransform: scaleX( -1 );\n\t}\n\n\t.shark.has-sprite .shark-body,\n\t.shark.has-sprite .shark-fin,\n\t.shark.has-sprite .shark-eye,\n\t.shark.has-sprite .shark-nose\n\t{\n\t\topacity: 0;\n\t}\n\n\t.shark.flip\n\t{\n\t\t// mirror via child transform-like: swap layout by flipping children positions\n\t}\n\n\t.shark-body\n\t{\n\t\tposition: absolute;\n\t\tleft: 8%;\n\t\tright: 12%;\n\t\ttop: 25%;\n\t\tbottom: 20%;\n\t\tbackground-color: #1a2830;\n\t\tborder: 2px solid #0a1018;\n\t\tborder-radius: 40%;\n\t}\n\n\t.shark.flip .shark-body\n\t{\n\t\tleft: 12%;\n\t\tright: 8%;\n\t}\n\n\t.shark-fin\n\t{\n\t\tposition: absolute;\n\t\tleft: 40%;\n\t\ttop: 0;\n\t\twidth: 18%;\n\t\theight: 35%;\n\t\tbackground-color: #243440;\n\t\tborder: 2px solid #0a1018;\n\t}\n\n\t.shark-eye\n\t{\n\t\tposition: absolute;\n\t\tright: 18%;\n\t\ttop: 38%;\n\t\twidth: 10px;\n\t\theight: 10px;\n\t\tbackground-color: #ff2040;\n\t\tborder-radius: 50%;\n\t}\n\n\t.shark.flip .shark-eye\n\t{\n\t\tright: auto;\n\t\tleft: 18%;\n\t}\n\n\t.shark-nose\n\t{\n\t\tposition: absolute;\n\t\tright: 0;\n\t\ttop: 35%;\n\t\twidth: 18%;\n\t\theight: 30%;\n\t\tbackground-color: #152028;\n\t\tborder-radius: 0 50% 50% 0;\n\t}\n\n\t.shark.flip .shark-nose\n\t{\n\t\tright: auto;\n\t\tleft: 0;\n\t\tborder-radius: 50% 0 0 50%;\n\t}\n\n\t.shark-label\n\t{\n\t\tposition: absolute;\n\t\tleft: 28%;\n\t\tbottom: 8%;\n\t\tfont-size: 9px;\n\t\tfont-weight: 800;\n\t\tcolor: #e0e8f0;\n\t\topacity: 0.85;\n\t}\n\n\t.playing.shark-alert .tank-water\n\t{\n\t\t// slight red tint under alert\n\t}\n\n\t.playing.shark-alert .cq-pill-right .cq-pill-sub\n\t{\n\t\tcolor: #ff6060;\n\t}\n\n\t// Ocean \u2014 deep open water\n\t.tank-glass.hab-ocean\n\t{\n\t\tborder-color: #104060;\n\t\tborder-radius: 0;\n\t}\n\n\t.tank-glass.hab-ocean .tank-rim\n\t{\n\t\tbackground-color: #0a2030;\n\t\theight: 6px;\n\t}\n\n\t.tank-glass.hab-ocean .tank-water\n\t{\n\t\tbackground-color: #042848;\n\t}\n\n\t.tank-glass.hab-ocean .tank-gravel\n\t{\n\t\tbackground-color: #1a3040;\n\t\theight: 16px;\n\t}\n\n\t.tank-glass.hab-ocean .tank-shine\n\t{\n\t\topacity: 0.15;\n\t}\n\n\t.tank-glass.hab-ocean .hab-prop\n\t{\n\t\topacity: 0.12;\n\t\tleft: 80px;\n\t\ttop: 80px;\n\t\twidth: 120px;\n\t\theight: 40px;\n\t\tbackground-color: #206080;\n\t\tborder-radius: 20px;\n\t}\n\n\t// ----- DECOR (pixel sprites \u002B CSS fallbacks on gravel) -----\n\t// Sizes come ONLY from inline style (DecorDef.Width/Height) \u2014 never force width here.\n\t.decor\n\t{\n\t\tposition: absolute;\n\t\t// Below fish hit pads (z 20) so swimming fish stay easy to click;\n\t\t// still above gravel / bubbles. Drag raises z while held.\n\t\tpointer-events: all;\n\t\tcursor: grab;\n\t\tz-index: 14;\n\t\toverflow: hidden;\n\t\tflex-direction: column;\n\t\tjustify-content: flex-end;\n\t\talign-items: stretch;\n\t}\n\n\t.decor.dragging\n\t{\n\t\tz-index: 28;\n\t\topacity: 0.95;\n\t\tcursor: grabbing;\n\t}\n\n\t.decor.flying\n\t{\n\t\tz-index: 14;\n\t\tpointer-events: none;\n\t}\n\n\t// While holding a prop, show grab cursor over the play screen.\n\t.playing.decor-holding\n\t{\n\t\tcursor: grabbing;\n\t}\n\n\t.decor.has-sprite\n\t{\n\t\t// Must be non-transparent for hit-testing \u2014 fully transparent panels often drop clicks in S\u0026box.\n\t\tbackground-color: #00000001;\n\t\tborder-width: 0;\n\t\tborder-radius: 0;\n\t}\n\n\t.decor .decor-sprite-fill\n\t{\n\t\tpointer-events: none;\n\t}\n\n\t// Fallback solid blocks only when no sprite (no fixed widths \u2014 use inline size)\n\t.decor-plant\n\t{\n\t\tbackground-color: #2a8a40;\n\t\tborder: 2px solid #145020;\n\t\tborder-radius: 8px 8px 2px 2px;\n\t}\n\n\t.decor-chest\n\t{\n\t\tbackground-color: #b8860b;\n\t\tborder: 2px solid #6a4a08;\n\t\tborder-radius: 3px;\n\t}\n\n\t.decor-bubbler\n\t{\n\t\tbackground-color: #6a90a8;\n\t\tborder: 2px solid #304858;\n\t}\n\n\t.decor-forty\n\t{\n\t\tbackground-color: #3a5a28;\n\t\tborder: 2px solid #1a3010;\n\t\tborder-radius: 4px 4px 2px 2px;\n\t}\n\n\t.decor-cuffs\n\t{\n\t\tbackground-color: #8a8a90;\n\t\tborder: 2px solid #404048;\n\t\tborder-radius: 10px;\n\t}\n\n\t.decor-boot\n\t{\n\t\tbackground-color: #5a3020;\n\t\tborder: 2px solid #301808;\n\t\tborder-radius: 2px 8px 4px 2px;\n\t}\n\n\t.decor-meds\n\t{\n\t\tbackground-color: #e8e0d0;\n\t\tborder: 2px solid #8a8070;\n\t\tborder-radius: 2px;\n\t}\n\n\t.decor-doge\n\t{\n\t\tbackground-color: #ffd24a;\n\t\tborder: 2px solid #a08020;\n\t\tborder-radius: 4px 4px 2px 2px;\n\t}\n\n\t.decor-m80crate\n\t{\n\t\tbackground-color: #8b1a1a;\n\t\tborder: 2px solid #401010;\n\t\tborder-radius: 2px;\n\t}\n\n\t.decor-rock\n\t{\n\t\tbackground-color: #6a6860;\n\t\tborder: 2px solid #3a3830;\n\t\tborder-radius: 40% 50% 30% 40%;\n\t}\n\n\t.decor-castle\n\t{\n\t\tbackground-color: #c0b090;\n\t\tborder: 2px solid #706050;\n\t\tborder-radius: 2px 2px 0 0;\n\t}\n\n\t.decor-anchor\n\t{\n\t\tbackground-color: #505860;\n\t\tborder: 2px solid #282c30;\n\t\tborder-radius: 4px 4px 50% 50%;\n\t}\n\n\t.decor-cone\n\t{\n\t\tbackground-color: #e06020;\n\t\tborder: 2px solid #803010;\n\t\tborder-radius: 2px 2px 8px 8px;\n\t}\n\n\t.decor-lava\n\t{\n\t\tbackground-color: #9020a0;\n\t\tborder: 2px solid #401050;\n\t\tborder-radius: 12px 12px 6px 6px;\n\t}\n\n\t.decor-pizza\n\t{\n\t\tbackground-color: #d0a040;\n\t\tborder: 2px solid #806020;\n\t\tborder-radius: 2px;\n\t}\n\n\t.decor-trophy\n\t{\n\t\tbackground-color: #e0c040;\n\t\tborder: 2px solid #806020;\n\t\tborder-radius: 4px 4px 2px 2px;\n\t}\n\n\t.decor-coral\n\t{\n\t\tbackground-color: #e06090;\n\t\tborder: 2px solid #803050;\n\t\tborder-radius: 30% 40% 20% 50%;\n\t}\n\n\t.decor.has-sprite.decor-plant,\n\t.decor.has-sprite.decor-chest,\n\t.decor.has-sprite.decor-bubbler,\n\t.decor.has-sprite.decor-forty,\n\t.decor.has-sprite.decor-cuffs,\n\t.decor.has-sprite.decor-boot,\n\t.decor.has-sprite.decor-meds,\n\t.decor.has-sprite.decor-doge,\n\t.decor.has-sprite.decor-m80crate\n\t{\n\t\tbackground-color: transparent;\n\t\tborder-width: 0;\n\t}\n\n\t.decor-sprite-fill\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpointer-events: none;\n\t}\n\n\t.legend-swatch.has-sprite\n\t{\n\t\tbackground-repeat: no-repeat;\n\t\tbackground-position: center;\n\t\tbackground-size: 100% 100%;\n\t\tbackground-color: transparent;\n\t}\n\n\t.fish\n\t{\n\t\tposition: absolute;\n\t\t// Fat invisible pad around the drawn body \u2014 young/small fish stay clickable.\n\t\tpointer-events: all;\n\t\tcursor: pointer;\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\t// Above decor so gravel props don\u0027t steal mid-tank clicks.\n\t\tz-index: 20;\n\t}\n\n\t// Drawn fish (sprite/body) sits centered inside the hit pad.\n\t.fish-vis\n\t{\n\t\tposition: absolute;\n\t\tpointer-events: none;\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\toverflow: visible;\n\t}\n\n\t.fish .fish-sprite,\n\t.fish .fish-body,\n\t.fish .m80-prop,\n\t.fish .monster-glitch,\n\t.fish .monster-aura,\n\t.fish .meth-aura,\n\t.fish .stoned-aura\n\t{\n\t\tpointer-events: none;\n\t}\n\n\t.fish.menu-on\n\t{\n\t\tz-index: 48;\n\t}\n\n\t// Parent flip still used for non-sprite bodies / M80 prop layout.\n\t// Textured sprites flip via SpriteBox.Mirror (scaleX on the panel that owns BackgroundImage).\n\t.fish.flip\n\t{\n\t\ttransform: scaleX( -1 );\n\t}\n\n\t// Sprite fish: don\u0027t rely on parent flip for the texture (BackgroundImage can ignore it).\n\t.fish.has-sprite.flip\n\t{\n\t\ttransform: none;\n\t}\n\n\t.fish.selected .fish-body\n\t{\n\t\tborder: 2px solid #ffd24a;\n\t}\n\n\t// ----- M80 taped onto fish -----\n\t.m80-prop\n\t{\n\t\tposition: absolute;\n\t\t// Sit on the fish midsection (fish local space; flip parent mirrors it).\n\t\tleft: 35%;\n\t\ttop: -6px;\n\t\twidth: 22px;\n\t\theight: 28px;\n\t\tpointer-events: none;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tz-index: 5;\n\t}\n\n\t.fish.just-taped .m80-prop\n\t{\n\t\t// Slap-on beat when tape SFX fires.\n\t\tanimation-name: m80slap;\n\t\tanimation-duration: 0.35s;\n\t\tanimation-timing-function: ease-out;\n\t\tanimation-iteration-count: 1;\n\t}\n\n\t.m80-tape\n\t{\n\t\tposition: absolute;\n\t\tleft: -2px;\n\t\tright: -2px;\n\t\ttop: 12px;\n\t\theight: 5px;\n\t\tbackground-color: #d4c48a;\n\t\tborder: 1px solid #8a7840;\n\t\tz-index: 3;\n\t\topacity: 0.95;\n\t}\n\n\t.m80-stick\n\t{\n\t\tposition: relative;\n\t\twidth: 12px;\n\t\theight: 22px;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tz-index: 2;\n\t}\n\n\t.m80-cap\n\t{\n\t\twidth: 12px;\n\t\theight: 4px;\n\t\tbackground-color: #2a2a2a;\n\t\tborder: 1px solid #101010;\n\t}\n\n\t.m80-body\n\t{\n\t\twidth: 12px;\n\t\theight: 16px;\n\t\tbackground-color: #c41e1e;\n\t\tborder: 1px solid #6a1010;\n\t\tborder-top: none;\n\t}\n\n\t.m80-label\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 8px;\n\t\tfont-size: 5px;\n\t\tfont-weight: 800;\n\t\tcolor: #ffd24a;\n\t\ttext-align: center;\n\t\tletter-spacing: 0px;\n\t}\n\n\t.m80-fuse\n\t{\n\t\tposition: absolute;\n\t\ttop: -6px;\n\t\tleft: 9px;\n\t\twidth: 2px;\n\t\theight: 8px;\n\t\tbackground-color: #5a4030;\n\t\tz-index: 4;\n\t}\n\n\t.m80-spark\n\t{\n\t\tposition: absolute;\n\t\ttop: -9px;\n\t\tleft: 7px;\n\t\twidth: 5px;\n\t\theight: 5px;\n\t\tbackground-color: #ff9020;\n\t\tborder-radius: 50%;\n\t\tz-index: 5;\n\t\tanimation-name: m80spark;\n\t\tanimation-duration: 0.25s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-timing-function: steps( 2, end );\n\t}\n\n\t.tank-glass.m80-mode\n\t{\n\t\tborder-color: #c41e1e;\n\t}\n\n\t.water-drop\n\t{\n\t\tposition: absolute;\n\t\tpointer-events: none;\n\t\tz-index: 26;\n\t}\n\n\t.water-drop.jet\n\t{\n\t\tz-index: 27;\n\t}\n\n\t.water-drop-spr\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.boom-bit\n\t{\n\t\tposition: absolute;\n\t\twidth: 7px;\n\t\theight: 7px;\n\t\tpointer-events: none;\n\t\tborder-radius: 1px;\n\t\tz-index: 6;\n\t}\n\n\t.px-spark\n\t{\n\t\tposition: absolute;\n\t\tpointer-events: none;\n\t\tz-index: 16;\n\t\tborder-radius: 0;\n\t}\n\n\t.px-spark.shop\n\t{\n\t\tz-index: 12;\n\t}\n\n\t.fish-body\n\t{\n\t\tposition: relative;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tborder-radius: 40px;\n\t\toverflow: hidden;\n\t\tborder: 1px solid #00000055;\n\t}\n\n\t.fish-fin\n\t{\n\t\tposition: absolute;\n\t\tleft: -6px;\n\t\ttop: 20%;\n\t\twidth: 12px;\n\t\theight: 60%;\n\t\tborder-radius: 4px;\n\t\topacity: 0.9;\n\t}\n\n\t// ----- Trained stats: readable in-tank tells -----\n\t// SPD \u2014 motion streaks trailing behind the swim direction (left of body when facing right).\n\t.stat-spd-trail\n\t{\n\t\tposition: absolute;\n\t\tpointer-events: none;\n\t\tz-index: 2;\n\t\tborder-radius: 2px;\n\t\tbackground-color: #80d0ff;\n\t\topacity: 0.35;\n\t}\n\n\t.stat-spd-trail.t1\n\t{\n\t\tleft: -10px;\n\t\ttop: 42%;\n\t\twidth: 12px;\n\t\theight: 3px;\n\t\topacity: 0.4;\n\t}\n\n\t.stat-spd-trail.t2\n\t{\n\t\tleft: -18px;\n\t\ttop: 48%;\n\t\twidth: 16px;\n\t\theight: 2px;\n\t\topacity: 0.3;\n\t\tbackground-color: #a8e0ff;\n\t}\n\n\t.stat-spd-trail.t3\n\t{\n\t\tleft: -26px;\n\t\ttop: 36%;\n\t\twidth: 20px;\n\t\theight: 2px;\n\t\topacity: 0.22;\n\t\tbackground-color: #c0ecff;\n\t\tanimation-name: spdtrail;\n\t\tanimation-duration: 0.35s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-timing-function: ease-in-out;\n\t\tanimation-direction: alternate;\n\t}\n\n\t.fish.spd-2 .stat-spd-trail.t1,\n\t.fish.spd-3 .stat-spd-trail.t1\n\t{\n\t\twidth: 14px;\n\t\topacity: 0.5;\n\t\tbox-shadow: 0 0 4px #60c0ff;\n\t}\n\n\t.fish.spd-3 .fish-body,\n\t.fish.spd-3.has-sprite .fish-sprite\n\t{\n\t\t// Slight cyan edge \u2014 torpedo look\n\t\tbox-shadow: -2px 0 0 #40a8e088;\n\t}\n\n\t// AGI \u2014 extra fin flares that flutter faster at higher tiers.\n\t.stat-agi-fin\n\t{\n\t\tposition: absolute;\n\t\tpointer-events: none;\n\t\tz-index: 3;\n\t\tbackground-color: #e8b050;\n\t\topacity: 0.55;\n\t\tborder-radius: 3px;\n\t}\n\n\t.stat-agi-fin.top\n\t{\n\t\tright: 28%;\n\t\ttop: -4px;\n\t\twidth: 10px;\n\t\theight: 8px;\n\t\ttransform: rotate( -25deg );\n\t}\n\n\t.stat-agi-fin.bot\n\t{\n\t\tright: 28%;\n\t\tbottom: -4px;\n\t\twidth: 10px;\n\t\theight: 8px;\n\t\ttransform: rotate( 25deg );\n\t}\n\n\t.stat-agi-fin.mid\n\t{\n\t\tleft: -8px;\n\t\ttop: 30%;\n\t\twidth: 8px;\n\t\theight: 40%;\n\t\topacity: 0.45;\n\t\tbackground-color: #f0c060;\n\t}\n\n\t.fish.agi-2 .stat-agi-fin,\n\t.fish.agi-3 .stat-agi-fin\n\t{\n\t\tanimation-name: agiflick;\n\t\tanimation-duration: 0.28s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-timing-function: ease-in-out;\n\t\tanimation-direction: alternate;\n\t}\n\n\t.fish.agi-3 .stat-agi-fin\n\t{\n\t\tanimation-duration: 0.16s;\n\t\topacity: 0.75;\n\t\tbox-shadow: 0 0 4px #ffc04088;\n\t}\n\n\t.fish.agi-2:not( .has-sprite ) .fish-fin,\n\t.fish.agi-3:not( .has-sprite ) .fish-fin\n\t{\n\t\twidth: 16px;\n\t\tleft: -10px;\n\t}\n\n\t// END \u2014 plate bulk / armored midsection.\n\t.stat-end-plate\n\t{\n\t\tposition: absolute;\n\t\tleft: 18%;\n\t\tright: 22%;\n\t\ttop: 28%;\n\t\tbottom: 28%;\n\t\tpointer-events: none;\n\t\tz-index: 2;\n\t\tborder: 2px solid #40a060aa;\n\t\tborder-radius: 40%;\n\t\topacity: 0.55;\n\t}\n\n\t.stat-end-band\n\t{\n\t\tposition: absolute;\n\t\tleft: 22%;\n\t\tright: 26%;\n\t\ttop: 48%;\n\t\theight: 4px;\n\t\tpointer-events: none;\n\t\tz-index: 3;\n\t\tbackground-color: #50c87888;\n\t\tborder-radius: 2px;\n\t}\n\n\t.fish.end-2 .stat-end-plate,\n\t.fish.end-3 .stat-end-plate\n\t{\n\t\tborder-width: 3px;\n\t\topacity: 0.7;\n\t}\n\n\t.fish.end-3 .stat-end-plate\n\t{\n\t\tborder-color: #60e090;\n\t\tbox-shadow: inset 0 0 6px #40a06055;\n\t}\n\n\t.fish.end-2:not( .has-sprite ) .fish-body,\n\t.fish.end-3:not( .has-sprite ) .fish-body\n\t{\n\t\tborder: 2px solid #30805088;\n\t}\n\n\t@keyframes spdtrail\n\t{\n\t\tfrom { opacity: 0.12; left: -22px; }\n\t\tto { opacity: 0.35; left: -28px; }\n\t}\n\n\t@keyframes agiflick\n\t{\n\t\tfrom { opacity: 0.35; }\n\t\tto { opacity: 0.8; }\n\t}\n\n\t.fish-eye\n\t{\n\t\tposition: absolute;\n\t\tright: 18%;\n\t\ttop: 28%;\n\t\twidth: 5px;\n\t\theight: 5px;\n\t\tborder-radius: 50%;\n\t\tbackground-color: #101018;\n\t}\n\n\t.fish-belly\n\t{\n\t\tposition: absolute;\n\t\tleft: 20%;\n\t\tright: 20%;\n\t\tbottom: 10%;\n\t\theight: 30%;\n\t\tbackground-color: #ffffff;\n\t\topacity: 0.18;\n\t\tborder-radius: 20px;\n\t}\n\n\t.tank-legend\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tmargin-top: 8px;\n\t\twidth: 1280px;\n\t}\n\n\t.legend-item\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tmargin-left: 12px;\n\t\tmargin-right: 12px;\n\t}\n\n\t.legend-swatch\n\t{\n\t\twidth: 14px;\n\t\theight: 10px;\n\t\tborder-radius: 4px;\n\t\tmargin-right: 6px;\n\t\tborder: 1px solid #00000066;\n\t}\n\n\t.legend-name\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 600;\n\t\tcolor: #8aa8b8;\n\t\tletter-spacing: 1px;\n\t}\n\n\t.tank-actions\n\t{\n\t\tmargin-top: 10px;\n\t\tflex-direction: row;\n\t\tjustify-content: center;\n\t\tflex-wrap: wrap;\n\t\twidth: 1280px;\n\t}\n\n\t.tank-actions .btn\n\t{\n\t\tmin-width: 140px;\n\t\tmargin-bottom: 0;\n\t\tmargin-left: 6px;\n\t\tmargin-right: 6px;\n\t}\n\n\t.pellet\n\t{\n\t\tposition: absolute;\n\t\twidth: 6px;\n\t\theight: 6px;\n\t\tborder-radius: 50%;\n\t\tbackground-color: #e8c060;\n\t\tborder: 1px solid #8a6020;\n\t\tpointer-events: none;\n\t}\n\n\t// Bloodworms / special feed \u2014 tiny worm sprite instead of a dot\n\t.pellet.pellet-sprite\n\t{\n\t\twidth: 22px;\n\t\theight: 14px;\n\t\tborder-radius: 0;\n\t\tbackground-color: transparent;\n\t\tborder: none;\n\t\tmargin-left: -8px;\n\t\tmargin-top: -4px;\n\t}\n\n\t.pellet-sprite-fill\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpointer-events: none;\n\t}\n\n\t// ----- Lot Runs tool (right rail) -----\n\t.lot-run-tool\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tmargin-top: 10px;\n\t\tpadding: 6px 4px;\n\t\tcursor: pointer;\n\t\tborder: 1px solid #5a4830;\n\t\tborder-radius: 8px;\n\t\tbackground-color: #1a1410;\n\t}\n\n\t.lot-run-tool.on\n\t{\n\t\tborder-color: #c09040;\n\t\tbackground-color: #282010;\n\t}\n\n\t.lot-run-tool-icon\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #f0c080;\n\t}\n\n\t.lot-run-tool-label\n\t{\n\t\tfont-size: 8px;\n\t\tfont-weight: 700;\n\t\tcolor: #c0a080;\n\t\tmargin-top: 4px;\n\t}\n\n\t.pipe-crawl\n\t{\n\t\tflex-direction: column;\n\t\twidth: 640px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.pipe-crawl.hurt\n\t{\n\t\tbackground-color: #401018;\n\t}\n\n\t.pipe-hud\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\theight: 26px;\n\t\tmargin-bottom: 6px;\n\t}\n\n\t.pipe-hp\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t}\n\n\t.pipe-pip\n\t{\n\t\twidth: 14px;\n\t\theight: 12px;\n\t\tmargin-right: 4px;\n\t\tbackground-color: #402028;\n\t\tborder: 1px solid #803040;\n\t}\n\n\t.pipe-pip.on\n\t{\n\t\tbackground-color: #e04050;\n\t\tborder-color: #ff8090;\n\t}\n\n\t.pipe-hud-mid\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t}\n\n\t.pipe-room\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 800;\n\t\tcolor: #e8d8b0;\n\t\tmargin-right: 8px;\n\t}\n\n\t.pipe-prog\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tcolor: #90a090;\n\t}\n\n\t.pipe-hud-right\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t}\n\n\t.pipe-combo\n\t{\n\t\tfont-size: 16px;\n\t\tfont-weight: 800;\n\t\tcolor: #ffe060;\n\t\tmargin-right: 10px;\n\t}\n\n\t.pipe-bag\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tcolor: #c8e090;\n\t}\n\n\t.pipe-field\n\t{\n\t\tposition: relative;\n\t\twidth: 640px;\n\t\theight: 196px;\n\t\tbackground-color: #102028;\n\t\tborder: 3px solid #3a5850;\n\t\toverflow: hidden;\n\t\tcursor: pointer;\n\t\tflex-shrink: 0;\n\t}\n\n\t.pipe-crawl.hot .pipe-field\n\t{\n\t\tborder-color: #d0c040;\n\t}\n\n\t.pipe-crawl.boss .pipe-field\n\t{\n\t\tborder-color: #a04030;\n\t}\n\n\t.pipe-field.chomp\n\t{\n\t\tbackground-color: #183040;\n\t}\n\n\t.pipe-ribs\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\tpointer-events: none;\n\t}\n\n\t.pipe-rib\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\theight: 3px;\n\t\tbackground-color: #1a3840;\n\t}\n\n\t.pipe-rib.r1 { top: 28px; }\n\t.pipe-rib.r2 { top: 72px; }\n\t.pipe-rib.r3 { top: 118px; }\n\t.pipe-rib.r4 { top: 162px; }\n\n\t.pipe-flow\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #184858;\n\t\topacity: 0.18;\n\t\tpointer-events: none;\n\t}\n\n\t.pipe-zone\n\t{\n\t\tposition: absolute;\n\t\ttop: 8px;\n\t\tbottom: 8px;\n\t\tbackground-color: #d8c030;\n\t\topacity: 0.22;\n\t\tpointer-events: none;\n\t\tborder-left: 2px solid #f0e070;\n\t\tborder-right: 2px solid #f0e070;\n\t}\n\n\t.pipe-crawl.hot .pipe-zone\n\t{\n\t\topacity: 0.38;\n\t}\n\n\t.pipe-zone-label\n\t{\n\t\tposition: absolute;\n\t\tleft: 168px;\n\t\ttop: 8px;\n\t\tfont-size: 9px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #f0e070;\n\t\tpointer-events: none;\n\t}\n\n\t.pipe-fish\n\t{\n\t\tposition: absolute;\n\t\tleft: 70px;\n\t\ttop: 92px;\n\t\twidth: 64px;\n\t\theight: 44px;\n\t\tpointer-events: none;\n\t\tz-index: 5;\n\t}\n\n\t.pipe-fish.ouch\n\t{\n\t\topacity: 0.45;\n\t}\n\n\t.pipe-fish-spr\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.pipe-thing\n\t{\n\t\tposition: absolute;\n\t\tpointer-events: none;\n\t\tz-index: 4;\n\t}\n\n\t.pipe-thing.pop\n\t{\n\t\topacity: 0.2;\n\t}\n\n\t.pipe-thing-spr\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.pipe-shout\n\t{\n\t\tposition: absolute;\n\t\tleft: 200px;\n\t\ttop: 72px;\n\t\twidth: 220px;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tfont-size: 22px;\n\t\tfont-weight: 800;\n\t\tcolor: #fff4a0;\n\t\tpointer-events: none;\n\t\tz-index: 8;\n\t}\n\n\t.pipe-hint\n\t{\n\t\tmargin-top: 8px;\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tcolor: #a8b090;\n\t}\n\n\t.adv-vignette\n\t{\n\t\tposition: absolute;\n\t\ttop: 50%;\n\t\tmargin-top: -200px;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tz-index: 155;\n\t\tpointer-events: none;\n\t}\n\n\t.adv-vignette.left\n\t{\n\t\tleft: 50%;\n\t\tmargin-left: -572px;\n\t}\n\n\t.adv-vignette.right\n\t{\n\t\tleft: 50%;\n\t\tmargin-left: 372px;\n\t}\n\n\t.adv-vignette-frame\n\t{\n\t\tborder: 3px solid #6a5040;\n\t\tbackground-color: #0c1014;\n\t\toverflow: hidden;\n\t\tflex-shrink: 0;\n\t}\n\n\t.adv-vignette-frame.loc\n\t{\n\t\twidth: 220px;\n\t\theight: 150px;\n\t}\n\n\t.adv-vignette-frame.ev\n\t{\n\t\twidth: 196px;\n\t\theight: 256px;\n\t}\n\n\t.adv-vignette-spr\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.adv-vignette-cap\n\t{\n\t\tmargin-top: 6px;\n\t\tpadding: 3px 8px;\n\t\tbackground-color: #100810ee;\n\t\tborder: 2px solid #504038;\n\t\tcolor: #d8c8a8;\n\t\tfont-size: 9px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 0.4px;\n\t}\n\n\t.adv-vignette.run-school .adv-vignette-frame { border-color: #506080; }\n\t.adv-vignette.run-alley .adv-vignette-frame { border-color: #604050; }\n\t.adv-vignette.run-pool .adv-vignette-frame { border-color: #3080a0; }\n\t.adv-vignette.run-gas .adv-vignette-frame { border-color: #a07030; }\n\t.adv-vignette.run-drain .adv-vignette-frame { border-color: #406050; }\n\t.adv-vignette.run-bingo .adv-vignette-frame { border-color: #904080; }\n\t.adv-vignette.run-toilet .adv-vignette-frame { border-color: #708090; }\n\t.adv-vignette.run-faucet .adv-vignette-frame { border-color: #60a0c0; }\n\n\t.lot-run-panel\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttop: 48%;\n\t\t// panel centered via negative margins (s\u0026box-friendly)\n\t\tmargin-left: -340px;\n\t\tmargin-top: -260px;\n\t\twidth: 680px;\n\t\tmax-height: 560px;\n\t\tflex-direction: column;\n\t\tpadding: 14px 16px;\n\t\tbackground-color: #141018;\n\t\tborder: 3px solid #6a5040;\n\t\tborder-radius: 12px;\n\t\tz-index: 160;\n\t\toverflow: scroll;\n\t\tpointer-events: all;\n\t}\n\n\t.lot-run-panel.run-school\n\t{\n\t\tborder-color: #506080;\n\t\tbackground-color: #121820;\n\t}\n\n\t.lot-run-panel.run-alley\n\t{\n\t\tborder-color: #604050;\n\t\tbackground-color: #181018;\n\t}\n\n\t.lot-run-panel.run-pool\n\t{\n\t\tborder-color: #3080a0;\n\t\tbackground-color: #101820;\n\t}\n\n\t.lot-run-panel.run-gas\n\t{\n\t\tborder-color: #a07030;\n\t\tbackground-color: #1a160e;\n\t}\n\n\t.lot-run-panel.run-drain\n\t{\n\t\tborder-color: #406050;\n\t\tbackground-color: #101814;\n\t}\n\n\t.lot-run-panel.run-bingo\n\t{\n\t\tborder-color: #904080;\n\t\tbackground-color: #181018;\n\t}\n\n\t.lot-run-panel.run-toilet\n\t{\n\t\tborder-color: #708090;\n\t\tbackground-color: #101418;\n\t}\n\n\t.lot-run-panel.run-faucet\n\t{\n\t\tborder-color: #60a0c0;\n\t\tbackground-color: #102028;\n\t}\n\n\t.lot-run-room-hint\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 700;\n\t\tcolor: #a09080;\n\t\tmargin-top: 12px;\n\t}\n\n\t.flush-scene, .faucet-scene\n\t{\n\t\tcursor: pointer;\n\t\tmin-height: 220px;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t}\n\n\t// ----- Adventure stage art (CSS geometry \u2014 no extra sprites) -----\n\t.adv-stage\n\t{\n\t\tposition: relative;\n\t\twidth: 280px;\n\t\theight: 160px;\n\t\tmargin-bottom: 12px;\n\t\tflex-shrink: 0;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\toverflow: hidden;\n\t\tborder-radius: 10px;\n\t\tbackground-color: #0c1014;\n\t\tborder: 1px solid #2a3840;\n\t}\n\n\t.adv-stage-spr\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.flush-stage\n\t{\n\t\tbackground-color: #0e1418;\n\t\tborder-color: #3a5058;\n\t}\n\n\t.faucet-stage\n\t{\n\t\tbackground-color: #0c1820;\n\t\tborder-color: #306080;\n\t}\n\n\t.adv-toilet\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: flex-end;\n\t\twidth: 90px;\n\t\theight: 100px;\n\t}\n\n\t.adv-toilet-tank\n\t{\n\t\twidth: 56px;\n\t\theight: 28px;\n\t\tbackground-color: #d0dce4;\n\t\tborder: 2px solid #8090a0;\n\t\tborder-radius: 4px 4px 2px 2px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.adv-toilet-bowl\n\t{\n\t\tposition: relative;\n\t\twidth: 72px;\n\t\theight: 42px;\n\t\tmargin-top: -2px;\n\t\tbackground-color: #c0d0dc;\n\t\tborder: 2px solid #8090a0;\n\t\tborder-radius: 8px 8px 36% 36%;\n\t\toverflow: hidden;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tflex-shrink: 0;\n\t}\n\n\t.adv-swirl\n\t{\n\t\tposition: absolute;\n\t\tborder: 2px solid #4090b8;\n\t\tborder-radius: 50%;\n\t\topacity: 0.55;\n\t}\n\n\t.adv-swirl.a\n\t{\n\t\twidth: 36px;\n\t\theight: 20px;\n\t\ttop: 10px;\n\t\tanimation-name: adv-spin;\n\t\tanimation-duration: 1.4s;\n\t\tanimation-timing-function: linear;\n\t\tanimation-iteration-count: infinite;\n\t}\n\n\t.adv-swirl.b\n\t{\n\t\twidth: 22px;\n\t\theight: 12px;\n\t\ttop: 16px;\n\t\tborder-color: #70c0e0;\n\t\tanimation-name: adv-spin;\n\t\tanimation-duration: 0.9s;\n\t\tanimation-timing-function: linear;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-direction: reverse;\n\t}\n\n\t.adv-fish-dot\n\t{\n\t\tposition: absolute;\n\t\twidth: 10px;\n\t\theight: 7px;\n\t\tbackground-color: #f0a040;\n\t\tborder-radius: 50%;\n\t\tz-index: 2;\n\t\tanimation-name: adv-fish-swirl;\n\t\tanimation-duration: 1.6s;\n\t\tanimation-timing-function: ease-in-out;\n\t\tanimation-iteration-count: infinite;\n\t}\n\n\t.adv-fish-dot.home\n\t{\n\t\tposition: relative;\n\t\tanimation-name: none;\n\t\ttop: 0;\n\t\tleft: 0;\n\t}\n\n\t.adv-toilet-base\n\t{\n\t\twidth: 40px;\n\t\theight: 12px;\n\t\tbackground-color: #b0c0cc;\n\t\tborder: 2px solid #8090a0;\n\t\tborder-top: 0;\n\t\tborder-radius: 0 0 6px 6px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.adv-pipe-row\n\t{\n\t\tposition: absolute;\n\t\tleft: 12px;\n\t\tright: 12px;\n\t\tbottom: 10px;\n\t\tflex-direction: row;\n\t\tjustify-content: space-between;\n\t\talign-items: center;\n\t}\n\n\t.adv-pipe\n\t{\n\t\twidth: 48px;\n\t\theight: 10px;\n\t\tbackground-color: #3a4848;\n\t\tborder: 1px solid #607070;\n\t\tborder-radius: 4px;\n\t}\n\n\t.adv-pipe.mid\n\t{\n\t\twidth: 64px;\n\t\theight: 8px;\n\t\tbackground-color: #2a3838;\n\t}\n\n\t.adv-sink\n\t{\n\t\tposition: relative;\n\t\twidth: 140px;\n\t\theight: 100px;\n\t\talign-items: center;\n\t}\n\n\t.adv-faucet-neck\n\t{\n\t\tposition: absolute;\n\t\ttop: 8px;\n\t\tleft: 58px;\n\t\twidth: 14px;\n\t\theight: 28px;\n\t\tbackground-color: #90a0a8;\n\t\tborder: 1px solid #607080;\n\t\tborder-radius: 3px;\n\t}\n\n\t.adv-faucet-spout\n\t{\n\t\tposition: absolute;\n\t\ttop: 30px;\n\t\tleft: 50px;\n\t\twidth: 30px;\n\t\theight: 12px;\n\t\tbackground-color: #a0b0b8;\n\t\tborder: 1px solid #607080;\n\t\tborder-radius: 0 6px 6px 0;\n\t}\n\n\t.adv-water-stream\n\t{\n\t\tposition: absolute;\n\t\ttop: 42px;\n\t\tleft: 68px;\n\t\twidth: 4px;\n\t\theight: 28px;\n\t\tbackground-color: #50a0c8;\n\t\topacity: 0.85;\n\t\tanimation-name: adv-drip;\n\t\tanimation-duration: 0.6s;\n\t\tanimation-iteration-count: infinite;\n\t}\n\n\t.adv-basin\n\t{\n\t\tposition: absolute;\n\t\tbottom: 8px;\n\t\tleft: 20px;\n\t\twidth: 100px;\n\t\theight: 36px;\n\t\tbackground-color: #d0dce4;\n\t\tborder: 2px solid #8090a0;\n\t\tborder-radius: 6px;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t}\n\n\t.room-stage\n\t{\n\t\twidth: 100%;\n\t\tmax-width: 420px;\n\t\theight: 72px;\n\t\tflex-direction: row;\n\t\tpadding: 0 16px;\n\t}\n\n\t.room-stage.kind-look { border-color: #4a5860; }\n\t.room-stage.kind-fight { border-color: #a05050; background-color: #181014; }\n\t.room-stage.kind-loot { border-color: #c0a040; background-color: #18160e; }\n\t.room-stage.kind-hazard { border-color: #a07030; background-color: #18140c; }\n\t.room-stage.kind-choice { border-color: #6080a0; }\n\t.room-stage.kind-boss { border-color: #c04060; background-color: #1a1014; }\n\n\t.adv-room-bars\n\t{\n\t\tflex-direction: row;\n\t\talign-items: flex-end;\n\t\tflex-grow: 1;\n\t\theight: 100%;\n\t\tpadding-bottom: 10px;\n\t}\n\n\t.adv-bar\n\t{\n\t\twidth: 18px;\n\t\theight: 28px;\n\t\tmargin-right: 6px;\n\t\tbackground-color: #2a3438;\n\t\tborder: 1px solid #405058;\n\t\tborder-radius: 2px;\n\t}\n\n\t.adv-bar.mid\n\t{\n\t\theight: 44px;\n\t\tbackground-color: #343c40;\n\t}\n\n\t.adv-room-mark\n\t{\n\t\twidth: 8px;\n\t\theight: 8px;\n\t\tborder-radius: 50%;\n\t\tbackground-color: #80c0e0;\n\t\tmargin-right: 12px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.adv-enemy-blob\n\t{\n\t\twidth: 36px;\n\t\theight: 22px;\n\t\tborder-radius: 40%;\n\t\tborder: 2px solid #101018;\n\t\tflex-shrink: 0;\n\t\tanimation-name: adv-threat;\n\t\tanimation-duration: 1.2s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-direction: alternate;\n\t}\n\n\t.adv-loot-coin\n\t{\n\t\twidth: 22px;\n\t\theight: 22px;\n\t\tborder-radius: 50%;\n\t\tbackground-color: #e0c040;\n\t\tborder: 2px solid #a08020;\n\t\tflex-shrink: 0;\n\t}\n\n\t@keyframes adv-spin\n\t{\n\t\t0% { transform: rotate( 0deg ); }\n\t\t100% { transform: rotate( 360deg ); }\n\t}\n\n\t@keyframes adv-fish-swirl\n\t{\n\t\t0% { left: 20px; top: 18px; }\n\t\t50% { left: 40px; top: 12px; }\n\t\t100% { left: 20px; top: 18px; }\n\t}\n\n\t@keyframes adv-drip\n\t{\n\t\t0% { opacity: 0.4; height: 20px; }\n\t\t100% { opacity: 0.95; height: 32px; }\n\t}\n\n\t@keyframes adv-threat\n\t{\n\t\t0% { transform: translate( 0px, 0px ); }\n\t\t100% { transform: translate( -3px, 2px ); }\n\t}\n\n\t.lot-run-prog\n\t{\n\t\tfont-size: 12px;\n\t\tcolor: #c0a880;\n\t}\n\n\t.lot-run-locations\n\t{\n\t\tflex-direction: column;\n\t\twidth: 100%;\n\t\tmargin-top: 8px;\n\t}\n\n\t.lot-run-loc\n\t{\n\t\tflex-direction: row;\n\t\talign-items: stretch;\n\t\tpadding: 0;\n\t\tmargin-bottom: 8px;\n\t\tbackground-color: #1c1820;\n\t\tborder: 1px solid #4a4050;\n\t\tborder-radius: 8px;\n\t\tcursor: pointer;\n\t\toverflow: hidden;\n\t}\n\n\t.lot-run-loc.locked\n\t{\n\t\topacity: 0.55;\n\t}\n\n\t.lot-run-loc.cleared\n\t{\n\t\tborder-color: #60a070;\n\t}\n\n\t.lot-run-loc-art\n\t{\n\t\twidth: 56px;\n\t\tmin-height: 72px;\n\t\tflex-shrink: 0;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tbackground-color: #141018;\n\t\tborder-right: 1px solid #2a2830;\n\t}\n\n\t.lot-run-loc-art.run-toilet { background-color: #101418; }\n\t.lot-run-loc-art.run-school { background-color: #101820; }\n\t.lot-run-loc-art.run-alley { background-color: #181018; }\n\t.lot-run-loc-art.run-pool { background-color: #0c1820; }\n\t.lot-run-loc-art.run-gas { background-color: #18140c; }\n\t.lot-run-loc-art.run-drain { background-color: #0c1410; }\n\t.lot-run-loc-art.run-bingo { background-color: #181018; }\n\n\t.lot-run-loc-glyph\n\t{\n\t\twidth: 22px;\n\t\theight: 22px;\n\t\tborder-radius: 4px;\n\t\tbackground-color: #506070;\n\t\tborder: 2px solid #90a8b0;\n\t}\n\n\t.lot-run-loc-art.run-toilet .lot-run-loc-glyph { border-radius: 50%; background-color: #80a0b0; }\n\t.lot-run-loc-art.run-school .lot-run-loc-glyph { background-color: #6080a0; }\n\t.lot-run-loc-art.run-alley .lot-run-loc-glyph { background-color: #704858; }\n\t.lot-run-loc-art.run-pool .lot-run-loc-glyph { border-radius: 50%; background-color: #40a0c0; }\n\t.lot-run-loc-art.run-gas .lot-run-loc-glyph { background-color: #a08040; }\n\t.lot-run-loc-art.run-drain .lot-run-loc-glyph { background-color: #407050; }\n\t.lot-run-loc-art.run-bingo .lot-run-loc-glyph { background-color: #904878; }\n\n\t.lot-run-loc-text\n\t{\n\t\tflex-direction: column;\n\t\tflex-grow: 1;\n\t\tpadding: 10px 12px;\n\t\tmin-width: 0;\n\t}\n\n\t.lot-run-loc-name\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 800;\n\t\tcolor: #f0e0c0;\n\t}\n\n\t.lot-run-loc-note\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 700;\n\t\tcolor: #a09070;\n\t\tmargin-top: 2px;\n\t\ttext-transform: uppercase;\n\t\tletter-spacing: 0.5px;\n\t}\n\n\t.lot-run-loc-blurb\n\t{\n\t\tfont-size: 11px;\n\t\tcolor: #c8b8a0;\n\t\tmargin-top: 6px;\n\t}\n\n\t.lot-run-loc-meta\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 700;\n\t\tcolor: #80c0a0;\n\t\tmargin-top: 6px;\n\t}\n\n\t.lot-run-room\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t\tpadding: 12px 8px;\n\t}\n\n\t.lot-run-room-kind\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1.5px;\n\t\tcolor: #a08060;\n\t\ttext-transform: uppercase;\n\t\tmargin-bottom: 6px;\n\t}\n\n\t.lot-run-room-title\n\t{\n\t\tfont-size: 16px;\n\t\tfont-weight: 800;\n\t\tcolor: #f8e8d0;\n\t\tmargin-bottom: 8px;\n\t\ttext-align: center;\n\t}\n\n\t.lot-run-room-body\n\t{\n\t\tfont-size: 12px;\n\t\tcolor: #d0c0b0;\n\t\ttext-align: center;\n\t\tmargin-bottom: 14px;\n\t\tmax-width: 520px;\n\t\tline-height: 18px;\n\t}\n\n\t.lot-run-choices\n\t{\n\t\tflex-direction: row;\n\t\tjustify-content: center;\n\t}\n\n\t.lot-run-choices .cq-btn\n\t{\n\t\tmargin-left: 8px;\n\t\tmargin-right: 8px;\n\t}\n\n\t.lot-run-log\n\t{\n\t\tmargin-top: 10px;\n\t\tmax-height: 100px;\n\t}\n\n\t// ----- Pre-tank lot stage (before Grandma gifts the glass) -----\n\t.lot-stage\n\t{\n\t\tposition: relative;\n\t\twidth: 900px;\n\t\theight: 520px;\n\t\tflex-shrink: 0;\n\t\toverflow: hidden;\n\t\tborder: 3px solid #4a3830;\n\t\tborder-radius: 0;\n\t\tbackground-color: #1a1410;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tpointer-events: all;\n\t\tcursor: pointer;\n\t\tz-index: 5;\n\t}\n\n\t.lot-stage-art\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpointer-events: none;\n\t}\n\n\t.lot-stage-fallback\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #2a2018;\n\t}\n\n\t.lot-stage-vignette\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\t// Soft edge darken \u2014 no fancy gradients in sbox panels if limited\n\t\tborder: 24px solid #08060caa;\n\t\tpointer-events: none;\n\t}\n\n\t.lot-stage-caption\n\t{\n\t\tposition: absolute;\n\t\tleft: 16px;\n\t\tbottom: 14px;\n\t\tflex-direction: column;\n\t\talign-items: flex-start;\n\t\tpointer-events: none;\n\t}\n\n\t.lot-stage-title\n\t{\n\t\tfont-size: 14px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 2px;\n\t\tcolor: #f0d8a0;\n\t}\n\n\t.lot-stage-sub\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 600;\n\t\tcolor: #c0a080;\n\t\tmargin-top: 4px;\n\t}\n\n\t.lot-stage-go\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 700;\n\t\tcolor: #ffd24a;\n\t\tmargin-top: 8px;\n\t\tletter-spacing: 0.5px;\n\t}\n\n\t.inv-panel-ghost\n\t{\n\t\topacity: 0;\n\t\tpointer-events: none;\n\t}\n\n\t// ----- Grandma RPG prologue (new game only) -----\n\t.grandma-prologue\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tz-index: 200;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: flex-end;\n\t\tpadding-bottom: 48px;\n\t\tbackground-color: #0a0814ee;\n\t\tpointer-events: all;\n\t\tcursor: pointer;\n\t}\n\n\t// ----- Pre-game inner monologue over the trailer lot -----\n\t.mono-lot\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tz-index: 220;\n\t\toverflow: hidden;\n\t\tpointer-events: all;\n\t\tcursor: pointer;\n\t\tbackground-color: #0a0604;\n\t\talign-items: center;\n\t\tjustify-content: flex-end;\n\t\tflex-direction: column;\n\t}\n\n\t.mono-lot-pan\n\t{\n\t\tposition: absolute;\n\t\tleft: -8%;\n\t\ttop: -6%;\n\t\twidth: 116%;\n\t\theight: 116%;\n\t\tanimation-name: monolot-pan;\n\t\tanimation-duration: 26s;\n\t\tanimation-timing-function: ease-in-out;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-direction: alternate;\n\t\tpointer-events: none;\n\t}\n\n\t.mono-lot-art\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t}\n\n\t.mono-lot-fallback\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #2a1c14;\n\t}\n\n\t.mono-lot-shade\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\theight: 42%;\n\t\tbackground-color: #080604dd;\n\t\tpointer-events: none;\n\t}\n\n\t.mono-lot-place\n\t{\n\t\tposition: absolute;\n\t\ttop: 36px;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tpointer-events: none;\n\t}\n\n\t.mono-lot-place-name\n\t{\n\t\tfont-size: 16px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 3px;\n\t\tcolor: #f0d8a0;\n\t}\n\n\t.mono-lot-place-sub\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 600;\n\t\tletter-spacing: 1.5px;\n\t\tcolor: #a88868;\n\t\tmargin-top: 6px;\n\t}\n\n\t.mono-thought\n\t{\n\t\tposition: relative;\n\t\tz-index: 2;\n\t\twidth: 820px;\n\t\tmax-width: 90%;\n\t\tmargin-bottom: 56px;\n\t\tpadding: 18px 22px 14px 22px;\n\t\tflex-direction: column;\n\t\talign-items: stretch;\n\t\tbackground-color: #120e0cee;\n\t\tborder: 1px solid #6a5040;\n\t\tborder-radius: 10px;\n\t\tpointer-events: none;\n\t}\n\n\t.mono-thought-label\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1.5px;\n\t\tcolor: #c09060;\n\t\tmargin-bottom: 10px;\n\t}\n\n\t.mono-thought-text\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 600;\n\t\tline-height: 22px;\n\t\tcolor: #f8e8d0;\n\t\t// Thought-y feel without true italics if font lacks them\n\t\tletter-spacing: 0.3px;\n\t\tmin-height: 44px;\n\t}\n\n\t.mono-thought-foot\n\t{\n\t\tflex-direction: row;\n\t\tjustify-content: space-between;\n\t\talign-items: center;\n\t\tmargin-top: 14px;\n\t}\n\n\t.mono-thought-prog\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 700;\n\t\tcolor: #806050;\n\t}\n\n\t.mono-thought-hint\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 600;\n\t\tcolor: #a88868;\n\t}\n\n\t.playing.looking-around .play-bezel\n\t{\n\t\t// Dim chrome behind the monologue\n\t\tbackground-color: #080604;\n\t}\n\n\t@keyframes monolot-pan\n\t{\n\t\t0%\n\t\t{\n\t\t\ttransform: translate( 0px, 0px );\n\t\t}\n\t\t100%\n\t\t{\n\t\t\t// Slow drift \u2014 \u201Clooking around\u201D the lot\n\t\t\ttransform: translate( -36px, -18px );\n\t\t}\n\t}\n\n\t// Soft porch-shift while peeping through the glass\n\t@keyframes janitor-peep\n\t{\n\t\t0%\n\t\t{\n\t\t\ttransform: translate( 0px, 2px );\n\t\t}\n\t\t100%\n\t\t{\n\t\t\ttransform: translate( 3px, -3px );\n\t\t}\n\t}\n\n\t@keyframes janitor-cameo-in\n\t{\n\t\t0% { opacity: 0; right: -40px; }\n\t\t100% { opacity: 1; right: 18px; }\n\t}\n\n\t@keyframes janitor-cameo-out\n\t{\n\t\t0% { opacity: 1; right: 18px; }\n\t\t100% { opacity: 0; right: -48px; }\n\t}\n\n\t.janitor-cameo\n\t{\n\t\tposition: absolute;\n\t\tright: 18px;\n\t\tbottom: 72px;\n\t\tflex-direction: row;\n\t\talign-items: flex-end;\n\t\tz-index: 40;\n\t\tpointer-events: all;\n\t\tcursor: pointer;\n\t}\n\n\t.janitor-cameo.offer .janitor-cameo-sill\n\t{\n\t\tcolor: #e8d060;\n\t}\n\n\t.janitor-cameo.in\n\t{\n\t\tanimation-name: janitor-cameo-in;\n\t\tanimation-duration: 0.35s;\n\t\tanimation-timing-function: ease-out;\n\t}\n\n\t.janitor-cameo.out\n\t{\n\t\tanimation-name: janitor-cameo-out;\n\t\tanimation-duration: 0.4s;\n\t\tanimation-timing-function: ease-in;\n\t\topacity: 0;\n\t\tpointer-events: none;\n\t\tcursor: default;\n\t}\n\n\t.janitor-cameo-window\n\t{\n\t\twidth: 88px;\n\t\theight: 104px;\n\t\tflex-direction: column;\n\t\tbackground-color: #2a2018;\n\t\tborder: 3px solid #6a5040;\n\t\tflex-shrink: 0;\n\t}\n\n\t.janitor-cameo-pane\n\t{\n\t\tposition: relative;\n\t\twidth: 100%;\n\t\tflex-grow: 1;\n\t\tbackground-color: #1a3040;\n\t\toverflow: hidden;\n\t}\n\n\t.janitor-cameo-face\n\t{\n\t\tposition: absolute;\n\t\tleft: 6px;\n\t\ttop: 10px;\n\t\twidth: 70px;\n\t\theight: 70px;\n\t\tanimation-name: janitor-peep;\n\t\tanimation-duration: 0.9s;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-direction: alternate;\n\t\tanimation-timing-function: ease-in-out;\n\t}\n\n\t.janitor-cameo-spr\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.janitor-cameo-face.fallback\n\t{\n\t\tbackground-color: #4a6850;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tcolor: #c8e0c0;\n\t\tfont-size: 22px;\n\t}\n\n\t.janitor-cameo-mullion-v\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\twidth: 2px;\n\t\tmargin-left: -1px;\n\t\tbackground-color: #3a3028;\n\t}\n\n\t.janitor-cameo-mullion-h\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 50%;\n\t\theight: 2px;\n\t\tmargin-top: -1px;\n\t\tbackground-color: #3a3028;\n\t}\n\n\t.janitor-cameo-sill\n\t{\n\t\theight: 16px;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tbackground-color: #4a3830;\n\t\tcolor: #c8b090;\n\t\tfont-size: 7px;\n\t\tletter-spacing: 0.5px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.janitor-cameo-bubble\n\t{\n\t\tflex-direction: column;\n\t\tmax-width: 280px;\n\t\tmargin-left: 8px;\n\t\tpadding: 8px 10px;\n\t\tbackground-color: #141810;\n\t\tborder: 2px solid #6a8050;\n\t}\n\n\t.janitor-cameo-name\n\t{\n\t\tfont-size: 8px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #90b070;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.janitor-cameo-line\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 600;\n\t\tcolor: #e0f0c8;\n\t\tline-height: 15px;\n\t}\n\n\t.grandma-gift-lot\n\t{\n\t\tposition: relative;\n\t\twidth: 280px;\n\t\theight: 150px;\n\t\tmargin-right: 0;\n\t\tflex-shrink: 0;\n\t\tborder: 3px solid #6a5040;\n\t\tborder-radius: 10px;\n\t\toverflow: hidden;\n\t\tbackground-color: #1a1410;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t}\n\n\t.grandma-gift-lot.popup-size\n\t{\n\t\twidth: 300px;\n\t\theight: 160px;\n\t\tmargin-bottom: 10px;\n\t}\n\n\t.grandma-gift-lot-spr\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t}\n\n\t.grandma-gift-lot .grandma-gift-label\n\t{\n\t\tposition: absolute;\n\t\tbottom: 8px;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttext-align: center;\n\t\tfont-size: 10px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #f0d8a0;\n\t\tz-index: 2;\n\t}\n\n\t.grandma-dialogue.you-theme\n\t{\n\t\tborder-color: #806040;\n\t\tbackground-color: #1a1410ee;\n\t}\n\n\t.grandma-dialogue.you-theme .grandma-dialogue-name\n\t{\n\t\tcolor: #f0c080;\n\t}\n\n\t.grandma-item-popup.you-theme\n\t{\n\t\tborder-color: #806040;\n\t}\n\n\t.grandma-portrait-wrap.you-theme\n\t{\n\t\tborder-color: #a07840;\n\t\tbackground-color: #181410;\n\t}\n\n\t.grandma-prologue-stage\n\t{\n\t\tflex-direction: row;\n\t\talign-items: flex-end;\n\t\tjustify-content: center;\n\t\twidth: 720px;\n\t\tmargin-bottom: 16px;\n\t\tflex-shrink: 0;\n\t}\n\n\t// Window-only stage (no side portrait) \u2014 let the peep own the frame.\n\t.grandma-prologue.scene-fusion .grandma-prologue-stage\n\t{\n\t\twidth: 360px;\n\t\talign-items: center;\n\t}\n\n\t.grandma-gift-tank\n\t{\n\t\tposition: relative;\n\t\twidth: 220px;\n\t\theight: 160px;\n\t\tmargin-right: 28px;\n\t\tflex-shrink: 0;\n\t\tborder: 3px solid #3a7088;\n\t\tborder-radius: 8px;\n\t\tbackground-color: #0c2030;\n\t\toverflow: hidden;\n\t}\n\n\t.grandma-gift-glass\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tborder: 2px solid #6ab0d088;\n\t\tpointer-events: none;\n\t}\n\n\t.grandma-gift-water\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 18px;\n\t\tbottom: 22px;\n\t\tbackground-color: #1a506888;\n\t}\n\n\t.grandma-gift-gravel\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\theight: 22px;\n\t\tbackground-color: #6a4830;\n\t}\n\n\t.grandma-gift-plant\n\t{\n\t\tposition: absolute;\n\t\tleft: 12px;\n\t\tbottom: 18px;\n\t\twidth: 48px;\n\t\theight: 64px;\n\t}\n\n\t.grandma-gift-plant-spr\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t}\n\n\t.grandma-gift-fish\n\t{\n\t\tposition: absolute;\n\t\twidth: 40px;\n\t\theight: 28px;\n\t}\n\n\t.grandma-gift-fish.a\n\t{\n\t\tleft: 90px;\n\t\ttop: 50px;\n\t}\n\n\t.grandma-gift-fish.b\n\t{\n\t\tleft: 140px;\n\t\ttop: 78px;\n\t\t// flip via scale if panel supports transform\n\t\ttransform: scaleX(-1);\n\t}\n\n\t.grandma-gift-fish-spr\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t}\n\n\t.grandma-gift-label\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 4px;\n\t\tfont-size: 10px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #ffd0e0;\n\t\ttext-align: center;\n\t\tjustify-content: center;\n\t}\n\n\t// Mini dogecoin cold wallet prop for wallet gift scene\n\t.grandma-gift-wallet\n\t{\n\t\tposition: relative;\n\t\twidth: 220px;\n\t\theight: 160px;\n\t\tmargin-right: 28px;\n\t\tflex-shrink: 0;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t}\n\n\t.grandma-gift-wallet-shell\n\t{\n\t\twidth: 200px;\n\t\tpadding: 10px 12px 12px 12px;\n\t\tbackground-color: #2c343e;\n\t\tborder: 2px solid #1a1e24;\n\t\tborder-radius: 12px;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tflex-shrink: 0;\n\t}\n\n\t.grandma-gift-wallet-brand\n\t{\n\t\tfont-size: 7px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 0.5px;\n\t\tcolor: #8a929c;\n\t\tmargin-bottom: 6px;\n\t\ttext-align: center;\n\t\tjustify-content: center;\n\t\twidth: 100%;\n\t}\n\n\t.grandma-gift-wallet-lcd\n\t{\n\t\twidth: 176px;\n\t\tpadding: 8px 6px;\n\t\tbackground-color: #1a2010;\n\t\tborder: 2px solid #0c1008;\n\t\tborder-radius: 4px;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tmargin-bottom: 8px;\n\t}\n\n\t.grandma-gift-wallet-bal\n\t{\n\t\tfont-size: 18px;\n\t\tfont-weight: 800;\n\t\tcolor: #90e050;\n\t\tletter-spacing: 1px;\n\t}\n\n\t.grandma-gift-wallet-sub\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 700;\n\t\tcolor: #6a8840;\n\t\tmargin-top: 4px;\n\t\tletter-spacing: 1px;\n\t}\n\n\t.grandma-gift-wallet-leds\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t}\n\n\t.grandma-gift-wallet-btn\n\t{\n\t\twidth: 14px;\n\t\theight: 8px;\n\t\tbackground-color: #1a2028;\n\t\tborder-radius: 2px;\n\t\tmargin-left: 4px;\n\t\tmargin-right: 4px;\n\t}\n\n\t.grandma-gift-wallet-led\n\t{\n\t\twidth: 8px;\n\t\theight: 8px;\n\t\tborder-radius: 50%;\n\t\tbackground-color: #40c060;\n\t\tmargin-left: 8px;\n\t}\n\n\t.grandma-gift-wallet .grandma-gift-label\n\t{\n\t\tposition: relative;\n\t\ttop: 0;\n\t\tmargin-top: 8px;\n\t}\n\n\t.grandma-prologue.scene-wallet\n\t{\n\t\tbackground-color: #0c1018ee;\n\t}\n\n\t.grandma-prologue.scene-fusion\n\t{\n\t\t// Warm trailer interior dusk \u2014 outside peeper energy.\n\t\tbackground-color: #120e0aee;\n\t}\n\n\t// Janitor peeks through the trailer window (dialogue stage).\n\t.grandma-gift-window\n\t{\n\t\tposition: relative;\n\t\twidth: 320px;\n\t\theight: 220px;\n\t\tmargin-right: 12px;\n\t\tflex-shrink: 0;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t}\n\n\t.grandma-window-wall\n\t{\n\t\tposition: relative;\n\t\twidth: 300px;\n\t\theight: 190px;\n\t\tbackground-color: #3a2818;\n\t\tborder: 3px solid #1a1008;\n\t\tborder-radius: 6px;\n\t\toverflow: hidden;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tpadding: 18px 22px 10px 22px;\n\t}\n\n\t// Cheap paneling studs\n\t.grandma-window-stud\n\t{\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\twidth: 10px;\n\t\tbackground-color: #2a1c10;\n\t\tpointer-events: none;\n\t}\n\n\t.grandma-window-stud.a\n\t{\n\t\tleft: 18px;\n\t}\n\n\t.grandma-window-stud.b\n\t{\n\t\tright: 18px;\n\t}\n\n\t.grandma-window-frame\n\t{\n\t\tposition: relative;\n\t\twidth: 200px;\n\t\theight: 130px;\n\t\tbackground-color: #e8d8b8;\n\t\tborder: 4px solid #5a4030;\n\t\tborder-radius: 4px;\n\t\tpadding: 6px;\n\t\tflex-shrink: 0;\n\t\tz-index: 2;\n\t}\n\n\t.grandma-window-pane\n\t{\n\t\tposition: relative;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tbackground-color: #203040;\n\t\tborder: 2px solid #304858;\n\t\toverflow: hidden;\n\t\tflex-shrink: 0;\n\t}\n\n\t.grandma-window-sky\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\t// Dusk lot sky outside\n\t\tbackground-color: #3a5068;\n\t\tpointer-events: none;\n\t}\n\n\t.grandma-window-lot-hint\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\theight: 28%;\n\t\tbackground-color: #2a2018;\n\t\tpointer-events: none;\n\t}\n\n\t// Face pressed against the glass from outside\n\t.grandma-window-peep\n\t{\n\t\tposition: absolute;\n\t\tleft: 18%;\n\t\ttop: 8%;\n\t\twidth: 64%;\n\t\theight: 88%;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tz-index: 2;\n\t\t// Soft bob like he\u0027s shifting on the porch\n\t\tanimation-name: janitor-peep;\n\t\tanimation-duration: 3.2s;\n\t\tanimation-timing-function: ease-in-out;\n\t\tanimation-iteration-count: infinite;\n\t\tanimation-direction: alternate;\n\t}\n\n\t.grandma-window-face\n\t{\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.grandma-window-face-fallback\n\t{\n\t\twidth: 72px;\n\t\theight: 72px;\n\t\tborder-radius: 40px;\n\t\tbackground-color: #304830;\n\t\tborder: 2px solid #80b070;\n\t\tfont-size: 28px;\n\t\tfont-weight: 800;\n\t\tcolor: #c0e8a0;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t}\n\n\t// Window cross bars\n\t.grandma-window-mullion-v\n\t{\n\t\tposition: absolute;\n\t\tleft: 48%;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\twidth: 4px;\n\t\tbackground-color: #c8b898;\n\t\tz-index: 4;\n\t\tpointer-events: none;\n\t}\n\n\t.grandma-window-mullion-h\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 48%;\n\t\theight: 4px;\n\t\tbackground-color: #c8b898;\n\t\tz-index: 4;\n\t\tpointer-events: none;\n\t}\n\n\t.grandma-window-glare\n\t{\n\t\tposition: absolute;\n\t\tleft: 8%;\n\t\ttop: 6%;\n\t\twidth: 22%;\n\t\theight: 36%;\n\t\tbackground-color: #ffffff22;\n\t\tborder-radius: 4px;\n\t\tz-index: 5;\n\t\tpointer-events: none;\n\t}\n\n\t.grandma-window-sill\n\t{\n\t\twidth: 220px;\n\t\theight: 18px;\n\t\tmargin-top: 2px;\n\t\tbackground-color: #6a5040;\n\t\tborder: 2px solid #3a2818;\n\t\tborder-top: 0;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tflex-shrink: 0;\n\t\tz-index: 3;\n\t}\n\n\t.grandma-window-tap\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tcolor: #d0b090;\n\t\tletter-spacing: 0.5px;\n\t}\n\n\t.grandma-gift-window .grandma-gift-label\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 2px;\n\t\tcolor: #c0a080;\n\t\tz-index: 6;\n\t}\n\n\t// Item popup still teaches the combine recipe (after the window bit).\n\t.grandma-gift-fusion\n\t{\n\t\tposition: relative;\n\t\twidth: 240px;\n\t\theight: 140px;\n\t\tmargin-right: 28px;\n\t\tflex-shrink: 0;\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tbackground-color: #142018;\n\t\tborder: 3px solid #5a8060;\n\t\tborder-radius: 12px;\n\t\toverflow: hidden;\n\t}\n\n\t.grandma-gift-fusion.popup-size\n\t{\n\t\tmargin-right: 0;\n\t\twidth: 260px;\n\t\theight: 120px;\n\t}\n\n\t.grandma-fusion-fish\n\t{\n\t\twidth: 44px;\n\t\theight: 32px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.grandma-fusion-fish.left\n\t{\n\t\tmargin-right: 4px;\n\t}\n\n\t.grandma-fusion-fish.right\n\t{\n\t\tmargin-left: 4px;\n\t}\n\n\t.grandma-fusion-plus,\n\t.grandma-fusion-eq\n\t{\n\t\tfont-size: 22px;\n\t\tfont-weight: 800;\n\t\tcolor: #90c890;\n\t\tmargin-left: 4px;\n\t\tmargin-right: 4px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.grandma-fusion-blob\n\t{\n\t\twidth: 40px;\n\t\theight: 36px;\n\t\tborder-radius: 14px;\n\t\tbackground-color: #604080;\n\t\tborder: 2px solid #a070c0;\n\t\tfont-size: 16px;\n\t\tfont-weight: 800;\n\t\tcolor: #f0e0ff;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tflex-shrink: 0;\n\t\tmargin-left: 4px;\n\t}\n\n\t.grandma-gift-fusion .grandma-gift-label\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 4px;\n\t\tcolor: #a0d0a0;\n\t}\n\n\t// Lot janitor dialogue theme\n\t.grandma-dialogue.theme-janitor\n\t{\n\t\tborder-color: #70a060;\n\t\tbackground-color: #0c1410f2;\n\t}\n\n\t.grandma-dialogue.theme-janitor .grandma-dialogue-name\n\t{\n\t\tcolor: #90d070;\n\t}\n\n\t.grandma-dialogue.theme-janitor .grandma-dialogue-hint\n\t{\n\t\tcolor: #80b070;\n\t}\n\n\t.grandma-portrait-wrap.theme-janitor\n\t{\n\t\tborder-color: #70a060;\n\t\tbackground-color: #101810;\n\t}\n\n\t.grandma-item-popup.theme-janitor\n\t{\n\t\tborder-color: #70a060;\n\t}\n\n\t.grandma-item-popup.theme-janitor .grandma-item-kicker\n\t{\n\t\tcolor: #90d070;\n\t}\n\n\t// Adventure Mode intro \u2014 last ladder gift (after fusion \u002B fight)\n\t.grandma-prologue.scene-adventure\n\t{\n\t\tbackground-color: #0a1014ee;\n\t}\n\n\t.grandma-gift-adventure\n\t{\n\t\tposition: relative;\n\t\twidth: 220px;\n\t\theight: 180px;\n\t\tmargin-right: 12px;\n\t\tflex-shrink: 0;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tbackground-color: #101820;\n\t\tborder: 3px solid #5080a0;\n\t\tborder-radius: 12px;\n\t\toverflow: hidden;\n\t}\n\n\t.grandma-gift-adventure.popup-size\n\t{\n\t\tmargin-right: 0;\n\t\twidth: 240px;\n\t\theight: 130px;\n\t}\n\n\t.grandma-toilet\n\t{\n\t\tposition: relative;\n\t\twidth: 100px;\n\t\theight: 120px;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: flex-end;\n\t\tflex-shrink: 0;\n\t}\n\n\t.grandma-toilet.mini\n\t{\n\t\twidth: 72px;\n\t\theight: 88px;\n\t}\n\n\t.grandma-toilet-tank\n\t{\n\t\twidth: 70%;\n\t\theight: 28%;\n\t\tbackground-color: #e8f0f4;\n\t\tborder: 2px solid #90a8b8;\n\t\tborder-radius: 6px 6px 2px 2px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.grandma-toilet-bowl\n\t{\n\t\tposition: relative;\n\t\twidth: 88%;\n\t\theight: 42%;\n\t\tmargin-top: -2px;\n\t\tbackground-color: #d8e4ec;\n\t\tborder: 2px solid #90a8b8;\n\t\tborder-radius: 8px 8px 40% 40%;\n\t\toverflow: hidden;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tflex-shrink: 0;\n\t}\n\n\t.grandma-toilet-water\n\t{\n\t\tposition: absolute;\n\t\tleft: 12%;\n\t\tright: 12%;\n\t\ttop: 28%;\n\t\tbottom: 18%;\n\t\tbackground-color: #40a0c8;\n\t\tborder-radius: 50%;\n\t\topacity: 0.85;\n\t}\n\n\t.grandma-toilet-fish\n\t{\n\t\tposition: absolute;\n\t\twidth: 36px;\n\t\theight: 26px;\n\t\tz-index: 2;\n\t\t// slight swirl vibe\n\t\tanimation-name: toilet-swirl;\n\t\tanimation-duration: 1.8s;\n\t\tanimation-timing-function: ease-in-out;\n\t\tanimation-iteration-count: infinite;\n\t}\n\n\t.grandma-toilet-base\n\t{\n\t\twidth: 55%;\n\t\theight: 14%;\n\t\tbackground-color: #c8d4dc;\n\t\tborder: 2px solid #90a8b8;\n\t\tborder-top: 0;\n\t\tborder-radius: 0 0 8px 8px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.grandma-gift-adventure .grandma-gift-label\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 6px;\n\t\tcolor: #80c0e0;\n\t}\n\n\t@keyframes toilet-swirl\n\t{\n\t\t0%\n\t\t{\n\t\t\ttransform: translate( -2px, 2px ) rotate( -8deg );\n\t\t}\n\t\t50%\n\t\t{\n\t\t\ttransform: translate( 2px, -1px ) rotate( 8deg );\n\t\t}\n\t\t100%\n\t\t{\n\t\t\ttransform: translate( -2px, 2px ) rotate( -8deg );\n\t\t}\n\t}\n\n\t// Lot bully fight challenge\n\t.grandma-prologue.scene-fight\n\t{\n\t\tbackground-color: #140a0aee;\n\t}\n\n\t.grandma-gift-fight\n\t{\n\t\tposition: relative;\n\t\twidth: 200px;\n\t\theight: 140px;\n\t\tmargin-right: 28px;\n\t\tflex-shrink: 0;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tbackground-color: #281018;\n\t\tborder: 3px solid #c04050;\n\t\tborder-radius: 12px;\n\t}\n\n\t.grandma-gift-fight.popup-size\n\t{\n\t\tmargin-right: 0;\n\t\twidth: 220px;\n\t\theight: 120px;\n\t}\n\n\t.grandma-fight-badge\n\t{\n\t\tfont-size: 28px;\n\t\tfont-weight: 900;\n\t\tcolor: #ff6070;\n\t\tletter-spacing: 2px;\n\t}\n\n\t.grandma-fight-name\n\t{\n\t\tfont-size: 20px;\n\t\tfont-weight: 800;\n\t\tcolor: #ffe0e4;\n\t\tmargin-top: 4px;\n\t}\n\n\t.grandma-fight-sub\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tcolor: #c08088;\n\t\tletter-spacing: 1px;\n\t\tmargin-top: 4px;\n\t}\n\n\t.grandma-gift-fight .grandma-gift-label\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 4px;\n\t\tcolor: #ff90a0;\n\t}\n\n\t.grandma-dialogue.theme-bully\n\t{\n\t\tborder-color: #c04050;\n\t\tbackground-color: #180c10f2;\n\t}\n\n\t.grandma-dialogue.theme-bully .grandma-dialogue-name\n\t{\n\t\tcolor: #ff7080;\n\t}\n\n\t.grandma-dialogue.theme-bully .grandma-dialogue-hint\n\t{\n\t\tcolor: #d07080;\n\t}\n\n\t.grandma-portrait-wrap.theme-bully\n\t{\n\t\tborder-color: #c04050;\n\t\tbackground-color: #181014;\n\t}\n\n\t.grandma-item-popup.theme-bully\n\t{\n\t\tborder-color: #c04050;\n\t}\n\n\t.grandma-item-popup.theme-bully .grandma-item-kicker\n\t{\n\t\tcolor: #ff7080;\n\t}\n\n\t// Item GET popup after dialogue\n\t.grandma-item-popup-layer\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tz-index: 201;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tbackground-color: #080610cc;\n\t\tpointer-events: all;\n\t}\n\n\t.grandma-item-popup\n\t{\n\t\twidth: 420px;\n\t\tpadding: 22px 24px 18px 24px;\n\t\tbackground-color: #161020;\n\t\tborder: 3px solid #e0b040;\n\t\tborder-radius: 14px;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tflex-shrink: 0;\n\t}\n\n\t.grandma-item-kicker\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 2px;\n\t\tcolor: #e0b040;\n\t\tmargin-bottom: 6px;\n\t}\n\n\t.grandma-item-title\n\t{\n\t\tfont-size: 22px;\n\t\tfont-weight: 800;\n\t\tcolor: #fff8e8;\n\t\tmargin-bottom: 14px;\n\t\ttext-align: center;\n\t}\n\n\t.grandma-item-body\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 600;\n\t\tcolor: #c8b8d0;\n\t\ttext-align: center;\n\t\tmargin-top: 12px;\n\t\tmargin-bottom: 16px;\n\t\twhite-space: normal;\n\t\twidth: 100%;\n\t}\n\n\t.grandma-gift-tank.popup-size,\n\t.grandma-gift-wallet.popup-size\n\t{\n\t\tmargin-right: 0;\n\t\twidth: 200px;\n\t\theight: 140px;\n\t}\n\n\t.grandma-portrait-wrap\n\t{\n\t\twidth: 160px;\n\t\theight: 160px;\n\t\tflex-shrink: 0;\n\t\tbackground-color: #1a1428;\n\t\tborder: 3px solid #c890b0;\n\t\tborder-radius: 12px;\n\t\toverflow: hidden;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t}\n\n\t.grandma-portrait\n\t{\n\t\twidth: 144px;\n\t\theight: 144px;\n\t\timage-rendering: pixelated;\n\t}\n\n\t.grandma-portrait-fallback\n\t{\n\t\tfont-size: 18px;\n\t\tfont-weight: 800;\n\t\tcolor: #e8c0d0;\n\t}\n\n\t.grandma-dialogue\n\t{\n\t\twidth: 720px;\n\t\tmin-height: 120px;\n\t\tpadding: 14px 18px 12px 18px;\n\t\tbackground-color: #120c1cf2;\n\t\tborder: 3px solid #e0a0c0;\n\t\tborder-radius: 12px;\n\t\tflex-direction: column;\n\t\tflex-shrink: 0;\n\t\tpointer-events: none; // whole prologue is the click target\n\t}\n\n\t.grandma-dialogue-name\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1.5px;\n\t\tcolor: #ffb0d0;\n\t\tmargin-bottom: 8px;\n\t\theight: 16px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.grandma-dialogue-text\n\t{\n\t\tfont-size: 16px;\n\t\tfont-weight: 600;\n\t\tcolor: #f0e8f8;\n\t\tmin-height: 48px;\n\t\tline-height: 22px;\n\t\twhite-space: normal;\n\t\tflex-grow: 1;\n\t}\n\n\t.grandma-dialogue-foot\n\t{\n\t\tflex-direction: row;\n\t\tjustify-content: space-between;\n\t\talign-items: center;\n\t\tmargin-top: 10px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.grandma-dialogue-prog\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tcolor: #806878;\n\t}\n\n\t.grandma-dialogue-hint\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 700;\n\t\tcolor: #c890b0;\n\t\tletter-spacing: 0.5px;\n\t}\n\n\t// ----- Dogecoin explainer -----\n\t// Story beat card \u2014 middle-school arc / tutorial\n\t.story-panel\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttop: 50%;\n\t\tmargin-left: -320px;\n\t\tmargin-top: -240px;\n\t\twidth: 640px;\n\t\tmax-height: 480px;\n\t\tpadding: 18px 20px 14px 20px;\n\t\tbackground-color: #141020;\n\t\tborder: 3px solid #6a48a0;\n\t\tborder-radius: 14px;\n\t\tflex-direction: column;\n\t\tz-index: 82;\n\t\tpointer-events: all;\n\t\toverflow: hidden;\n\t}\n\n\t.story-kicker\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #b898e8;\n\t\ttext-transform: uppercase;\n\t\tmargin-bottom: 8px;\n\t}\n\n\t.story-title\n\t{\n\t\tfont-size: 22px;\n\t\tfont-weight: 800;\n\t\tcolor: #f0e8ff;\n\t\tmargin-bottom: 12px;\n\t}\n\n\t.story-body\n\t{\n\t\tflex-direction: column;\n\t\twidth: 100%;\n\t\toverflow: scroll;\n\t\tflex-grow: 1;\n\t\tmin-height: 0;\n\t\tmargin-bottom: 12px;\n\t}\n\n\t.story-p\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 600;\n\t\tcolor: #c8b8e0;\n\t\tline-height: 20px;\n\t\tmargin-bottom: 10px;\n\t\twidth: 100%;\n\t}\n\n\t.story-objective\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t\tpadding: 10px 12px;\n\t\tmargin-bottom: 12px;\n\t\tbackground-color: #1c1430;\n\t\tborder: 1px solid #5a3a80;\n\t\tborder-radius: 10px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.story-obj-label\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #9070c0;\n\t\ttext-transform: uppercase;\n\t\tmargin-right: 10px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.story-obj-text\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 700;\n\t\tcolor: #e8d8ff;\n\t\tflex-grow: 1;\n\t}\n\n\t.story-actions\n\t{\n\t\tflex-direction: row;\n\t\tjustify-content: flex-end;\n\t\twidth: 100%;\n\t\tflex-shrink: 0;\n\t}\n\n\t.econ-help-panel\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttop: 50%;\n\t\tmargin-left: -300px;\n\t\tmargin-top: -260px;\n\t\twidth: 600px;\n\t\tmax-height: 520px;\n\t\tpadding: 16px 18px 12px 18px;\n\t\tbackground-color: #102838;\n\t\tborder: 3px solid #4a9abc;\n\t\tborder-radius: 14px;\n\t\tflex-direction: column;\n\t\t// Above dim backdrop (70)\n\t\tz-index: 81;\n\t\tpointer-events: all;\n\t\toverflow: hidden;\n\t}\n\n\t.playing.nochill .econ-help-panel\n\t{\n\t\tbackground-color: #1a1018;\n\t\tborder-color: #8b4a5a;\n\t}\n\n\t.econ-help-header\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t\tmargin-bottom: 10px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.econ-help-kicker\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 700;\n\t\tletter-spacing: 1px;\n\t\tcolor: #6a9ab0;\n\t\ttext-transform: uppercase;\n\t\tmargin-right: 10px;\n\t}\n\n\t.econ-help-title\n\t{\n\t\tfont-size: 20px;\n\t\tfont-weight: 800;\n\t\tcolor: #ffd24a;\n\t\tflex-grow: 1;\n\t}\n\n\t.econ-help-scroll\n\t{\n\t\tflex-direction: column;\n\t\twidth: 100%;\n\t\toverflow: scroll;\n\t\tflex-grow: 1;\n\t\tmin-height: 0;\n\t}\n\n\t.econ-help-block\n\t{\n\t\tflex-direction: column;\n\t\twidth: 100%;\n\t\tpadding: 10px 12px;\n\t\tmargin-bottom: 8px;\n\t\tbackground-color: #0a2030;\n\t\tborder: 1px solid #2a5060;\n\t\tborder-radius: 10px;\n\t\tflex-shrink: 0;\n\t}\n\n\t.playing.nochill .econ-help-block\n\t{\n\t\tbackground-color: #140c12;\n\t\tborder-color: #503038;\n\t}\n\n\t.econ-help-h\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 800;\n\t\tcolor: #e8f4ff;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.econ-help-p\n\t{\n\t\tfont-size: 12px;\n\t\tcolor: #a8c0d0;\n\t\tline-height: 17px;\n\t}\n\n\t.econ-help-foot\n\t{\n\t\tmargin-top: 8px;\n\t\tfont-size: 10px;\n\t\tcolor: #6a8898;\n\t\ttext-align: center;\n\t\tflex-shrink: 0;\n\t}\n\n\t// ----- DARK WEB marketplace (shop) -----\n\t// Sibling backdrop \u002B absolute panel (same pattern as battle / inject).\n\t.shop-backdrop\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tbackground-color: #040810;\n\t\topacity: 0.78;\n\t\t// Below modal panels (train/inject/battle/shop use 80\u002B)\n\t\tz-index: 70;\n\t\tpointer-events: all;\n\t}\n\n\t.shop-backdrop.shop-dim\n\t{\n\t\tz-index: 79;\n\t}\n\n\t.shop-backdrop.battle-dim\n\t{\n\t\tz-index: 70;\n\t}\n\n\t// Ensure battle chrome is clickable above the dim\n\t.battle-panel .cq-btn,\n\t.battle-panel .battle-row,\n\t.battle-panel .battle-chip\n\t{\n\t\tpointer-events: all;\n\t}\n\n\t.shop-panel\n\t{\n\t\tposition: absolute;\n\t\tleft: 50%;\n\t\ttop: 50%;\n\t\tmargin-left: -460px;\n\t\tmargin-top: -280px;\n\t\twidth: 920px;\n\t\theight: 560px;\n\t\tpadding: 14px 18px 12px 18px;\n\t\tbackground-color: #0a1418f5;\n\t\tborder: 3px solid #2a6048;\n\t\tborder-radius: 0;\n\t\tflex-direction: column;\n\t\tflex-shrink: 0;\n\t\tz-index: 80;\n\t\toverflow: hidden;\n\t\tpointer-events: all;\n\t}\n\n\t.shop-panel.cq-shop.darkweb\n\t{\n\t\tbackground-color: #0a1410;\n\t\tborder-color: #3a9060;\n\t}\n\n\t.playing.nochill .shop-panel\n\t{\n\t\tbackground-color: #120a10;\n\t\tborder-color: #6a3040;\n\t}\n\n\t.dw-topbar\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\twidth: 100%;\n\t\tflex-shrink: 0;\n\t\tmargin-bottom: 8px;\n\t\tpadding-bottom: 8px;\n\t\tborder-bottom: 1px solid #1a4030;\n\t}\n\n\t.dw-topbar-left\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t}\n\n\t.dw-onion\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #102018;\n\t\tbackground-color: #50c878;\n\t\tpadding: 3px 6px;\n\t\tborder-radius: 4px;\n\t\tmargin-right: 8px;\n\t}\n\n\t.dw-site\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 700;\n\t\tcolor: #90e0b0;\n\t}\n\n\t.dw-topbar-mid\n\t{\n\t\tfont-size: 11px;\n\t\tcolor: #5a8870;\n\t\tletter-spacing: 0.5px;\n\t}\n\n\t.dw-topbar-right\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t}\n\n\t.dw-wallet\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tcolor: #ffd24a;\n\t\tmargin-right: 10px;\n\t\tpadding: 4px 8px;\n\t\tbackground-color: #1a2010;\n\t\tborder: 1px solid #6a8040;\n\t\tborder-radius: 6px;\n\t}\n\n\t.shop-header\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\twidth: 100%;\n\t\tflex-shrink: 0;\n\t\tmargin-bottom: 6px;\n\t}\n\n\t.shop-title\n\t{\n\t\tfont-size: 22px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 2px;\n\t\tcolor: #b8f0d0;\n\t\twhite-space: nowrap;\n\t}\n\n\t.dw-status-pill\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 700;\n\t\tcolor: #70a888;\n\t\tletter-spacing: 0.5px;\n\t\tpadding: 3px 8px;\n\t\tborder: 1px solid #2a5040;\n\t\tborder-radius: 10px;\n\t}\n\n\t.shop-close\n\t{\n\t\tmin-width: 90px;\n\t\tmargin-bottom: 0;\n\t}\n\n\t// Fixed slot so buy success/fail never shoves the list\n\t.shop-msg-slot\n\t{\n\t\twidth: 100%;\n\t\theight: 36px;\n\t\tmin-height: 36px;\n\t\tmax-height: 36px;\n\t\tmargin-bottom: 6px;\n\t\tflex-shrink: 0;\n\t\toverflow: hidden;\n\t}\n\n\t.shop-msg\n\t{\n\t\tfont-size: 12px;\n\t\tcolor: #8aadc0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tflex-shrink: 0;\n\t\toverflow: hidden;\n\t\t// Single-line status; long tips clip instead of wrapping layout\n\t\twhite-space: nowrap;\n\t}\n\n\t.shop-msg.dw-msg\n\t{\n\t\tcolor: #70c898;\n\t\tbackground-color: #0c1c14;\n\t\tborder: 1px solid #1a4030;\n\t\tborder-radius: 6px;\n\t\tpadding: 6px 10px;\n\t\talign-items: center;\n\t}\n\n\t// List \u002B optional checkout overlay share one flex region\n\t.shop-body\n\t{\n\t\tposition: relative;\n\t\tflex-direction: column;\n\t\tflex-grow: 1;\n\t\twidth: 100%;\n\t\tmin-height: 0;\n\t\toverflow: hidden;\n\t}\n\n\t// Escrow / listing modal \u2014 overlay list, don\u0027t push rows\n\t.dw-checkout\n\t{\n\t\tposition: absolute;\n\t\tleft: 8px;\n\t\tright: 8px;\n\t\ttop: 8px;\n\t\tz-index: 5;\n\t\tflex-direction: column;\n\t\twidth: auto;\n\t\tflex-shrink: 0;\n\t\tmargin-bottom: 0;\n\t\tpadding: 12px 14px;\n\t\tbackground-color: #101808f0;\n\t\tborder: 2px solid #c0a040;\n\t\tborder-radius: 10px;\n\t\tpointer-events: all;\n\t}\n\n\t.dw-checkout-kicker\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1.5px;\n\t\tcolor: #c0a040;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.dw-checkout-title\n\t{\n\t\tfont-size: 16px;\n\t\tfont-weight: 800;\n\t\tcolor: #f0f8e8;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.dw-checkout-note\n\t{\n\t\tfont-size: 11px;\n\t\tcolor: #a0b890;\n\t\tmargin-bottom: 8px;\n\t}\n\n\t.dw-checkout-meta\n\t{\n\t\tflex-direction: row;\n\t\tflex-wrap: wrap;\n\t\tmargin-bottom: 8px;\n\t}\n\n\t.dw-checkout-meta span\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 700;\n\t\tcolor: #d0e0c0;\n\t\tbackground-color: #1a2810;\n\t\tborder: 1px solid #3a5030;\n\t\tborder-radius: 4px;\n\t\tpadding: 3px 8px;\n\t\tmargin-right: 6px;\n\t\tmargin-bottom: 4px;\n\t}\n\n\t.dw-checkout-log\n\t{\n\t\tfont-size: 12px;\n\t\tcolor: #80e0a0;\n\t\tmargin-bottom: 10px;\n\t\tmin-height: 18px;\n\t}\n\n\t.dw-checkout-actions\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t}\n\n\t.dw-checkout-actions .cq-btn\n\t{\n\t\tmargin-right: 8px;\n\t\tmargin-bottom: 0;\n\t}\n\n\t.dw-spinner\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 700;\n\t\tcolor: #90c070;\n\t\tmargin-right: 12px;\n\t}\n\n\t.dw-checkout-foot\n\t{\n\t\tfont-size: 9px;\n\t\tcolor: #5a7050;\n\t\tmargin-top: 8px;\n\t\tletter-spacing: 0.5px;\n\t}\n\n\t.shop-list\n\t{\n\t\tflex-direction: column;\n\t\tflex-grow: 1;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\toverflow: scroll;\n\t\t// Keep first glyphs off the clip edge (s\u0026box scroll panels eat ~1 char otherwise).\n\t\tpadding-left: 8px;\n\t\tpadding-right: 10px;\n\t\tpointer-events: all;\n\t}\n\n\t.shop-list.checkout-dim\n\t{\n\t\topacity: 0.35;\n\t\tpointer-events: none;\n\t}\n\n\t.shop-row\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t\tmin-height: 38px;\n\t\tpadding-top: 4px;\n\t\tpadding-bottom: 4px;\n\t\tpadding-left: 4px;\n\t\tpadding-right: 4px;\n\t\tborder-top: 1px solid #1a3028;\n\t\tflex-shrink: 0;\n\t\tpointer-events: all;\n\t\toverflow: visible;\n\t}\n\n\t.shop-row.buyable\n\t{\n\t\tcursor: pointer;\n\t}\n\n\t// Entire row is one hit target \u2014 children must NOT re-fire Buy (double place).\n\t.shop-row.buyable .shop-col,\n\t.shop-row.buyable .cq-btn\n\t{\n\t\tpointer-events: none;\n\t}\n\n\t.shop-row.buyable:hover\n\t{\n\t\tbackground-color: #143028;\n\t}\n\n\t.shop-row.can-buy\n\t{\n\t\tbackground-color: #0e2018;\n\t\tborder-left: 3px solid #40a070;\n\t}\n\n\t.shop-row.can-buy:hover\n\t{\n\t\tbackground-color: #163828;\n\t}\n\n\t.shop-row.cant-buy\n\t{\n\t\topacity: 0.72;\n\t\tborder-left: 3px solid #304050;\n\t}\n\n\t.shop-row.locked\n\t{\n\t\topacity: 0.55;\n\t\tborder-left: 3px solid #2a2838;\n\t}\n\n\t.shop-row.shop-odds-row\n\t{\n\t\tmin-height: 22px;\n\t\topacity: 0.85;\n\t\tborder-top: none;\n\t\tbackground-color: #0c1814;\n\t}\n\n\t.shop-row.shop-odds-row .shop-col\n\t{\n\t\tfont-size: 11px;\n\t\tcolor: #ffd24a;\n\t\tfont-weight: 700;\n\t}\n\n\t.shop-row.header\n\t{\n\t\tcursor: default;\n\t}\n\n\t.shop-row.header\n\t{\n\t\tborder-top: none;\n\t\topacity: 0.6;\n\t\tmin-height: 24px;\n\t}\n\n\t.shop-row.shady\n\t{\n\t\tbackground-color: #141008;\n\t\tborder-top-color: #403018;\n\t}\n\n\t.shop-col\n\t{\n\t\tfont-size: 12px;\n\t\tfont-weight: 600;\n\t\tcolor: #c8d8c8;\n\t\talign-items: center;\n\t\tflex-shrink: 0;\n\t\toverflow: visible;\n\t}\n\n\t// Name: fixed-ish band so decor \u002B shady titles aren\u0027t eaten by rate.\n\t.shop-col.name\n\t{\n\t\twidth: 250px;\n\t\tmin-width: 200px;\n\t\tflex-shrink: 0;\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\twhite-space: nowrap;\n\t\toverflow: visible;\n\t\tpadding-right: 10px;\n\t}\n\n\t// Shop mini preview \u2014 leave room for the label next to it.\n\t.shop-col.name .legend-swatch\n\t{\n\t\twidth: 28px;\n\t\theight: 28px;\n\t\tflex-shrink: 0;\n\t\tmargin-right: 10px;\n\t\tborder-radius: 6px;\n\t}\n\n\t.shop-col.name .legend-swatch.swatch-wide\n\t{\n\t\twidth: 48px;\n\t\theight: 24px;\n\t\tborder-radius: 3px;\n\t}\n\n\t.shop-row.equipped\n\t{\n\t\topacity: 0.72;\n\t}\n\n\t.shop-col.name .shop-swatch-empty\n\t{\n\t\tbackground-color: #122018;\n\t\tborder: 1px solid #1a3830;\n\t\topacity: 0.55;\n\t}\n\n\t// Rate takes remaining width; long shady hints can use it without crushing names.\n\t.shop-col.rate\n\t{\n\t\tflex-grow: 1;\n\t\tflex-shrink: 1;\n\t\twidth: auto;\n\t\tmin-width: 140px;\n\t\tcolor: #70c070;\n\t\twhite-space: nowrap;\n\t\toverflow: visible;\n\t\tpadding-right: 8px;\n\t}\n\n\t.shop-col.cost\n\t{\n\t\twidth: 88px;\n\t\tflex-shrink: 0;\n\t\tcolor: #ffd24a;\n\t\twhite-space: nowrap;\n\t\tjustify-content: flex-end;\n\t\tpadding-right: 8px;\n\t}\n\n\t.shop-col.act\n\t{\n\t\twidth: 96px;\n\t\tflex-shrink: 0;\n\t\tjustify-content: flex-end;\n\t}\n\n\t.shop-col.act .btn\n\t{\n\t\tmargin-bottom: 0;\n\t}\n\n\t.shop-list.fish-packs-list\n\t{\n\t\tpadding-left: 4px;\n\t\tpadding-right: 6px;\n\t\toverflow: hidden;\n\t}\n\n\t.shop-chase-bar\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\twidth: 100%;\n\t\tflex-shrink: 0;\n\t\tpadding: 4px 6px 8px 6px;\n\t\tborder-bottom: 1px solid #1a4030;\n\t\tmargin-bottom: 8px;\n\t}\n\n\t.shop-chase-left\n\t{\n\t\tflex-direction: column;\n\t\tflex-grow: 1;\n\t}\n\n\t.shop-chase-title\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 800;\n\t\tcolor: #ffd24a;\n\t\tletter-spacing: 0.4px;\n\t}\n\n\t.shop-chase-meta\n\t{\n\t\tfont-size: 10px;\n\t\tcolor: #70c898;\n\t\tmargin-top: 2px;\n\t}\n\n\t.pity-meter\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tflex-shrink: 0;\n\t\tmargin-left: 10px;\n\t}\n\n\t.pity-pip\n\t{\n\t\twidth: 10px;\n\t\theight: 10px;\n\t\tmargin-left: 3px;\n\t\tbackground-color: #1a3028;\n\t\tborder: 1px solid #3a5848;\n\t\tborder-radius: 2px;\n\t}\n\n\t.pity-pip.on\n\t{\n\t\tbackground-color: #c0a040;\n\t\tborder-color: #ffd24a;\n\t}\n\n\t.pity-meter.hot .pity-pip.on\n\t{\n\t\tbackground-color: #ffd24a;\n\t\tborder-color: #fff0a0;\n\t\tanimation: pityhot 0.35s linear infinite;\n\t}\n\n\t.pity-word\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #ffd24a;\n\t\tmargin-left: 8px;\n\t}\n\n\t.shop-fish-layout\n\t{\n\t\tflex-direction: row;\n\t\tflex-grow: 1;\n\t\twidth: 100%;\n\t\tmin-height: 0;\n\t\toverflow: hidden;\n\t}\n\n\t.shop-packs\n\t{\n\t\tflex-direction: row;\n\t\tflex-wrap: wrap;\n\t\tflex-grow: 1;\n\t\talign-content: flex-start;\n\t\toverflow: scroll;\n\t\tpadding-right: 6px;\n\t\tmin-width: 0;\n\t}\n\n\t.shop-pack\n\t{\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\twidth: 124px;\n\t\theight: 168px;\n\t\tmargin-right: 8px;\n\t\tmargin-bottom: 8px;\n\t\tpadding: 8px 6px 6px 6px;\n\t\tbackground-color: #0c1814;\n\t\tborder: 2px solid #2a5040;\n\t\tflex-shrink: 0;\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t\toverflow: hidden;\n\t}\n\n\t.shop-pack .shop-pack-art,\n\t.shop-pack .shop-pack-name,\n\t.shop-pack .shop-pack-water,\n\t.shop-pack .shop-pack-buy\n\t{\n\t\tpointer-events: none;\n\t}\n\n\t.shop-pack.can-buy\n\t{\n\t\tborder-color: #40a070;\n\t\tbackground-color: #10241c;\n\t}\n\n\t.shop-pack.can-buy:hover\n\t{\n\t\tborder-color: #70e0a0;\n\t\tbackground-color: #163828;\n\t}\n\n\t.shop-pack.cant-buy\n\t{\n\t\topacity: 0.72;\n\t\tborder-color: #304050;\n\t}\n\n\t.shop-pack.locked\n\t{\n\t\topacity: 0.5;\n\t\tborder-color: #2a2838;\n\t}\n\n\t.shop-pack.mismatch\n\t{\n\t\tborder-color: #a05040;\n\t}\n\n\t.shop-pack-art\n\t{\n\t\twidth: 88px;\n\t\theight: 72px;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tflex-shrink: 0;\n\t\tbackground-color: #081410;\n\t\tborder: 1px solid #1a3830;\n\t\toverflow: hidden;\n\t}\n\n\t.shop-pack-sprite\n\t{\n\t\twidth: 72px;\n\t\theight: 56px;\n\t}\n\n\t.shop-pack-blob\n\t{\n\t\twidth: 40px;\n\t\theight: 24px;\n\t\tborder-radius: 40%;\n\t}\n\n\t.shop-pack-name\n\t{\n\t\tfont-size: 11px;\n\t\tfont-weight: 800;\n\t\tcolor: #d8f0e0;\n\t\tmargin-top: 6px;\n\t\twhite-space: nowrap;\n\t}\n\n\t.shop-pack-water\n\t{\n\t\tfont-size: 8px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #5a8870;\n\t\tmargin-top: 2px;\n\t}\n\n\t.shop-pack-buy\n\t{\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\twidth: 100%;\n\t\tmargin-top: auto;\n\t\tpadding: 4px 6px;\n\t\tbackground-color: #0a2018;\n\t\tborder: 1px solid #2a5840;\n\t}\n\n\t.shop-pack-buy.go\n\t{\n\t\tbackground-color: #184830;\n\t\tborder-color: #40a070;\n\t}\n\n\t.shop-pack-cost\n\t{\n\t\tfont-size: 10px;\n\t\tfont-weight: 800;\n\t\tcolor: #ffd24a;\n\t}\n\n\t.shop-pack-act\n\t{\n\t\tfont-size: 9px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 0.5px;\n\t\tcolor: #90e0b0;\n\t}\n\n\t.shop-reveal-stage\n\t{\n\t\tposition: relative;\n\t\twidth: 210px;\n\t\tflex-shrink: 0;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tpadding-left: 8px;\n\t\tborder-left: 1px solid #1a4030;\n\t\toverflow: hidden;\n\t}\n\n\t.buy-card\n\t{\n\t\tposition: relative;\n\t\twidth: 188px;\n\t\theight: 248px;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tbackground-color: #101808;\n\t\tborder: 3px solid #3a5840;\n\t\toverflow: hidden;\n\t\tcursor: pointer;\n\t\tpointer-events: all;\n\t}\n\n\t.buy-card.idle\n\t{\n\t\topacity: 0.55;\n\t\tcursor: default;\n\t\tborder-color: #243830;\n\t}\n\n\t.buy-card.tease\n\t{\n\t\tborder-color: #e8c050;\n\t\tbackground-color: #181808;\n\t}\n\n\t.buy-card.phase-deal\n\t{\n\t\tanimation: buy-deal 0.1s linear both;\n\t}\n\n\t.buy-card.revealed.rarity-uncommon { border-color: #7ad8b0; }\n\t.buy-card.revealed.rarity-rare { border-color: #7ab8ff; }\n\t.buy-card.revealed.rarity-paintedrare { border-color: #f090e0; }\n\t.buy-card.revealed.rarity-legendary { border-color: #ffd24a; }\n\t.buy-card.revealed.rarity-mythical { border-color: #d0a0ff; }\n\n\t.buy-card.revealed.rarity-legendary .buy-card-art { border-color: #ffd24a; background-color: #201808; }\n\t.buy-card.revealed.rarity-mythical .buy-card-art { border-color: #d0a0ff; background-color: #180820; }\n\t.buy-card.revealed.rarity-paintedrare .buy-card-art { border-color: #f090e0; }\n\n\t.buy-card.spicy\n\t{\n\t\tanimation: buy-spicy 0.18s linear 2;\n\t}\n\n\t.buy-card.jackpot\n\t{\n\t\tborder-width: 4px;\n\t\tanimation: buy-jack 0.22s linear 3;\n\t}\n\n\t.buy-card-flap\n\t{\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\twidth: 50%;\n\t\theight: 100%;\n\t\tbackground-color: #1a2818;\n\t\tborder: 1px solid #3a5040;\n\t\tz-index: 4;\n\t\tpointer-events: none;\n\t}\n\n\t.buy-card-flap.left { left: 0; }\n\t.buy-card-flap.right { right: 0; }\n\n\t.buy-card.phase-unfold .buy-card-flap.left,\n\t.buy-card.fish-on .buy-card-flap.left,\n\t.buy-card.revealed .buy-card-flap.left\n\t{\n\t\tanimation: buy-flap-l 0.12s linear both;\n\t}\n\n\t.buy-card.phase-unfold .buy-card-flap.right,\n\t.buy-card.fish-on .buy-card-flap.right,\n\t.buy-card.revealed .buy-card-flap.right\n\t{\n\t\tanimation: buy-flap-r 0.12s linear both;\n\t}\n\n\t.buy-card.fish-on .buy-card-flap,\n\t.buy-card.revealed .buy-card-flap\n\t{\n\t\topacity: 0;\n\t}\n\n\t.buy-card-back\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tfont-size: 48px;\n\t\tfont-weight: 800;\n\t\tcolor: #3a5848;\n\t\tz-index: 3;\n\t\tpointer-events: none;\n\t}\n\n\t.buy-card-back.idle-q\n\t{\n\t\tfont-size: 42px;\n\t\tcolor: #2a4038;\n\t}\n\n\t.buy-card.fish-on .buy-card-back,\n\t.buy-card.revealed .buy-card-back\n\t{\n\t\topacity: 0;\n\t}\n\n\t.buy-card-face\n\t{\n\t\tposition: relative;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpadding-top: 16px;\n\t\tz-index: 2;\n\t\topacity: 0;\n\t\tpointer-events: none;\n\t}\n\n\t.buy-card.fish-on .buy-card-face,\n\t.buy-card.revealed .buy-card-face\n\t{\n\t\topacity: 1;\n\t}\n\n\t.buy-card-art\n\t{\n\t\twidth: 120px;\n\t\theight: 96px;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tbackground-color: #081410;\n\t\tborder: 1px solid #2a4038;\n\t\toverflow: hidden;\n\t}\n\n\t.buy-card-sprite\n\t{\n\t\twidth: 108px;\n\t\theight: 80px;\n\t}\n\n\t.buy-card-blob\n\t{\n\t\twidth: 56px;\n\t\theight: 32px;\n\t\tborder-radius: 40%;\n\t}\n\n\t.buy-card-name\n\t{\n\t\tfont-size: 13px;\n\t\tfont-weight: 800;\n\t\tcolor: #e8f8ee;\n\t\tmargin-top: 10px;\n\t}\n\n\t.buy-card-stamp\n\t{\n\t\tfont-size: 14px;\n\t\tfont-weight: 800;\n\t\tletter-spacing: 1px;\n\t\tcolor: #8a9a90;\n\t\tmargin-top: 8px;\n\t\topacity: 0;\n\t\ttext-transform: uppercase;\n\t}\n\n\t.buy-card.revealed .buy-card-stamp\n\t{\n\t\topacity: 1;\n\t\tanimation: buy-stamp 0.12s linear both;\n\t}\n\n\t.buy-card.revealed.rarity-uncommon .buy-card-stamp { color: #7ad8b0; }\n\t.buy-card.revealed.rarity-rare .buy-card-stamp { color: #7ab8ff; }\n\t.buy-card.revealed.rarity-paintedrare .buy-card-stamp { color: #f090e0; }\n\t.buy-card.revealed.rarity-legendary .buy-card-stamp { color: #ffd24a; }\n\t.buy-card.revealed.rarity-mythical .buy-card-stamp { color: #d0a0ff; }\n\n\t.buy-card-hint\n\t{\n\t\tposition: absolute;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 8px;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tfont-size: 9px;\n\t\tletter-spacing: 0.5px;\n\t\tcolor: #6a8878;\n\t\tz-index: 5;\n\t\tpointer-events: none;\n\t}\n\n\t.shop-hint\n\t{\n\t\tflex-shrink: 0;\n\t\tmargin-top: 8px;\n\t\tfont-size: 10px;\n\t\tcolor: #5a4a58;\n\t\tletter-spacing: 1px;\n\t\ttext-align: center;\n\t}\n\n\t.shop-hint.dw-hint\n\t{\n\t\tcolor: #4a6858;\n\t}\n}\n\n@keyframes buy-deal\n{\n\t0% { opacity: 0; }\n\t40% { opacity: 1; }\n\t100% { opacity: 1; }\n}\n\n@keyframes buy-flap-l\n{\n\t0% { opacity: 1; }\n\t100% { opacity: 0; }\n}\n\n@keyframes buy-flap-r\n{\n\t0% { opacity: 1; }\n\t100% { opacity: 0; }\n}\n\n@keyframes buy-stamp\n{\n\t0% { opacity: 0; }\n\t60% { opacity: 1; }\n\t100% { opacity: 1; }\n}\n\n@keyframes buy-spicy\n{\n\t0% { border-color: #f090e0; }\n\t50% { border-color: #fff0ff; }\n\t100% { border-color: #f090e0; }\n}\n\n@keyframes buy-jack\n{\n\t0% { border-color: #ffd24a; }\n\t50% { border-color: #fff8d0; }\n\t100% { border-color: #ffd24a; }\n}\n\n@keyframes goldpulse\n{\n\t0% { opacity: 0.55; }\n\t50% { opacity: 1; }\n\t100% { opacity: 0.55; }\n}\n\n@keyframes gba-rainbow\n{\n\t0%, 24% { background-color: #ff4040; }\n\t25%, 49% { background-color: #ffd24a; }\n\t50%, 74% { background-color: #40d070; }\n\t75%, 100% { background-color: #4080ff; }\n}\n\n@keyframes pityhot\n{\n\t0% { background-color: #c0a040; }\n\t50% { background-color: #ffe080; }\n\t100% { background-color: #c0a040; }\n}\n\n@keyframes warnpulse\n{\n\t0%\n\t{\n\t\tborder-color: #3a7a98;\n\t}\n\n\t50%\n\t{\n\t\tborder-color: #e0b040;\n\t}\n\n\t100%\n\t{\n\t\tborder-color: #3a7a98;\n\t}\n}\n\n@keyframes m80slap\n{\n\t0%\n\t{\n\t\t// start slightly above, then land\n\t\ttop: -16px;\n\t\topacity: 0.2;\n\t}\n\n\t60%\n\t{\n\t\ttop: -4px;\n\t\topacity: 1;\n\t}\n\n\t100%\n\t{\n\t\ttop: -6px;\n\t\topacity: 1;\n\t}\n}\n\n@keyframes m80spark\n{\n\t0%\n\t{\n\t\tbackground-color: #ff9020;\n\t\topacity: 1;\n\t}\n\n\t50%\n\t{\n\t\tbackground-color: #ffe060;\n\t\topacity: 0.4;\n\t}\n\n\t100%\n\t{\n\t\tbackground-color: #ff9020;\n\t\topacity: 1;\n\t}\n}\n\n@keyframes boomshock\n{\n\t0%\n\t{\n\t\twidth: 40px;\n\t\theight: 40px;\n\t\tmargin-left: -20px;\n\t\tmargin-top: -20px;\n\t\topacity: 0.95;\n\t\tborder-color: #ffffff;\n\t}\n\n\t100%\n\t{\n\t\twidth: 420px;\n\t\theight: 420px;\n\t\tmargin-left: -210px;\n\t\tmargin-top: -210px;\n\t\topacity: 0;\n\t\tborder-color: #ff4020;\n\t}\n}\n\n@keyframes methpulse\n{\n\t0%\n\t{\n\t\t// slightly larger / tense\n\t\topacity: 1;\n\t}\n\n\t50%\n\t{\n\t\topacity: 0.92;\n\t}\n\n\t100%\n\t{\n\t\topacity: 1;\n\t}\n}\n\n@keyframes methaura\n{\n\t0%\n\t{\n\t\topacity: 0.35;\n\t\tborder-color: #ff40a0;\n\t}\n\n\t50%\n\t{\n\t\topacity: 0.8;\n\t\tborder-color: #ffd0ff;\n\t}\n\n\t100%\n\t{\n\t\topacity: 0.35;\n\t\tborder-color: #ff40a0;\n\t}\n}\n\n@keyframes stonedwobble\n{\n\t0%\n\t{\n\t\topacity: 0.88;\n\t}\n\n\t50%\n\t{\n\t\topacity: 1;\n\t}\n\n\t100%\n\t{\n\t\topacity: 0.88;\n\t}\n}\n\n@keyframes stonedaura\n{\n\t0%\n\t{\n\t\topacity: 0.25;\n\t\tborder-color: #70d050;\n\t}\n\n\t33%\n\t{\n\t\topacity: 0.55;\n\t\tborder-color: #c060e0;\n\t}\n\n\t66%\n\t{\n\t\topacity: 0.4;\n\t\tborder-color: #60c0e0;\n\t}\n\n\t100%\n\t{\n\t\topacity: 0.25;\n\t\tborder-color: #70d050;\n\t}\n}\n\n@keyframes stonedspark\n{\n\t0%\n\t{\n\t\topacity: 0.3;\n\t}\n\n\t50%\n\t{\n\t\topacity: 1;\n\t}\n\n\t100%\n\t{\n\t\topacity: 0.3;\n\t}\n}\n\n@keyframes wdshiver\n{\n\t0%\n\t{\n\t\topacity: 0.25;\n\t}\n\n\t50%\n\t{\n\t\topacity: 0.7;\n\t}\n\n\t100%\n\t{\n\t\topacity: 0.25;\n\t}\n}\n"},{"Ident":"dogecoin.nochillquarium","Path":"AchievementSystem.cs","FileName":"AchievementSystem.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"namespace NoChillquarium;\n\n/// \u003Csummary\u003EOne porch trophy. Hint is what to do; punchline is the unlock toast.\u003C/summary\u003E\npublic sealed class AchievementDef\n{\n\tpublic string Id { get; init; }\n\tpublic string Name { get; init; }\n\t/// \u003Csummary\u003EShown while locked \u2014 points at the next verb.\u003C/summary\u003E\n\tpublic string Hint { get; init; }\n\t/// \u003Csummary\u003EToast line when it pops.\u003C/summary\u003E\n\tpublic string Punch { get; init; }\n}\n\n/// \u003Csummary\u003E\n/// Twenty gameplay trophies. They teach the ladder and celebrate the felonies.\n/// \u003C/summary\u003E\npublic static class AchievementSystem\n{\n\tpublic static IReadOnlyList\u003CAchievementDef\u003E Catalog { get; } = new[]\n\t{\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022feed\u0022,\n\t\t\tName = \u0022Papa Flakes\u0022,\n\t\t\tHint = \u0022Tools \u2192 Feed. They have to eat.\u0022,\n\t\t\tPunch = \u0022They\u0027re eating. Don\u0027t get cocky.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022train\u0022,\n\t\t\tName = \u0022Swim Lessons\u0022,\n\t\t\tHint = \u0022Click a fish \u2192 Train. Sweat first.\u0022,\n\t\t\tPunch = \u0022Nice sweat. Brad\u0027s garage is listening.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022combine\u0022,\n\t\t\tName = \u0022Two Become Wet\u0022,\n\t\t\tHint = \u0022Click a fish \u2192 Combine. Mash two.\u0022,\n\t\t\tPunch = \u0022Ugly little angel. The janitor took notes.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022fight\u0022,\n\t\t\tName = \u0022Garage Receipt\u0022,\n\t\t\tHint = \u0022Fish menu \u2192 Fight. Pay Brad\u0027s entry.\u0022,\n\t\t\tPunch = \u0022You showed up. He laminated it either way.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022win\u0022,\n\t\t\tName = \u0022Brad Cried\u0022,\n\t\t\tHint = \u0022Win a fight in the garage.\u0022,\n\t\t\tPunch = \u0022He cried into a receipt. Fridge magnet pending.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022flush\u0022,\n\t\t\tName = \u0022Municipal Directions\u0022,\n\t\t\tHint = \u0022Click a fish \u2192 Flush. Adventure starts dirty.\u0022,\n\t\t\tPunch = \u0022Down. Down. Down. The pipes said thank you.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022pipes\u0022,\n\t\t\tName = \u0022Adventure Mode\u0022,\n\t\t\tHint = \u0022Clear a crawl. Die = faucet home.\u0022,\n\t\t\tPunch = \u0022You mapped a hole. Grandma is pretending not to know.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022sell\u0022,\n\t\t\tName = \u0022Cash for Wet\u0022,\n\t\t\tHint = \u0022Fish menu \u2192 Sell. Flip a common.\u0022,\n\t\t\tPunch = \u0022Yard sale energy. Very American.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022decor\u0022,\n\t\t\tName = \u0022Tasteful Junk\u0022,\n\t\t\tHint = \u0022Shop \u2192 Decor. Put something in the glass.\u0022,\n\t\t\tPunch = \u0022Interior design. The fish did not consent.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022habitat\u0022,\n\t\t\tName = \u0022Bigger Bowl\u0022,\n\t\t\tHint = \u0022Shop \u2192 Tank. Upgrade the habitat.\u0022,\n\t\t\tPunch = \u0022Bigger glass. Still her porch.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022salt\u0022,\n\t\t\tName = \u0022Second Fridge\u0022,\n\t\t\tHint = \u0022Shop \u2192 Tank \u2192 Salt. Clownfish come free.\u0022,\n\t\t\tPunch = \u0022Two tanks. Wrong water still means supper.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022shady\u0022,\n\t\t\tName = \u0022Science Illegal\u0022,\n\t\t\tHint = \u0022Shop \u2192 Shady. Buy an M80 or a bag.\u0022,\n\t\t\tPunch = \u0022The laptop made a noise. The noise has a mop.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022tape\u0022,\n\t\t\tName = \u0022Duct Tape Solutions\u0022,\n\t\t\tHint = \u0022Tools \u2192 M80 \u2192 click a fish.\u0022,\n\t\t\tPunch = \u0022You taped an M80 to a coworker. HR is the janitor.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022boom\u0022,\n\t\t\tName = \u0022Indoor Fireworks\u0022,\n\t\t\tHint = \u0022Detonate a taped M80.\u0022,\n\t\t\tPunch = \u0022Pop. Quality control. Grandma: \\\u0022That one had potential.\\\u0022\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022furniture\u0022,\n\t\t\tName = \u0022Fish \u002B Furniture\u0022,\n\t\t\tHint = \u0022Combine a fish with a tank prop.\u0022,\n\t\t\tPunch = \u0022That\u0027s a flamingo with gills. That\u0027s a sentence.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022meth\u0022,\n\t\t\tName = \u0022Breaking Bass\u0022,\n\t\t\tHint = \u0022Tools \u2192 Meth. Dose the glass.\u0022,\n\t\t\tPunch = \u0022Wrong water, right snack. Contain the thrash.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022janitor\u0022,\n\t\t\tName = \u0022Folgers Has a Lawyer\u0022,\n\t\t\tHint = \u0022BAD or click the janitor. Offer meth.\u0022,\n\t\t\tPunch = \u0022He refused. Caffeine is his manager.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022kick\u0022,\n\t\t\tName = \u0022Assault Furniture\u0022,\n\t\t\tHint = \u0022Tools \u2192 Kick. The glass remembers.\u0022,\n\t\t\tPunch = \u0022You kicked a living room. The gravel filed a complaint.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022tin\u0022,\n\t\t\tName = \u0022Funeral Funds\u0022,\n\t\t\tHint = \u0022BAD \u2192 Rob the funeral tin.\u0022,\n\t\t\tPunch = \u0022Grandpa\u0027s leftover hash. I mean cash. She can still count.\u0022\n\t\t},\n\t\tnew AchievementDef\n\t\t{\n\t\t\tId = \u0022dead\u0022,\n\t\t\tName = \u0022Still Your Fault\u0022,\n\t\t\tHint = \u0022Let one die. Wrong water works.\u0022,\n\t\t\tPunch = \u0022Supper\u0027s on. Love you. Wipe the glass.\u0022\n\t\t},\n\t};\n\n\tconst float ToastSeconds = 4.2f;\n\n\tstatic readonly HashSet\u003Cstring\u003E _unlocked = new( StringComparer.OrdinalIgnoreCase );\n\tstatic readonly Queue\u003Cstring\u003E _toastQ = new();\n\tstatic string _toastId = \u0022\u0022;\n\tstatic float _toastLeft;\n\tstatic bool _boardOpen;\n\tstatic int _version;\n\n\tpublic static int Version =\u003E _version;\n\tpublic static int UnlockedCount =\u003E _unlocked.Count;\n\tpublic static int Total =\u003E Catalog.Count;\n\tpublic static string ProgressLabel =\u003E UnlockedCount \u002B \u0022/\u0022 \u002B Total;\n\tpublic static bool BoardOpen =\u003E _boardOpen;\n\tpublic static bool ToastVisible =\u003E _toastLeft \u003E 0f \u0026\u0026 !string.IsNullOrEmpty( _toastId );\n\tpublic static AchievementDef ToastDef =\u003E Find( _toastId );\n\n\tpublic static bool IsUnlocked( string id ) =\u003E\n\t\t!string.IsNullOrEmpty( id ) \u0026\u0026 _unlocked.Contains( id );\n\n\tpublic static AchievementDef Find( string id )\n\t{\n\t\tif ( string.IsNullOrEmpty( id ) )\n\t\t\treturn null;\n\t\tforeach ( var d in Catalog )\n\t\t{\n\t\t\tif ( string.Equals( d.Id, id, StringComparison.OrdinalIgnoreCase ) )\n\t\t\t\treturn d;\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic static void ResetForNewGame()\n\t{\n\t\t_unlocked.Clear();\n\t\t_toastQ.Clear();\n\t\t_toastId = \u0022\u0022;\n\t\t_toastLeft = 0f;\n\t\t_boardOpen = false;\n\t\t_version\u002B\u002B;\n\t}\n\n\tpublic static void Clear()\n\t{\n\t\t_toastQ.Clear();\n\t\t_toastId = \u0022\u0022;\n\t\t_toastLeft = 0f;\n\t\t_boardOpen = false;\n\t\t_version\u002B\u002B;\n\t}\n\n\tpublic static void Load( SaveData data )\n\t{\n\t\t_unlocked.Clear();\n\t\t_toastQ.Clear();\n\t\t_toastId = \u0022\u0022;\n\t\t_toastLeft = 0f;\n\t\t_boardOpen = false;\n\t\tif ( data?.AchievementsUnlocked is not null )\n\t\t{\n\t\t\tforeach ( var id in data.AchievementsUnlocked )\n\t\t\t{\n\t\t\t\tif ( !string.IsNullOrWhiteSpace( id ) \u0026\u0026 Find( id.Trim() ) is not null )\n\t\t\t\t\t_unlocked.Add( id.Trim() );\n\t\t\t}\n\t\t}\n\t\t_version\u002B\u002B;\n\t\tEvaluate( silent: true );\n\t}\n\n\tpublic static void WriteToSave( SaveData data )\n\t{\n\t\tif ( data is null )\n\t\t\treturn;\n\t\tdata.AchievementsUnlocked = _unlocked.ToList();\n\t}\n\n\tpublic static void ToggleBoard()\n\t{\n\t\tif ( !GrandmaGifts.HasTank )\n\t\t\treturn;\n\t\t_boardOpen = !_boardOpen;\n\t\tGameAudio.PlayUi( _boardOpen ? GameAudio.Notice : GameAudio.Close );\n\t\t_version\u002B\u002B;\n\t}\n\n\tpublic static void CloseBoard()\n\t{\n\t\tif ( !_boardOpen )\n\t\t\treturn;\n\t\t_boardOpen = false;\n\t\t_version\u002B\u002B;\n\t}\n\n\tpublic static void Tick( float dt )\n\t{\n\t\tif ( dt \u003C= 0f )\n\t\t\treturn;\n\t\tif ( _toastLeft \u003E 0f )\n\t\t{\n\t\t\t_toastLeft -= dt;\n\t\t\tif ( _toastLeft \u003C= 0f )\n\t\t\t{\n\t\t\t\t_toastLeft = 0f;\n\t\t\t\t_toastId = \u0022\u0022;\n\t\t\t\t_version\u002B\u002B;\n\t\t\t\tTryShowNextToast();\n\t\t\t}\n\t\t}\n\n\t\t// Cheap poll so we don\u0027t miss a flag that fired without a hook.\n\t\tif ( TankSim.IsActive \u0026\u0026 GrandmaGifts.HasTank )\n\t\t\tEvaluate( silent: false );\n\t}\n\n\tpublic static bool Unlock( string id, bool silent = false )\n\t{\n\t\tif ( string.IsNullOrEmpty( id ) )\n\t\t\treturn false;\n\t\tif ( Find( id ) is null )\n\t\t\treturn false;\n\t\tif ( !_unlocked.Add( id ) )\n\t\t\treturn false;\n\n\t\tif ( !silent )\n\t\t{\n\t\t\t_toastQ.Enqueue( id );\n\t\t\tif ( !ToastVisible )\n\t\t\t\tTryShowNextToast();\n\t\t\tSaveGame.TrySave( quiet: true );\n\t\t}\n\n\t\t_version\u002B\u002B;\n\t\treturn true;\n\t}\n\n\t/// \u003Csummary\u003EBackfill from world state. Silent on load so we don\u0027t spam a mid-empire save.\u003C/summary\u003E\n\tpublic static void Evaluate( bool silent )\n\t{\n\t\tif ( !TankSim.IsActive || !GrandmaGifts.HasTank )\n\t\t\treturn;\n\n\t\tif ( StorySystem.HasFedOnce )\n\t\t\tUnlock( \u0022feed\u0022, silent );\n\t\tif ( StorySystem.HasTrainedOnce )\n\t\t\tUnlock( \u0022train\u0022, silent );\n\t\tif ( StorySystem.HasCombinedOnce || TankSim.AbominationsCreated \u003E 0 )\n\t\t\tUnlock( \u0022combine\u0022, silent );\n\t\tif ( BattleSystem.Wins \u002B BattleSystem.Losses \u003E 0 )\n\t\t\tUnlock( \u0022fight\u0022, silent );\n\t\tif ( BattleSystem.Wins \u003E 0 )\n\t\t\tUnlock( \u0022win\u0022, silent );\n\t\tif ( LotRunSystem.HasFlushed )\n\t\t\tUnlock( \u0022flush\u0022, silent );\n\t\tif ( AnyMapCleared() )\n\t\t\tUnlock( \u0022pipes\u0022, silent );\n\t\tif ( StorySystem.HasSoldOnce )\n\t\t\tUnlock( \u0022sell\u0022, silent );\n\t\tif ( TankSim.DecorCount \u003E 0 || !string.IsNullOrEmpty( TankSim.ActiveBackdropId ) )\n\t\t\tUnlock( \u0022decor\u0022, silent );\n\t\tif ( TankSim.HabitatRank \u003E 0 )\n\t\t\tUnlock( \u0022habitat\u0022, silent );\n\t\tif ( TankSim.SaltUnlocked )\n\t\t\tUnlock( \u0022salt\u0022, silent );\n\t\tif ( Shop.M80Bags \u003E 0 || Shop.MethBags \u003E 0 || StorySystem.HasBoomOnce )\n\t\t\tUnlock( \u0022shady\u0022, silent );\n\t\tif ( StorySystem.HasBoomOnce )\n\t\t\tUnlock( \u0022boom\u0022, silent );\n\t\tif ( CrimeSystem.JanitorOffers \u003E 0 )\n\t\t\tUnlock( \u0022janitor\u0022, silent );\n\t\tif ( CrimeSystem.Kicks \u003E 0 )\n\t\t\tUnlock( \u0022kick\u0022, silent );\n\t\tif ( CrimeSystem.RobbedTin )\n\t\t\tUnlock( \u0022tin\u0022, silent );\n\t\tif ( TankSim.FishDeaths \u003E 0 )\n\t\t\tUnlock( \u0022dead\u0022, silent );\n\t}\n\n\tstatic bool AnyMapCleared()\n\t{\n\t\tforeach ( var loc in LotLocationDef.Catalog )\n\t\t{\n\t\t\tif ( LotRunSystem.IsCleared( loc.Id ) )\n\t\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tstatic void TryShowNextToast()\n\t{\n\t\tif ( ToastVisible || _toastQ.Count == 0 )\n\t\t\treturn;\n\t\t_toastId = _toastQ.Dequeue();\n\t\t_toastLeft = ToastSeconds;\n\t\tGameAudio.PlayUi( GameAudio.Achievement );\n\t\t_version\u002B\u002B;\n\t}\n}\n"},{"Ident":"dogecoin.nochillquarium","Path":"Assembly.cs","FileName":"Assembly.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"global using Sandbox;\nglobal using Sandbox.UI;\nglobal using System;\nglobal using System.Collections.Generic;\nglobal using System.Linq;\n"},{"Ident":"dogecoin.nochillquarium","Path":"GameBootstrap.cs","FileName":"GameBootstrap.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"namespace NoChillquarium;\n\n/// \u003Csummary\u003EScene root breadcrumb. UI lives on \u003Csee cref=\u0022MainShell\u0022/\u003E.\u003C/summary\u003E\npublic sealed class GameBootstrap : Component\n{\n\tprotected override void OnStart()\n\t{\n\t\tLog.Info( $\u0022[NO-CHILLquarium] {GameInfo.VersionLabel} boot\u0022 );\n\t}\n}\n"},{"Ident":"dogecoin.nochillquarium","Path":"Karma.cs","FileName":"Karma.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"namespace NoChillquarium;\n\n/// \u003Csummary\u003E\n/// Grandma\u0027s respect meter. Opposite pressure to \u003Csee cref=\u0022Chaos\u0022/\u003E.\n/// She loves abominations, championships, and quality-control booms.\n/// She cools off if you chicken out of fights, and blames you when fish die.\n/// \u003C/summary\u003E\npublic static class Karma\n{\n\tpublic const float Max = 100f;\n\n\tstatic float _value = 40f; // slightly neutral-good start\n\tstatic int _version;\n\n\tpublic static float Value =\u003E _value;\n\tpublic static int Version =\u003E _version;\n\tpublic static string ValueText =\u003E $\u0022{_value:0}\u0022;\n\n\t/// \u003Csummary\u003EGrandma\u0027s current vibe \u2014 player-facing label.\u003C/summary\u003E\n\tpublic static string Label =\u003E\n\t\t_value \u003E= 70f ? \u0022PROUD\u0022 :\n\t\t_value \u003E= 50f ? \u0022FOND\u0022 :\n\t\t_value \u003E= 30f ? \u0022COOLING\u0022 :\n\t\t\u0022DISAPPOINTED\u0022;\n\n\t/// \u003Csummary\u003EHUD / credits kicker (was \u0022Karma\u0022).\u003C/summary\u003E\n\tpublic static string MeterName =\u003E \u0022Grandma\u0022;\n\n\tpublic static void Reset()\n\t{\n\t\t_value = 40f;\n\t\t_version\u002B\u002B;\n\t}\n\n\tpublic static void Set( float value )\n\t{\n\t\t_value = Math.Clamp( value, 0f, Max );\n\t\t_version\u002B\u002B;\n\t}\n\n\tpublic static void Add( float amount )\n\t{\n\t\tif ( MathF.Abs( amount ) \u003C 0.0001f )\n\t\t\treturn;\n\t\t_value = Math.Clamp( _value \u002B amount, 0f, Max );\n\t\t_version\u002B\u002B;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Slow fondness while the tank is thriving \u2014 she likes a full, ugly empire.\n\t/// \u003C/summary\u003E\n\tpublic static void TickCalmCare( float dt )\n\t{\n\t\tif ( !TankSim.IsActive || dt \u003C= 0f )\n\t\t\treturn;\n\t\tif ( !EndingSystem.ThrivingTank )\n\t\t\treturn;\n\t\tif ( Chaos.Value \u003E 40f )\n\t\t\treturn;\n\n\t\t// Prefer abom tanks: monsters get a little more love.\n\t\tvar rate = TankSim.HasAnyMonster ? 0.035f : 0.02f;\n\t\tAdd( dt * rate );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Chicken out of the fight ladder too long and Grandma stops respecting you.\n\t/// Only after Fight is actually on the menu (trained \u002B can pay).\n\t/// \u003C/summary\u003E\n\tpublic static void TickWuss( float dt )\n\t{\n\t\tif ( !TankSim.IsActive || dt \u003C= 0f )\n\t\t\treturn;\n\t\t// Don\u0027t punish people still learning gym / still broke.\n\t\tif ( !Progression.FightUnlocked )\n\t\t\treturn;\n\t\t// Already fighting = not a wuss.\n\t\tif ( BattleSystem.Wins \u002B BattleSystem.Losses \u003E= 2 )\n\t\t\treturn;\n\t\tif ( BattleSystem.ChampionshipTitles \u003E 0 )\n\t\t\treturn;\n\n\t\t// ~-1 respect per ~60s of hiding once Fight is available.\n\t\tAdd( -dt * 0.017f );\n\t}\n\n\tpublic static void WriteToSave( SaveData data )\n\t{\n\t\tif ( data is not null )\n\t\t\tdata.Karma = _value;\n\t}\n\n\tpublic static void LoadFromSave( SaveData data )\n\t{\n\t\tif ( data is null )\n\t\t{\n\t\t\tReset();\n\t\t\treturn;\n\t\t}\n\n\t\t// Old saves without karma field deserialize as 0 \u2014 treat as fresh mid.\n\t\tif ( data.Karma \u003C= 0.01f \u0026\u0026 data.PlaytimeSeconds \u003C 5f )\n\t\t\t_value = 40f;\n\t\telse if ( data.Version \u003C 5 \u0026\u0026 data.Karma \u003C= 0.01f )\n\t\t\t_value = 40f;\n\t\telse\n\t\t\t_value = Math.Clamp( data.Karma, 0f, Max );\n\t\t_version\u002B\u002B;\n\t}\n}\n"},{"Ident":"dogecoin.nochillquarium","Path":"LotRunDef.cs","FileName":"LotRunDef.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"namespace NoChillquarium;\n\n/// \u003Csummary\u003EWhat happens in one dungeon room.\u003C/summary\u003E\npublic enum LotRoomKind\n{\n\t/// \u003Csummary\u003EFlavor only \u2014 click Continue.\u003C/summary\u003E\n\tLook,\n\t/// \u003Csummary\u003EAuto-resolve skirmish with local AI fish.\u003C/summary\u003E\n\tFight,\n\t/// \u003Csummary\u003EInstant \u00D0 (and maybe chaos).\u003C/summary\u003E\n\tLoot,\n\t/// \u003Csummary\u003ERisk: injury / chaos / small \u00D0.\u003C/summary\u003E\n\tHazard,\n\t/// \u003Csummary\u003ETwo buttons with different outcomes.\u003C/summary\u003E\n\tChoice,\n\t/// \u003Csummary\u003EHarder fight; clearing ends the run.\u003C/summary\u003E\n\tBoss\n}\n\n/// \u003Csummary\u003EOne room on a lot-run map.\u003C/summary\u003E\npublic sealed class LotRoomDef\n{\n\tpublic string Title { get; init; }\n\tpublic string Body { get; init; }\n\tpublic LotRoomKind Kind { get; init; } = LotRoomKind.Look;\n\tpublic string Button { get; init; } = \u0022Continue\u0022;\n\t// Fight / boss\n\tpublic float AiStatMin { get; init; } = 2f;\n\tpublic float AiStatMax { get; init; } = 4f;\n\tpublic string[] EnemyNames { get; init; }\n\tpublic string EnemyColor { get; init; } = \u0022#8090a0\u0022;\n\tpublic double FightRewardMin { get; init; }\n\tpublic double FightRewardMax { get; init; }\n\t// Loot\n\tpublic double LootMin { get; init; }\n\tpublic double LootMax { get; init; }\n\t// Hazard\n\tpublic float InjuryChance { get; init; } = 0.35f;\n\tpublic float ChaosOnHazard { get; init; } = 2f;\n\t// Choice\n\tpublic string ChoiceA { get; init; }\n\tpublic string ChoiceB { get; init; }\n\tpublic string ChoiceAResult { get; init; }\n\tpublic string ChoiceBResult { get; init; }\n\tpublic double ChoiceALoot { get; init; }\n\tpublic double ChoiceBLoot { get; init; }\n\tpublic float ChoiceAChaos { get; init; }\n\tpublic float ChoiceBChaos { get; init; }\n\tpublic float ChoiceBInjuryChance { get; init; }\n}\n\n/// \u003Csummary\u003EA crawlable location off Grandma\u0027s lot (jr high, alley, pool, \u2026).\u003C/summary\u003E\npublic sealed class LotLocationDef\n{\n\tpublic string Id { get; init; }\n\tpublic string Name { get; init; }\n\tpublic string Note { get; init; }\n\tpublic string Blurb { get; init; }\n\tpublic string ThemeClass { get; init; } = \u0022\u0022;\n\tpublic double EntryFee { get; init; }\n\tpublic int TeamSize { get; init; } = 1;\n\t/// \u003Csummary\u003EMin playtime seconds before the map shows as unlocked.\u003C/summary\u003E\n\tpublic float UnlockPlaytime { get; init; }\n\t/// \u003Csummary\u003ENeed this many fight wins (or equal losses\u002Bwins) to enter.\u003C/summary\u003E\n\tpublic int UnlockFights { get; init; }\n\t/// \u003Csummary\u003EMust have trained once.\u003C/summary\u003E\n\tpublic bool RequireTrained { get; init; }\n\tpublic double ClearBonus { get; init; }\n\tpublic string ClearBanner { get; init; }\n\t/// \u003Csummary\u003EFirst adventure: only entered by flushing a fish (then re-openable from map).\u003C/summary\u003E\n\tpublic bool FlushOrigin { get; init; }\n\tpublic IReadOnlyList\u003CLotRoomDef\u003E Rooms { get; init; }\n\n\tpublic static IReadOnlyList\u003CLotLocationDef\u003E Catalog { get; } = new[]\n\t{\n\t\t// 0 \u2014 first crawl (flush entry)\n\t\tnew LotLocationDef\n\t\t{\n\t\t\tId = \u0022toilet_pipes\u0022,\n\t\t\tName = \u0022Toilet Pipes\u0022,\n\t\t\tNote = \u0022under the trailer\u0022,\n\t\t\tBlurb = \u0022Municipal pipe. U-bends. Your fish is ahead of you.\u0022,\n\t\t\tThemeClass = \u0022run-toilet\u0022,\n\t\t\tEntryFee = 0,\n\t\t\tTeamSize = 1,\n\t\t\tUnlockPlaytime = 0f,\n\t\t\tRequireTrained = false,\n\t\t\tUnlockFights = 0,\n\t\t\tFlushOrigin = true,\n\t\t\tClearBonus = 40,\n\t\t\tClearBanner = \u0022Pipes cleared. Other maps unlocked on the board.\u0022,\n\t\t\tRooms = new[]\n\t\t\t{\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Look,\n\t\t\t\t\tTitle = \u0022Into the trap\u0022,\n\t\t\t\t\tBody = \u0022Tile walls. Cold water. They\u0027re already past the U-bend.\u0022,\n\t\t\t\t\tButton = \u0022Follow\u0022\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Hazard,\n\t\t\t\t\tTitle = \u0022U-Bend\u0022,\n\t\t\t\t\tBody = \u0022Narrow curve. Rough scale. Easy to scrape if you rush.\u0022,\n\t\t\t\t\tButton = \u0022Squeeze through\u0022,\n\t\t\t\t\tInjuryChance = 0.25f,\n\t\t\t\t\tChaosOnHazard = 1.5f\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Fight,\n\t\t\t\t\tTitle = \u0022Pipe fauna\u0022,\n\t\t\t\t\tBody = \u0022Small, mean things that live in standing water.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 1.4f,\n\t\t\t\t\tAiStatMax = 2.8f,\n\t\t\t\t\tEnemyNames = new[] { \u0022Pipe Mite\u0022, \u0022Silt Ray\u0022, \u0022Trap Snail\u0022, \u0022Scale Flake\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#607080\u0022,\n\t\t\t\t\tFightRewardMin = 12,\n\t\t\t\t\tFightRewardMax = 22\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Loot,\n\t\t\t\t\tTitle = \u0022Silt pocket\u0022,\n\t\t\t\t\tBody = \u0022A ring, some coins, grit. Worth pocketing.\u0022,\n\t\t\t\t\tButton = \u0022Take it\u0022,\n\t\t\t\t\tLootMin = 16,\n\t\t\t\t\tLootMax = 34\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Boss,\n\t\t\t\t\tTitle = \u0022Main line\u0022,\n\t\t\t\t\tBody = \u0022Where house lines join the lot trunk. Something large blocks the flow.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 2.4f,\n\t\t\t\t\tAiStatMax = 4.2f,\n\t\t\t\t\tEnemyNames = new[] { \u0022Mainline Carp\u0022, \u0022Trunk Bass\u0022, \u0022Joint Eel\u0022, \u0022Sewer Perch\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#405060\u0022,\n\t\t\t\t\tFightRewardMin = 28,\n\t\t\t\t\tFightRewardMax = 48\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// 1 \u2014 after first flush adventure\n\t\tnew LotLocationDef\n\t\t{\n\t\t\tId = \u0022jr_high_night\u0022,\n\t\t\tName = \u0022Jr High After Hours\u0022,\n\t\t\tNote = \u0022after dark\u0022,\n\t\t\tBlurb = \u0022Fence, halls, principal\u0027s office. Bring one trained fish.\u0022,\n\t\t\tThemeClass = \u0022run-school\u0022,\n\t\t\tEntryFee = 12,\n\t\t\tTeamSize = 1,\n\t\t\tUnlockPlaytime = 6f * 60f,\n\t\t\tRequireTrained = true,\n\t\t\tUnlockFights = 0,\n\t\t\tClearBonus = 35,\n\t\t\tClearBanner = \u0022School run done. Stay off the cameras.\u0022,\n\t\t\tRooms = new[]\n\t\t\t{\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Look,\n\t\t\t\t\tTitle = \u0022Chain-link\u0022,\n\t\t\t\t\tBody = \u0022Wax and metal. Hall lights on a timer.\u0022,\n\t\t\t\t\tButton = \u0022Climb in\u0022\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Fight,\n\t\t\t\t\tTitle = \u0022Hall patrol\u0022,\n\t\t\t\t\tBody = \u0022Someone\u0027s fish in a plastic carrier. They take the job seriously.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 1.6f,\n\t\t\t\t\tAiStatMax = 3.2f,\n\t\t\t\t\tEnemyNames = new[] { \u0022Carrier Guppy\u0022, \u0022Badge Molly\u0022, \u0022Tardy Tetra\u0022, \u0022Whistle Ray\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#708090\u0022,\n\t\t\t\t\tFightRewardMin = 10,\n\t\t\t\t\tFightRewardMax = 18\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Loot,\n\t\t\t\t\tTitle = \u0022Vending machine\u0022,\n\t\t\t\t\tBody = \u0022Kick once. Coins rattle out.\u0022,\n\t\t\t\t\tButton = \u0022Kick\u0022,\n\t\t\t\t\tLootMin = 8,\n\t\t\t\t\tLootMax = 22\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Choice,\n\t\t\t\t\tTitle = \u0022Split hall\u0022,\n\t\t\t\t\tBody = \u0022Detention wing is quiet. Gym still smells like bleach.\u0022,\n\t\t\t\t\tChoiceA = \u0022Detention\u0022,\n\t\t\t\t\tChoiceB = \u0022Gym\u0022,\n\t\t\t\t\tChoiceAResult = \u0022Teacher\u0027s coffee can \u2014 cash and receipts.\u0022,\n\t\t\t\t\tChoiceBResult = \u0022Hard floor. Your fish takes a knock.\u0022,\n\t\t\t\t\tChoiceALoot = 14,\n\t\t\t\t\tChoiceBLoot = 6,\n\t\t\t\t\tChoiceBChaos = 1.5f,\n\t\t\t\t\tChoiceBInjuryChance = 0.25f\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Boss,\n\t\t\t\t\tTitle = \u0022Principal\u0027s office\u0022,\n\t\t\t\t\tBody = \u0022Nameplate. Plastic plant. A prize guppy in a desk tank.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 2.8f,\n\t\t\t\t\tAiStatMax = 4.6f,\n\t\t\t\t\tEnemyNames = new[] { \u0022Desk Guppy\u0022, \u0022Office Betta\u0022, \u0022Policy Pike\u0022, \u0022Badge Bass\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#405070\u0022,\n\t\t\t\t\tFightRewardMin = 28,\n\t\t\t\t\tFightRewardMax = 48\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// 2\n\t\tnew LotLocationDef\n\t\t{\n\t\t\tId = \u0022dark_alley\u0022,\n\t\t\tName = \u0022Dark Alley\u0022,\n\t\t\tNote = \u0022behind 4B\u0022,\n\t\t\tBlurb = \u0022Dumpsters and standing water. Watch for glass.\u0022,\n\t\t\tThemeClass = \u0022run-alley\u0022,\n\t\t\tEntryFee = 22,\n\t\t\tTeamSize = 1,\n\t\t\tUnlockPlaytime = 10f * 60f,\n\t\t\tRequireTrained = true,\n\t\t\tUnlockFights = 1,\n\t\t\tClearBonus = 55,\n\t\t\tClearBanner = \u0022Alley cleared.\u0022,\n\t\t\tRooms = new[]\n\t\t\t{\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Look,\n\t\t\t\t\tTitle = \u0022Dumpster line\u0022,\n\t\t\t\t\tBody = \u0022Gas-station neon. Grease film on the water.\u0022,\n\t\t\t\t\tButton = \u0022Enter\u0022\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Hazard,\n\t\t\t\t\tTitle = \u0022Broken glass\u0022,\n\t\t\t\t\tBody = \u0022Shards in the runoff. Easy cut if you\u0027re careless.\u0022,\n\t\t\t\t\tButton = \u0022Push through\u0022,\n\t\t\t\t\tInjuryChance = 0.4f,\n\t\t\t\t\tChaosOnHazard = 2.5f\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Fight,\n\t\t\t\t\tTitle = \u0022Alley stock\u0022,\n\t\t\t\t\tBody = \u0022Half-feral fish living off trash water.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 2.4f,\n\t\t\t\t\tAiStatMax = 4.2f,\n\t\t\t\t\tEnemyNames = new[] { \u0022Grease Molly\u0022, \u0022Moth Betta\u0022, \u0022Neon Trash\u0022, \u0022Curb Guppy\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#504060\u0022,\n\t\t\t\t\tFightRewardMin = 16,\n\t\t\t\t\tFightRewardMax = 30\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Loot,\n\t\t\t\t\tTitle = \u0022Dropped wallet\u0022,\n\t\t\t\t\tBody = \u0022Not yours. Still money.\u0022,\n\t\t\t\t\tButton = \u0022Take \u00D0\u0022,\n\t\t\t\t\tLootMin = 18,\n\t\t\t\t\tLootMax = 40\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Boss,\n\t\t\t\t\tTitle = \u00224B cousin\u0022,\n\t\t\t\t\tBody = \u0022Related to Brad. Runs this strip like he owns it.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 3.5f,\n\t\t\t\t\tAiStatMax = 5.5f,\n\t\t\t\t\tEnemyNames = new[] { \u00224B Cousin\u0022, \u0022Alley Lead\u0022, \u0022Strip Boss\u0022, \u0022Lot Ray\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#803040\u0022,\n\t\t\t\t\tFightRewardMin = 40,\n\t\t\t\t\tFightRewardMax = 70\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// 3\n\t\tnew LotLocationDef\n\t\t{\n\t\t\tId = \u0022neighbor_pool_night\u0022,\n\t\t\tName = \u0022Neighbor\u0027s Pool\u0022,\n\t\t\tNote = \u0022after midnight\u0022,\n\t\t\tBlurb = \u0022Chlorine, cameras, deed-rule koi. Team of two.\u0022,\n\t\t\tThemeClass = \u0022run-pool\u0022,\n\t\t\tEntryFee = 35,\n\t\t\tTeamSize = 2,\n\t\t\tUnlockPlaytime = 14f * 60f,\n\t\t\tRequireTrained = true,\n\t\t\tUnlockFights = 2,\n\t\t\tClearBonus = 80,\n\t\t\tClearBanner = \u0022Pool cleared. Don\u0027t leave wet footprints.\u0022,\n\t\t\tRooms = new[]\n\t\t\t{\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Look,\n\t\t\t\t\tTitle = \u0022Back gate\u0022,\n\t\t\t\t\tBody = \u0022Motion light. Timer. You have a window.\u0022,\n\t\t\t\t\tButton = \u0022Slip in\u0022\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Fight,\n\t\t\t\t\tTitle = \u0022Skimmer line\u0022,\n\t\t\t\t\tBody = \u0022Pool fish used to clean water. Territorial.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 3f,\n\t\t\t\t\tAiStatMax = 5f,\n\t\t\t\t\tEnemyNames = new[] { \u0022Skimmer Ray\u0022, \u0022Float Bass\u0022, \u0022Filter Guppy\u0022, \u0022Deep Molly\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#2080a0\u0022,\n\t\t\t\t\tFightRewardMin = 22,\n\t\t\t\t\tFightRewardMax = 40\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Hazard,\n\t\t\t\t\tTitle = \u0022Chemical shock\u0022,\n\t\t\t\t\tBody = \u0022High chlorine. Hard on gills if you linger.\u0022,\n\t\t\t\t\tButton = \u0022Push through\u0022,\n\t\t\t\t\tInjuryChance = 0.45f,\n\t\t\t\t\tChaosOnHazard = 3f\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Choice,\n\t\t\t\t\tTitle = \u0022Cabana / board\u0022,\n\t\t\t\t\tBody = \u0022Quiet loot at the cabana, or risk noise off the board.\u0022,\n\t\t\t\t\tChoiceA = \u0022Cabana\u0022,\n\t\t\t\t\tChoiceB = \u0022Board\u0022,\n\t\t\t\t\tChoiceAResult = \u0022Cash in a towel bin.\u0022,\n\t\t\t\t\tChoiceBResult = \u0022Splash carries. Injury risk.\u0022,\n\t\t\t\t\tChoiceALoot = 28,\n\t\t\t\t\tChoiceBLoot = 12,\n\t\t\t\t\tChoiceBChaos = 4f,\n\t\t\t\t\tChoiceBInjuryChance = 0.35f\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Boss,\n\t\t\t\t\tTitle = \u0022Deed koi\u0022,\n\t\t\t\t\tBody = \u0022HOA champion stock. Big, trained, expensive.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 4.2f,\n\t\t\t\t\tAiStatMax = 6.5f,\n\t\t\t\t\tEnemyNames = new[] { \u0022Covenant Koi\u0022, \u0022Deed Bass\u0022, \u0022Laminate Ray\u0022, \u0022Rule Carp\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#30a060\u0022,\n\t\t\t\t\tFightRewardMin = 55,\n\t\t\t\t\tFightRewardMax = 95\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// 4\n\t\tnew LotLocationDef\n\t\t{\n\t\t\tId = \u0022gas_station\u0022,\n\t\t\tName = \u0022Gas Station Dumpsters\u0022,\n\t\t\tNote = \u0022behind pumps\u0022,\n\t\t\tBlurb = \u0022Grease trap water. Janitor pretends not to see.\u0022,\n\t\t\tThemeClass = \u0022run-gas\u0022,\n\t\t\tEntryFee = 28,\n\t\t\tTeamSize = 1,\n\t\t\tUnlockPlaytime = 12f * 60f,\n\t\t\tRequireTrained = true,\n\t\t\tUnlockFights = 1,\n\t\t\tClearBonus = 60,\n\t\t\tClearBanner = \u0022Dumpster line cleared.\u0022,\n\t\t\tRooms = new[]\n\t\t\t{\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Look,\n\t\t\t\t\tTitle = \u0022Pump row\u0022,\n\t\t\t\t\tBody = \u0022Janitor mops past. \\\u0022Not my dumpsters.\\\u0022\u0022,\n\t\t\t\t\tButton = \u0022Around back\u0022\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Loot,\n\t\t\t\t\tTitle = \u0022Change tray\u0022,\n\t\t\t\t\tBody = \u0022Uncounted change under the register shelf.\u0022,\n\t\t\t\t\tButton = \u0022Take it\u0022,\n\t\t\t\t\tLootMin = 12,\n\t\t\t\t\tLootMax = 28\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Fight,\n\t\t\t\t\tTitle = \u0022Grease trap\u0022,\n\t\t\t\t\tBody = \u0022Something lives in the trap water. Hungry.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 2.8f,\n\t\t\t\t\tAiStatMax = 4.8f,\n\t\t\t\t\tEnemyNames = new[] { \u0022Trap Carp\u0022, \u0022Oil Ray\u0022, \u0022Sludge Molly\u0022, \u0022Pump Perch\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#806020\u0022,\n\t\t\t\t\tFightRewardMin = 18,\n\t\t\t\t\tFightRewardMax = 34\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Hazard,\n\t\t\t\t\tTitle = \u0022Oil film\u0022,\n\t\t\t\t\tBody = \u0022Slick surface. Hard to get purchase. Easy bruise.\u0022,\n\t\t\t\t\tButton = \u0022Cross\u0022,\n\t\t\t\t\tInjuryChance = 0.3f,\n\t\t\t\t\tChaosOnHazard = 2f\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Boss,\n\t\t\t\t\tTitle = \u0022Under-counter tank\u0022,\n\t\t\t\t\tBody = \u0022Illegal display stock. Big, fed on scraps, mean.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 3.8f,\n\t\t\t\t\tAiStatMax = 5.8f,\n\t\t\t\t\tEnemyNames = new[] { \u0022Counter Bass\u0022, \u0022Shelf Shark\u0022, \u0022Lottery Carp\u0022, \u0022Shift Lead\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#a05020\u0022,\n\t\t\t\t\tFightRewardMin = 45,\n\t\t\t\t\tFightRewardMax = 75\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// 5\n\t\tnew LotLocationDef\n\t\t{\n\t\t\tId = \u0022storm_drain\u0022,\n\t\t\tName = \u0022Storm Drain\u0022,\n\t\t\tNote = \u0022lot runoff\u0022,\n\t\t\tBlurb = \u0022Concrete under the street. Team of two. Watch the drop.\u0022,\n\t\t\tThemeClass = \u0022run-drain\u0022,\n\t\t\tEntryFee = 40,\n\t\t\tTeamSize = 2,\n\t\t\tUnlockPlaytime = 18f * 60f,\n\t\t\tRequireTrained = true,\n\t\t\tUnlockFights = 3,\n\t\t\tClearBonus = 90,\n\t\t\tClearBanner = \u0022Drain cleared.\u0022,\n\t\t\tRooms = new[]\n\t\t\t{\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Look,\n\t\t\t\t\tTitle = \u0022Grate\u0022,\n\t\t\t\t\tBody = \u0022Lift with a stick. Cold air, wet concrete.\u0022,\n\t\t\t\t\tButton = \u0022Drop in\u0022\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Fight,\n\t\t\t\t\tTitle = \u0022Runoff school\u0022,\n\t\t\t\t\tBody = \u0022Fish living on street wash. Tough and dirty.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 3.2f,\n\t\t\t\t\tAiStatMax = 5.2f,\n\t\t\t\t\tEnemyNames = new[] { \u0022Leaf Carp\u0022, \u0022Curb Ray\u0022, \u0022Grate Guppy\u0022, \u0022Gutter Bass\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#405848\u0022,\n\t\t\t\t\tFightRewardMin = 24,\n\t\t\t\t\tFightRewardMax = 42\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Loot,\n\t\t\t\t\tTitle = \u0022Coin silt\u0022,\n\t\t\t\t\tBody = \u0022Fountain and parking runoff. Small metal, real \u00D0.\u0022,\n\t\t\t\t\tButton = \u0022Scoop\u0022,\n\t\t\t\t\tLootMin = 20,\n\t\t\t\t\tLootMax = 50\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Choice,\n\t\t\t\t\tTitle = \u0022Forked pipe\u0022,\n\t\t\t\t\tBody = \u0022Left is quieter. Right is deeper and richer.\u0022,\n\t\t\t\t\tChoiceA = \u0022Quiet pipe\u0022,\n\t\t\t\t\tChoiceB = \u0022Deep pipe\u0022,\n\t\t\t\t\tChoiceAResult = \u0022Cleaner water. Small take.\u0022,\n\t\t\t\t\tChoiceBResult = \u0022Bigger take. Something bit.\u0022,\n\t\t\t\t\tChoiceALoot = 15,\n\t\t\t\t\tChoiceBLoot = 45,\n\t\t\t\t\tChoiceBChaos = 3f,\n\t\t\t\t\tChoiceBInjuryChance = 0.4f\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Boss,\n\t\t\t\t\tTitle = \u0022Trunk blockage\u0022,\n\t\t\t\t\tBody = \u0022Large fish lodged in the main trunk. Clear it or turn back.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 4.5f,\n\t\t\t\t\tAiStatMax = 7f,\n\t\t\t\t\tEnemyNames = new[] { \u0022Trunk Carp\u0022, \u0022Block Bass\u0022, \u0022Cap Ray\u0022, \u0022Mainline Eel\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#304838\u0022,\n\t\t\t\t\tFightRewardMin = 70,\n\t\t\t\t\tFightRewardMax = 110\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// 6\n\t\tnew LotLocationDef\n\t\t{\n\t\t\tId = \u0022bingo_hall\u0022,\n\t\t\tName = \u0022Bingo Hall Basement\u0022,\n\t\t\tNote = \u0022downstairs\u0022,\n\t\t\tBlurb = \u0022Under the hall Grandma plays. She pretends not to know the door code.\u0022,\n\t\t\tThemeClass = \u0022run-bingo\u0022,\n\t\t\tEntryFee = 50,\n\t\t\tTeamSize = 2,\n\t\t\tUnlockPlaytime = 22f * 60f,\n\t\t\tRequireTrained = true,\n\t\t\tUnlockFights = 4,\n\t\t\tClearBonus = 120,\n\t\t\tClearBanner = \u0022Basement cleared. Don\u0027t tell bingo night.\u0022,\n\t\t\tRooms = new[]\n\t\t\t{\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Look,\n\t\t\t\t\tTitle = \u0022Side door\u0022,\n\t\t\t\t\tBody = \u0022Coffee, ash, floor wax. Her crowd.\u0022,\n\t\t\t\t\tButton = \u0022Enter\u0022\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Fight,\n\t\t\t\t\tTitle = \u0022Basement stock\u0022,\n\t\t\t\t\tBody = \u0022Fish kept for side bets. Ink-stained carriers.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 3.6f,\n\t\t\t\t\tAiStatMax = 5.8f,\n\t\t\t\t\tEnemyNames = new[] { \u0022Daub Molly\u0022, \u0022Card Ray\u0022, \u0022Caller Carp\u0022, \u0022Pot Bass\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#704080\u0022,\n\t\t\t\t\tFightRewardMin = 28,\n\t\t\t\t\tFightRewardMax = 48\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Loot,\n\t\t\t\t\tTitle = \u0022Tip jar\u0022,\n\t\t\t\t\tBody = \u0022She said not to. You take a little.\u0022,\n\t\t\t\t\tButton = \u0022Take \u00D0\u0022,\n\t\t\t\t\tLootMin = 30,\n\t\t\t\t\tLootMax = 70\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Hazard,\n\t\t\t\t\tTitle = \u0022Smoke break\u0022,\n\t\t\t\t\tBody = \u0022Stale air. Hard on gills if you hang around.\u0022,\n\t\t\t\t\tButton = \u0022Push through\u0022,\n\t\t\t\t\tInjuryChance = 0.35f,\n\t\t\t\t\tChaosOnHazard = 2f\n\t\t\t\t},\n\t\t\t\tnew LotRoomDef\n\t\t\t\t{\n\t\t\t\t\tKind = LotRoomKind.Boss,\n\t\t\t\t\tTitle = \u0022Hall champion\u0022,\n\t\t\t\t\tBody = \u0022Not Grandma. Someone who wins too often to be lucky.\u0022,\n\t\t\t\t\tButton = \u0022Fight\u0022,\n\t\t\t\t\tAiStatMin = 5f,\n\t\t\t\t\tAiStatMax = 7.5f,\n\t\t\t\t\tEnemyNames = new[] { \u0022Hall Champ\u0022, \u0022Blackout Bass\u0022, \u0022Hard-Way Ray\u0022, \u0022House Carp\u0022 },\n\t\t\t\t\tEnemyColor = \u0022#903070\u0022,\n\t\t\t\t\tFightRewardMin = 90,\n\t\t\t\t\tFightRewardMax = 140\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t};\n\n\tpublic static LotLocationDef Find( string id )\n\t{\n\t\tif ( string.IsNullOrWhiteSpace( id ) )\n\t\t\treturn null;\n\t\tforeach ( var loc in Catalog )\n\t\t{\n\t\t\tif ( string.Equals( loc.Id, id, StringComparison.OrdinalIgnoreCase ) )\n\t\t\t\treturn loc;\n\t\t}\n\t\treturn null;\n\t}\n}\n"},{"Ident":"dogecoin.nochillquarium","Path":"FusionFeel.cs","FileName":"FusionFeel.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"namespace NoChillquarium;\n\n/// \u003Csummary\u003E\n/// Fusion VFX: hard multi-color flash \u002B tank shake \u002B spark at the merge point.\n/// Respects Reduce Strobe (softer, fewer flashes).\n/// \u003C/summary\u003E\npublic static class FusionFeel\n{\n\tstatic float _time;\n\tstatic float _duration;\n\tstatic float _flashLeft;\n\tstatic float _shakeLeft;\n\tstatic float _shakeAmp;\n\tstatic float _cx;\n\tstatic float _cy;\n\tstatic int _flashCount;\n\tstatic int _version;\n\n\tpublic static int Version =\u003E _version;\n\tpublic static bool IsActive =\u003E _time \u003C _duration \u0026\u0026 _duration \u003E 0f;\n\n\t/// \u003Csummary\u003E0\u20131 overall intensity while playing.\u003C/summary\u003E\n\tpublic static float Intensity\n\t{\n\t\tget\n\t\t{\n\t\t\tif ( !IsActive )\n\t\t\t\treturn 0f;\n\t\t\tvar t = _time / _duration;\n\t\t\t// Peak mid-fusion, fade out.\n\t\t\tif ( t \u003C 0.35f )\n\t\t\t\treturn t / 0.35f;\n\t\t\treturn Math.Clamp( 1f - (t - 0.35f) / 0.65f, 0f, 1f );\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003EStrobe flash amount 0\u20131 (drives full-screen overlay).\u003C/summary\u003E\n\tpublic static float Flash { get; private set; }\n\n\t/// \u003Csummary\u003EWhich flash color phase (0 white, 1 magenta, 2 toxic green).\u003C/summary\u003E\n\tpublic static int FlashPhase { get; private set; }\n\n\tpublic static float ShakeX { get; private set; }\n\tpublic static float ShakeY { get; private set; }\n\n\t/// \u003Csummary\u003ETank-local fusion epicenter.\u003C/summary\u003E\n\tpublic static float OriginX =\u003E _cx;\n\tpublic static float OriginY =\u003E _cy;\n\n\t/// \u003Csummary\u003ERing expand 0\u20131 for CSS scale.\u003C/summary\u003E\n\tpublic static float RingNorm\n\t{\n\t\tget\n\t\t{\n\t\t\tif ( !IsActive )\n\t\t\t\treturn 0f;\n\t\t\treturn Math.Clamp( _time / MathF.Max( 0.05f, _duration ), 0f, 1f );\n\t\t}\n\t}\n\n\tpublic static void Clear()\n\t{\n\t\t_time = 0f;\n\t\t_duration = 0f;\n\t\t_flashLeft = 0f;\n\t\t_shakeLeft = 0f;\n\t\t_shakeAmp = 0f;\n\t\tFlash = 0f;\n\t\tFlashPhase = 0;\n\t\tShakeX = 0f;\n\t\tShakeY = 0f;\n\t\t_flashCount = 0;\n\t\t_version\u002B\u002B;\n\t}\n\n\t/// \u003Csummary\u003EKick off abomination birth flash at tank-local point.\u003C/summary\u003E\n\tpublic static void Trigger( float tankX, float tankY )\n\t{\n\t\t_cx = tankX;\n\t\t_cy = tankY;\n\t\tvar soft = GameSettings.ReduceStrobe;\n\t\t_duration = soft ? 0.55f : 1.15f;\n\t\t_time = 0f;\n\t\t_shakeAmp = (soft ? 6f : 14f) * GameSettings.ShakeScale;\n\t\t_shakeLeft = _duration;\n\t\t_flashLeft = soft ? 0.08f : 0.14f;\n\t\t_flashCount = 0;\n\t\tFlashPhase = 0;\n\t\tFlash = soft ? 0.45f : 0.85f;\n\t\t_version\u002B\u002B;\n\t\tGameAudio.PlayFusion();\n\t\tPixelFx.BurstRarity( PixelFxLayer.Tank, tankX, tankY, FishRarity.PaintedRare );\n\t}\n\n\tpublic static void Tick( float dt )\n\t{\n\t\tif ( _duration \u003C= 0f )\n\t\t\treturn;\n\t\tif ( dt \u003C= 0f )\n\t\t\treturn;\n\t\tif ( dt \u003E 0.05f )\n\t\t\tdt = 0.05f;\n\n\t\t_time \u002B= dt;\n\t\tif ( _time \u003E= _duration )\n\t\t{\n\t\t\tClear();\n\t\t\treturn;\n\t\t}\n\n\t\t// Strobe: fire short flash pulses through the fusion.\n\t\tvar soft = GameSettings.ReduceStrobe;\n\t\tvar pulseHz = soft ? 4f : 9f;\n\t\tvar expected = (int)(_time * pulseHz);\n\t\tif ( expected \u003E _flashCount )\n\t\t{\n\t\t\t_flashCount = expected;\n\t\t\tFlashPhase = (_flashCount % 3);\n\t\t\t_flashLeft = soft ? 0.06f : 0.09f;\n\t\t\tFlash = (soft ? 0.4f : 0.75f) * GameSettings.FlashScale;\n\t\t\t_version\u002B\u002B;\n\t\t}\n\n\t\tif ( _flashLeft \u003E 0f )\n\t\t{\n\t\t\t_flashLeft -= dt;\n\t\t\t// Decay this pulse\n\t\t\tvar peak = soft ? 0.5f : 0.9f;\n\t\t\tFlash = Math.Clamp( (_flashLeft / 0.1f) * peak * GameSettings.FlashScale, 0f, 1f );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tFlash = 0f;\n\t\t}\n\n\t\tif ( _shakeLeft \u003E 0f )\n\t\t{\n\t\t\t_shakeLeft -= dt;\n\t\t\tvar n = Math.Clamp( _shakeLeft / _duration, 0f, 1f );\n\t\t\tvar amp = _shakeAmp * n;\n\t\t\tShakeX = MathF.Sin( _time * 48f ) * amp;\n\t\t\tShakeY = MathF.Cos( _time * 41f ) * amp * 0.7f;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tShakeX = 0f;\n\t\t\tShakeY = 0f;\n\t\t}\n\n\t\t_version\u002B\u002B;\n\t}\n\n\t/// \u003Csummary\u003ECSS class for current flash color.\u003C/summary\u003E\n\tpublic static string FlashClass =\u003E FlashPhase switch\n\t{\n\t\t1 =\u003E \u0022fusion-mag\u0022,\n\t\t2 =\u003E \u0022fusion-toxic\u0022,\n\t\t_ =\u003E \u0022fusion-white\u0022\n\t};\n}\n"},{"Ident":"dogecoin.nochillquarium","Path":"GameAudio.cs","FileName":"GameAudio.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":355227,"Code":"namespace NoChillquarium;\n\n/// \u003Csummary\u003E\n/// UI SFX \u002B music beds.\n/// Rule of thumb: \u003Cb\u003Echill\u003C/b\u003E when the tank is calm; \u003Cb\u003Eno-chill / metal\u003C/b\u003E\n/// when things get absurd or dark (chaos, sharks, meth, M80, death, battles\u2026).\n/// Priority: detonation sting \u0026gt; death ramp \u0026gt; absurd/no-chill \u0026gt; chill.\n/// \u003C/summary\u003E\npublic static class GameAudio\n{\n\tpublic const string Pop = \u0022sounds/ui/pop\u0022;\n\tpublic const string Notice = \u0022sounds/ui/notice\u0022;\n\tpublic const string Close = \u0022sounds/ui/close_window\u0022;\n\tpublic const string Error = \u0022sounds/ui/error\u0022;\n\tpublic const string Confirm = \u0022sounds/ui/confirm\u0022;\n\tpublic const string AlertWarning = \u0022sounds/ui/alert_warning\u0022;\n\t/// \u003Csummary\u003EDog bark \u2014 Dogecoin jackpots / big wallet wins only. Not for UI chrome.\u003C/summary\u003E\n\tpublic const string Doge = \u0022sounds/ui/doge\u0022;\n\t/// \u003Csummary\u003EShort coin / trade sting \u2014 Dogecoin spend/earn feedback (not bark).\u003C/summary\u003E\n\tpublic const string Coin = \u0022sounds/ui/trade\u0022;\n\tpublic const string Feed = \u0022sounds/ui/feed\u0022;\n\tpublic const string Upgrade = \u0022sounds/ui/upgrade\u0022;\n\tpublic const string Trade = \u0022sounds/ui/trade\u0022;\n\tpublic const string Tape = \u0022sounds/ui/tape\u0022;\n\tpublic const string M80 = \u0022sounds/ui/m80\u0022;\n\tpublic const string Inject = \u0022sounds/ui/inject\u0022;\n\tpublic const string Achievement = \u0022sounds/ui/achievement\u0022;\n\t/// \u003Csummary\u003EGolden stamp \u2014 short \u0022favorite\u0022 ding.\u003C/summary\u003E\n\tpublic const string Favorite = \u0022sounds/ui/favorite\u0022;\n\t/// \u003Csummary\u003EEgg hatch chirp.\u003C/summary\u003E\n\tpublic const string Hatch = \u0022sounds/ui/hatch\u0022;\n\t/// \u003Csummary\u003EAbomination birth growl (trimmed).\u003C/summary\u003E\n\tpublic const string Growl = \u0022sounds/ui/growl\u0022;\n\t/// \u003Csummary\u003ESalt tank / ocean sting.\u003C/summary\u003E\n\tpublic const string Dolphin = \u0022sounds/ui/dolphin\u0022;\n\t/// \u003Csummary\u003EBlue whale paperwork sting.\u003C/summary\u003E\n\tpublic const string Whale = \u0022sounds/ui/whale\u0022;\n\n\tpublic const string MenuMusic = \u0022sounds/music/bitten_crown\u0022;\n\tpublic const string ChillTankMusic = \u0022sounds/music/moonberry\u0022;\n\n\t// Escalation ladder when a fish is dying (especially from chill).\n\t// 1 unease \u2192 2 metal \u2192 3 heavy \u2192 4 death peak\n\tconst string DeathStage1 = \u0022sounds/music/gloom_cartridge\u0022;      // uneasy / dark\n\tconst string DeathStage2 = \u0022sounds/music/game_boy_hex\u0022;         // gear up\n\tconst string DeathStage3 = \u0022sounds/music/pixel_horns\u0022;          // heavy\n\tconst string DeathStage4 = \u0022sounds/music/moonbyte_blasphemy\u0022;   // death metal peak\n\n\t/// \u003Csummary\u003EAll no-chill beds \u2014 rotate so monsters/meth don\u0027t lock one track forever.\u003C/summary\u003E\n\tstatic readonly string[] MetalPlaylist =\n\t{\n\t\t\u0022sounds/music/bitten_crown\u0022,\n\t\t\u0022sounds/music/game_boy_hex\u0022,\n\t\t\u0022sounds/music/pixel_horns\u0022,\n\t\t\u0022sounds/music/pixel_hex_rite\u0022,\n\t\t\u0022sounds/music/gloom_cartridge\u0022,\n\t\t\u0022sounds/music/moonbyte_blasphemy\u0022\n\t};\n\n\t/// \u003Csummary\u003ESeconds on one metal bed before auto-advancing (while still no-chill).\u003C/summary\u003E\n\tconst float MetalRotateSeconds = 110f;\n\n\tstatic SoundHandle _music;\n\tstatic string _musicEvent;\n\tstatic int _metalIndex;\n\tstatic int _lastDeathStage = -1;\n\tstatic bool? _lastNoChillBed;\n\tstatic float _detonationStingLeft;\n\tstatic string _lastAbsurdReason = \u0022\u0022;\n\tstatic float _musicRetryCooldown;\n\tstatic string _lastMissingMusic;\n\tstatic float _metalBedTimer;\n\tstatic string _activeMetalPick;\n\tstatic readonly Random _musicRng = new();\n\t/// \u003Csummary\u003EShort music volume kick on big moments (sell, boom, stage, fight).\u003C/summary\u003E\n\tstatic float _pulseBoost;\n\tstatic float _pulseTimer;\n\n\tpublic static bool DetonationStingActive =\u003E _detonationStingLeft \u003E 0f;\n\n\t/// \u003Csummary\u003EKick music intensity for a beat so the bed reacts to gameplay, not only mood swaps.\u003C/summary\u003E\n\tpublic static void PulseMusic( float amount = 0.14f, float seconds = 0.9f )\n\t{\n\t\tamount = Math.Clamp( amount, 0.04f, 0.35f );\n\t\tseconds = Math.Clamp( seconds, 0.25f, 2.5f );\n\t\t_pulseBoost = Math.Max( _pulseBoost, amount );\n\t\t_pulseTimer = Math.Max( _pulseTimer, seconds );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// True when gameplay should feel NO-CHILL (music \u002B chrome), not only high chaos.\n\t/// \u003C/summary\u003E\n\tpublic static bool WantsNoChillMusic =\u003E EvaluateAbsurd( out _ );\n\n\tpublic static string AbsurdReason\n\t{\n\t\tget\n\t\t{\n\t\t\tEvaluateAbsurd( out var reason );\n\t\t\treturn reason ?? \u0022\u0022;\n\t\t}\n\t}\n\n\tpublic static string CurrentBedLabel\n\t{\n\t\tget\n\t\t{\n\t\t\tif ( string.IsNullOrEmpty( _musicEvent ) )\n\t\t\t\treturn \u0022\u2014\u0022;\n\t\t\tif ( DetonationStingActive )\n\t\t\t\treturn \u0022DEATH METAL\u0022;\n\t\t\tif ( _musicEvent.Contains( \u0022moonberry\u0022 ) )\n\t\t\t\treturn \u0022CHILL BED\u0022;\n\t\t\tif ( _musicEvent.Contains( \u0022gloom\u0022 ) )\n\t\t\t\treturn \u0022UNEASE BED\u0022;\n\t\t\tif ( _musicEvent.Contains( \u0022moonbyte\u0022 ) || _musicEvent.Contains( \u0022pixel_hex\u0022 ) )\n\t\t\t\treturn \u0022DEATH METAL\u0022;\n\t\t\tif ( _musicEvent.Contains( \u0022pixel_horns\u0022 ) )\n\t\t\t\treturn \u0022HEAVY METAL\u0022;\n\t\t\treturn \u0022NO-CHILL BED\u0022;\n\t\t}\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Absurd / dark moments that pull the bed off chill even if chaos is still low.\n\t/// Calm tank \u002B low chaos \u002B no hazards \u2192 chill.\n\t/// \u003C/summary\u003E\n\tpublic static bool EvaluateAbsurd( out string reason )\n\t{\n\t\treason = \u0022\u0022;\n\n\t\tif ( DetonationStingActive )\n\t\t{\n\t\t\treason = \u0022detonation\u0022;\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( BoomFeel.IsActive )\n\t\t{\n\t\t\treason = \u0022boom\u0022;\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( DeathFeel.PeakStage \u003E 0 )\n\t\t{\n\t\t\treason = \u0022dying\u0022;\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( SharkSystem.HasSharks )\n\t\t{\n\t\t\treason = \u0022sharks\u0022;\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( BattleSystem.Phase == BattlePhase.Fighting )\n\t\t{\n\t\t\treason = \u0022battle\u0022;\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( BattleSystem.IsOpen )\n\t\t{\n\t\t\treason = \u0022arena\u0022;\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( LotRunSystem.IsOpen \u0026\u0026 !LotRunSystem.IsFlushing )\n\t\t{\n\t\t\treason = \u0022adventure\u0022;\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( MethRush.Open )\n\t\t{\n\t\t\treason = \u0022meth-rush\u0022;\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( TrainingSystem.InjectOpen )\n\t\t{\n\t\t\treason = \u0022inject\u0022;\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( ExplodeSystem.ModeActive \u0026\u0026 ExplodeSystem.SelectedCount \u003E 0 )\n\t\t{\n\t\t\treason = \u0022m80-armed\u0022;\n\t\t\treturn true;\n\t\t}\n\n\t\t// Fusion abominations force NO-CHILL bed (any unlocked tank).\n\t\tif ( TankSim.HasAnyMonster )\n\t\t{\n\t\t\treason = \u0022monsters\u0022;\n\t\t\treturn true;\n\t\t}\n\n\t\t// Living absurdity in the active tank\n\t\tif ( TankSim.IsActive )\n\t\t{\n\t\t\tvar meth = 0;\n\t\t\tvar jacked = 0;\n\t\t\tvar wd = 0;\n\t\t\tforeach ( var f in TankSim.Fish )\n\t\t\t{\n\t\t\t\tif ( f.OnMeth )\n\t\t\t\t\tmeth\u002B\u002B;\n\t\t\t\tif ( f.Jacked )\n\t\t\t\t\tjacked\u002B\u002B;\n\t\t\t\tif ( f.InWithdrawal )\n\t\t\t\t\twd\u002B\u002B;\n\t\t\t}\n\n\t\t\tif ( meth \u003E 0 )\n\t\t\t{\n\t\t\t\treason = \u0022meth\u0022;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif ( wd \u003E= 2 )\n\t\t\t{\n\t\t\t\treason = \u0022withdrawal\u0022;\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif ( jacked \u003E= 3 )\n\t\t\t{\n\t\t\t\treason = \u0022jacked-pack\u0022;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\t// High chaos meter = classic no-chill band\n\t\tif ( Chaos.IsNoChill )\n\t\t{\n\t\t\treason = \u0022chaos\u0022;\n\t\t\treturn true;\n\t\t}\n\n\t\t// Ocean alone is tense but still \u0022chill bed\u0022 until sharks or chaos \u2014 keep calm water chill.\n\t\treturn false;\n\t}\n\n\t/// \u003Csummary\u003EHard cut to peak death metal for a few seconds after M80 detonation.\u003C/summary\u003E\n\tpublic static void TriggerDetonationSting( int victimCount )\n\t{\n\t\tif ( GameFlow.Screen != GameScreen.Playing )\n\t\t\treturn;\n\n\t\tvar n = Math.Max( 1, victimCount );\n\t\t_detonationStingLeft = 4.2f \u002B n * 0.55f;\n\t\t// Peak blasphemy \u002B louder.\n\t\tStartMusic( DeathStage4, force: true, volumeMult: 1.28f );\n\t}\n\n\tpublic static void PlayUi( string soundEvent )\n\t{\n\t\tif ( string.IsNullOrWhiteSpace( soundEvent ) )\n\t\t\treturn;\n\n\t\t// Never let the bark leak through a mis-wire \u2014 only PlayDogeBark uses it.\n\t\tif ( string.Equals( soundEvent, Doge, StringComparison.OrdinalIgnoreCase ) )\n\t\t{\n\t\t\tPlayDogeBark();\n\t\t\treturn;\n\t\t}\n\n\t\tvar handle = PlayLocal( soundEvent, GameSettings.SfxVolume );\n\t\t// One soft fail is fine \u2014 avoid console spam for SFX.\n\t\t_ = handle;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Pack-open ticks. Step 0 deal \u00B7 1 unfold \u00B7 2 fish \u00B7 3 rarity stamp.\n\t/// Pitch climbs so the unfold feels like a slot stopping.\n\t/// \u003C/summary\u003E\n\tpublic static void PlayBuyTick( int step, FishRarity rarity )\n\t{\n\t\tvar vol = Math.Clamp( GameSettings.SfxVolume, 0f, 1f );\n\t\tstep = Math.Clamp( step, 0, 3 );\n\t\tvar pitch = 0.92f \u002B step * 0.08f;\n\n\t\tif ( step == 0 )\n\t\t{\n\t\t\tPlayPitched( Pop, vol * 0.88f, pitch );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( step == 1 )\n\t\t{\n\t\t\tPlayPitched( Pop, vol * 0.72f, pitch \u002B 0.04f );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( step == 2 )\n\t\t{\n\t\t\tPlayPitched( Trade, vol * 0.95f, 1.02f );\n\t\t\treturn;\n\t\t}\n\n\t\t// Stamp \u2014 commons thud, jackpots explode.\n\t\tif ( rarity \u003E= FishRarity.Mythical )\n\t\t{\n\t\t\tPlayBigWin( bark: true );\n\t\t\tPlayPitched( Favorite, vol * 0.85f, 1.12f );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( rarity \u003E= FishRarity.Legendary )\n\t\t{\n\t\t\tPlayBigWin( bark: false );\n\t\t\tPlayPitched( Favorite, vol * 0.95f, 1.05f );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( rarity \u003E= FishRarity.PaintedRare )\n\t\t{\n\t\t\tPlayLayered( Achievement, Confirm, 0.55f );\n\t\t\tPulseMusic( 0.14f, 0.7f );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( rarity \u003E= FishRarity.Rare )\n\t\t{\n\t\t\tPlayLayered( Upgrade, Confirm, 0.5f );\n\t\t\tPulseMusic( 0.1f, 0.55f );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( rarity \u003E= FishRarity.Uncommon )\n\t\t{\n\t\t\tPlayPitched( Confirm, vol, 1.1f );\n\t\t\treturn;\n\t\t}\n\n\t\t// Common: quiet pop so the next click is the thought.\n\t\tPlayPitched( Pop, vol * 0.7f, 0.88f );\n\t}\n\n\tstatic void PlayPitched( string soundEvent, float volume, float pitch )\n\t{\n\t\t_ = PlayLocal( soundEvent, volume, pitch );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Dogecoin jackpot bark. Call only for meaningful \u00D0 wins (fight prize, shark loot, big milestone).\n\t/// \u003C/summary\u003E\n\tpublic static void PlayDogeBark()\n\t{\n\t\tvar handle = PlayLocal( Doge, GameSettings.SfxVolume );\n\t\t_ = handle;\n\t}\n\n\t/// \u003Csummary\u003ESoft coin / trade ping for Dogecoin earn or spend feedback.\u003C/summary\u003E\n\tpublic static void PlayCoin()\n\t{\n\t\tvar handle = PlayLocal( Coin, GameSettings.SfxVolume * 0.9f );\n\t\t_ = handle;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Layer two SFX so big moments land even if one event fails to load.\n\t/// Primary at full SFX vol; secondary quieter and slightly delayed feel via volume only.\n\t/// \u003C/summary\u003E\n\tpublic static void PlayLayered( string primary, string secondary, float secondaryMul = 0.55f )\n\t{\n\t\tvar vol = Math.Clamp( GameSettings.SfxVolume, 0f, 1f );\n\t\tif ( !string.IsNullOrWhiteSpace( primary ) )\n\t\t\t_ = PlayLocal( primary, vol );\n\t\tif ( !string.IsNullOrWhiteSpace( secondary ) \u0026\u0026 secondaryMul \u003E 0.01f )\n\t\t\t_ = PlayLocal( secondary, vol * Math.Clamp( secondaryMul, 0.1f, 1f ) );\n\t}\n\n\t/// \u003Csummary\u003ESell / stage / fight win \u2014 coin \u002B upgrade so the wallet *and* the room react.\u003C/summary\u003E\n\tpublic static void PlayBigCash()\n\t{\n\t\tPlayLayered( Coin, Upgrade, 0.5f );\n\t\tPulseMusic( 0.12f, 0.7f );\n\t}\n\n\t/// \u003Csummary\u003ECombine birth / championship / shark kill \u2014 bark-tier without always barking.\u003C/summary\u003E\n\tpublic static void PlayBigWin( bool bark = false )\n\t{\n\t\tif ( bark )\n\t\t{\n\t\t\tPlayDogeBark();\n\t\t\t_ = PlayLocal( Upgrade, Math.Clamp( GameSettings.SfxVolume * 0.7f, 0f, 1f ) );\n\t\t\tPulseMusic( 0.22f, 1.2f );\n\t\t\treturn;\n\t\t}\n\t\tPlayLayered( Confirm, Coin, 0.65f );\n\t\tPulseMusic( 0.16f, 0.9f );\n\t}\n\n\t/// \u003Csummary\u003EEgg hatch \u2014 chirp, hotter if the guppy is spicy.\u003C/summary\u003E\n\tpublic static void PlayHatch( FishRarity rarity )\n\t{\n\t\tvar vol = Math.Clamp( GameSettings.SfxVolume, 0f, 1f );\n\t\tPlayPitched( Hatch, vol, rarity \u003E= FishRarity.Rare ? 1.12f : 1f );\n\t\tif ( rarity \u003E= FishRarity.Legendary )\n\t\t\tPlayPitched( Favorite, vol * 0.7f, 1.04f );\n\t}\n\n\t/// \u003Csummary\u003ECombine birth \u2014 growl over the upgrade sting.\u003C/summary\u003E\n\tpublic static void PlayFusion()\n\t{\n\t\tPlayLayered( Growl, Upgrade, 0.55f );\n\t\tPulseMusic( 0.18f, 0.9f );\n\t}\n\n\t/// \u003Csummary\u003ESalt tank online \u2014 short dolphin.\u003C/summary\u003E\n\tpublic static void PlaySaltUnlock()\n\t{\n\t\tPlayLayered( Dolphin, Upgrade, 0.5f );\n\t\tPulseMusic( 0.12f, 0.7f );\n\t}\n\n\t/// \u003Csummary\u003EBlue whale bought.\u003C/summary\u003E\n\tpublic static void PlayWhale()\n\t{\n\t\tPlayLayered( Whale, Coin, 0.4f );\n\t\tPulseMusic( 0.16f, 1.0f );\n\t}\n\n\t/// \u003Csummary\u003EShark enters \u2014 alert \u002B notice so it never feels silent.\u003C/summary\u003E\n\tpublic static void PlaySharkAlert()\n\t{\n\t\tPlayLayered( AlertWarning, Notice, 0.45f );\n\t}\n\n\t/// \u003Csummary\u003ETraining minigame \u2014 clear hit / miss feedback (slightly hotter than chrome UI).\u003C/summary\u003E\n\tpublic static void PlayTrainRep( bool hit, bool perfect = false )\n\t{\n\t\tvar vol = Math.Clamp( GameSettings.SfxVolume * 1.2f, 0f, 1f );\n\t\tif ( !hit )\n\t\t{\n\t\t\t_ = PlayLocal( Error, vol );\n\t\t\treturn;\n\t\t}\n\n\t\t// Perfect = brighter confirm; solid hit = upgrade sting\n\t\t_ = PlayLocal( perfect ? Confirm : Upgrade, vol );\n\t\t// Soft second layer so a hit always \u0022lands\u0022 even if one event fails\n\t\tif ( perfect )\n\t\t\t_ = PlayLocal( Notice, vol * 0.55f );\n\t}\n\n\t/// \u003Csummary\u003EEnd of a 3-rep training set.\u003C/summary\u003E\n\tpublic static void PlayTrainSessionDone( bool anyHits )\n\t{\n\t\tvar vol = Math.Clamp( GameSettings.SfxVolume * 1.15f, 0f, 1f );\n\t\t_ = PlayLocal( anyHits ? Upgrade : Close, vol );\n\t}\n\n\tpublic static void StartMenuMusic()\n\t{\n\t\t_lastDeathStage = -1;\n\t\t_lastNoChillBed = null;\n\t\t_detonationStingLeft = 0f;\n\t\t_lastAbsurdReason = \u0022\u0022;\n\t\t_metalBedTimer = 0f;\n\t\t_activeMetalPick = null;\n\t\tStartMusic( MenuMusic, force: true );\n\t}\n\n\tpublic static void StartTankMusic()\n\t{\n\t\t_lastNoChillBed = null;\n\t\t_lastDeathStage = -1;\n\t\t_detonationStingLeft = 0f;\n\t\t_lastAbsurdReason = \u0022\u0022;\n\t\t_metalBedTimer = 0f;\n\t\t_activeMetalPick = null;\n\t\tRefreshPlayingMusic( force: true );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Next metal bed, never the same as the one just playing when possible.\n\t/// \u003C/summary\u003E\n\tstatic string NextMetalTrack()\n\t{\n\t\tif ( MetalPlaylist.Length == 0 )\n\t\t\treturn MenuMusic;\n\n\t\t// Prefer a random different track so long monster sessions don\u0027t cycle 1-2-3-1\u2026\n\t\tif ( MetalPlaylist.Length \u003E 1 )\n\t\t{\n\t\t\tvar avoid = _activeMetalPick ?? _musicEvent;\n\t\t\tfor ( var attempt = 0; attempt \u003C 8; attempt\u002B\u002B )\n\t\t\t{\n\t\t\t\tvar i = _musicRng.Next( MetalPlaylist.Length );\n\t\t\t\tvar pick = MetalPlaylist[i];\n\t\t\t\tif ( !string.Equals( pick, avoid, StringComparison.OrdinalIgnoreCase ) )\n\t\t\t\t{\n\t\t\t\t\t_metalIndex = i;\n\t\t\t\t\t_activeMetalPick = pick;\n\t\t\t\t\t_metalBedTimer = 0f;\n\t\t\t\t\treturn pick;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t_metalIndex = (_metalIndex \u002B 1) % MetalPlaylist.Length;\n\t\t_activeMetalPick = MetalPlaylist[_metalIndex];\n\t\t_metalBedTimer = 0f;\n\t\treturn _activeMetalPick;\n\t}\n\n\tstatic string CurrentMetalTrack()\n\t{\n\t\tif ( !string.IsNullOrEmpty( _activeMetalPick ) )\n\t\t\treturn _activeMetalPick;\n\t\t_activeMetalPick = MetalPlaylist[_metalIndex % MetalPlaylist.Length];\n\t\treturn _activeMetalPick;\n\t}\n\n\t/// \u003Csummary\u003EGood-boy credits \u2014 pure chill bed.\u003C/summary\u003E\n\tpublic static void StartMusicChillEnding()\n\t{\n\t\t_detonationStingLeft = 0f;\n\t\t_lastDeathStage = -1;\n\t\t_lastNoChillBed = false;\n\t\tStartMusic( ChillTankMusic, force: true, volumeMult: 1f );\n\t}\n\n\t/// \u003Csummary\u003EBad/mixed credits \u2014 metal bed (bad = death metal peak).\u003C/summary\u003E\n\tpublic static void StartMusicMetalEnding( bool peakDeathMetal )\n\t{\n\t\t_detonationStingLeft = 0f;\n\t\t_lastDeathStage = -1;\n\t\t_lastNoChillBed = true;\n\t\tif ( peakDeathMetal )\n\t\t\tStartMusic( DeathStage4, force: true, volumeMult: 1.2f );\n\t\telse\n\t\t\tStartMusic( MetalPlaylist[_metalIndex % MetalPlaylist.Length], force: true, volumeMult: 1.1f );\n\t}\n\n\t/// \u003Csummary\u003ECalled when chaos crosses chill/no-chill threshold or other systems request a recheck.\u003C/summary\u003E\n\tpublic static void OnMoodMaybeChanged( bool force = false )\n\t{\n\t\tif ( GameFlow.Screen != GameScreen.Playing )\n\t\t\treturn;\n\n\t\tRefreshPlayingMusic( force: force );\n\t}\n\n\t/// \u003Csummary\u003ECalled from DeathFeel when sickness stage changes.\u003C/summary\u003E\n\tpublic static void OnDeathStageChanged( int stage )\n\t{\n\t\tif ( GameFlow.Screen != GameScreen.Playing )\n\t\t\treturn;\n\n\t\tif ( stage == _lastDeathStage )\n\t\t\treturn;\n\n\t\t_lastDeathStage = stage;\n\t\tRefreshPlayingMusic( force: true );\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Pick the right bed for playing: dark moments beat chill.\n\t/// Metal beds rotate on enter / reason change / track end / timed swap.\n\t/// \u003C/summary\u003E\n\t/// \u003Cparam name=\u0022force\u0022\u003ERe-resolve mood even if already playing.\u003C/param\u003E\n\t/// \u003Cparam name=\u0022rotateMetal\u0022\u003EAdvance to a different metal track while staying no-chill.\u003C/param\u003E\n\tpublic static void RefreshPlayingMusic( bool force = false, bool rotateMetal = false )\n\t{\n\t\tif ( GameFlow.Screen != GameScreen.Playing )\n\t\t\treturn;\n\n\t\tvar want = ResolvePlayingBed( out var volumeMult, out var deathStage, out var noChillBed, out var reason );\n\t\tif ( want is null )\n\t\t\treturn;\n\n\t\tvar deathChanged = deathStage != _lastDeathStage;\n\t\tvar moodChanged = _lastNoChillBed != noChillBed;\n\t\tvar reasonChanged = reason != _lastAbsurdReason;\n\t\tvar enteringMetal = noChillBed \u0026\u0026 deathStage \u003C= 0 \u0026\u0026 !DetonationStingActive\n\t\t\t\u0026\u0026 (moodChanged || _lastNoChillBed != true);\n\t\tvar metalPool = noChillBed \u0026\u0026 deathStage \u003C= 0 \u0026\u0026 !DetonationStingActive\n\t\t\t\u0026\u0026 reason is not \u0022dying\u0022 and not \u0022detonation\u0022;\n\n\t\t// Pick / rotate metal playlist tracks (not the fixed death-ramp beds).\n\t\tif ( metalPool )\n\t\t{\n\t\t\tif ( rotateMetal || enteringMetal || reasonChanged || string.IsNullOrEmpty( _activeMetalPick ) )\n\t\t\t\twant = NextMetalTrack();\n\t\t\telse\n\t\t\t\twant = CurrentMetalTrack();\n\t\t}\n\t\telse if ( !noChillBed )\n\t\t{\n\t\t\t_activeMetalPick = null;\n\t\t\t_metalBedTimer = 0f;\n\t\t}\n\n\t\tif ( !force\n\t\t\t\u0026\u0026 !deathChanged\n\t\t\t\u0026\u0026 !moodChanged\n\t\t\t\u0026\u0026 !reasonChanged\n\t\t\t\u0026\u0026 !rotateMetal\n\t\t\t\u0026\u0026 _music.IsValid()\n\t\t\t\u0026\u0026 _music.IsPlaying\n\t\t\t\u0026\u0026 _musicEvent == want )\n\t\t{\n\t\t\tApplyMusicVolume( volumeMult );\n\t\t\treturn;\n\t\t}\n\n\t\t_lastDeathStage = deathStage;\n\t\t_lastNoChillBed = noChillBed;\n\t\t_lastAbsurdReason = reason ?? \u0022\u0022;\n\t\tStartMusic( want, force: true, volumeMult: volumeMult );\n\t}\n\n\tstatic string CurrentBedLabelFor( string evt )\n\t{\n\t\tif ( string.IsNullOrEmpty( evt ) )\n\t\t\treturn \u0022\u2014\u0022;\n\t\tif ( evt.Contains( \u0022moonberry\u0022 ) )\n\t\t\treturn \u0022CHILL\u0022;\n\t\tif ( evt.Contains( \u0022gloom\u0022 ) )\n\t\t\treturn \u0022UNEASE\u0022;\n\t\tif ( evt.Contains( \u0022moonbyte\u0022 ) )\n\t\t\treturn \u0022DEATH METAL\u0022;\n\t\treturn \u0022METAL\u0022;\n\t}\n\n\tstatic string ResolvePlayingBed( out float volumeMult, out int deathStage, out bool noChillBed, out string reason )\n\t{\n\t\tdeathStage = DeathFeel.PeakStage;\n\t\tvolumeMult = 1f;\n\t\tnoChillBed = false;\n\t\treason = \u0022\u0022;\n\n\t\t// --- Detonation sting outranks everything for a few seconds ---\n\t\tif ( _detonationStingLeft \u003E 0f )\n\t\t{\n\t\t\tvolumeMult = 1.28f;\n\t\t\tnoChillBed = true;\n\t\t\treason = \u0022detonation\u0022;\n\t\t\treturn DeathStage4;\n\t\t}\n\n\t\t// --- Death ramp (from chill, this is the big gear shift) ---\n\t\tif ( deathStage \u003E= 4 )\n\t\t{\n\t\t\tvolumeMult = 1.18f;\n\t\t\tnoChillBed = true;\n\t\t\treason = \u0022dying\u0022;\n\t\t\treturn DeathStage4;\n\t\t}\n\n\t\tif ( deathStage \u003E= 3 )\n\t\t{\n\t\t\tvolumeMult = 1.12f;\n\t\t\tnoChillBed = true;\n\t\t\treason = \u0022dying\u0022;\n\t\t\treturn DeathStage3;\n\t\t}\n\n\t\tif ( deathStage \u003E= 2 )\n\t\t{\n\t\t\tvolumeMult = 1.08f;\n\t\t\tnoChillBed = true;\n\t\t\treason = \u0022dying\u0022;\n\t\t\treturn DeathStage2;\n\t\t}\n\n\t\tif ( deathStage \u003E= 1 )\n\t\t{\n\t\t\t// Leave chill: dark unease first, not full blast.\n\t\t\tvolumeMult = 1.05f;\n\t\t\tnoChillBed = true;\n\t\t\treason = \u0022dying\u0022;\n\t\t\treturn DeathStage1;\n\t\t}\n\n\t\t// --- Absurd / dark moments \u2192 metal no-chill beds (track chosen in RefreshPlayingMusic) ---\n\t\tif ( EvaluateAbsurd( out reason ) )\n\t\t{\n\t\t\tnoChillBed = true;\n\t\t\tvolumeMult = reason switch\n\t\t\t{\n\t\t\t\t\u0022sharks\u0022 =\u003E 1.16f,\n\t\t\t\t\u0022monsters\u0022 =\u003E 1.14f,\n\t\t\t\t\u0022meth\u0022 =\u003E 1.12f,\n\t\t\t\t\u0022meth-rush\u0022 =\u003E 1.14f,\n\t\t\t\t\u0022battle\u0022 =\u003E 1.12f,\n\t\t\t\t\u0022arena\u0022 =\u003E 1.08f,\n\t\t\t\t\u0022adventure\u0022 =\u003E 1.1f,\n\t\t\t\t\u0022m80-armed\u0022 =\u003E 1.1f,\n\t\t\t\t\u0022jacked-pack\u0022 =\u003E 1.12f,\n\t\t\t\t\u0022boom\u0022 =\u003E 1.18f,\n\t\t\t\t\u0022inject\u0022 =\u003E 1.08f,\n\t\t\t\t_ =\u003E 1.06f\n\t\t\t};\n\n\t\t\t// Placeholder \u2014 RefreshPlayingMusic swaps via NextMetalTrack / CurrentMetalTrack.\n\t\t\treturn CurrentMetalTrack();\n\t\t}\n\n\t\t// --- Calm tank \u2192 chill bed ---\n\t\tnoChillBed = false;\n\t\treason = \u0022calm\u0022;\n\t\tvolumeMult = 1f;\n\t\treturn ChillTankMusic;\n\t}\n\n\tstatic void StartMusic( string soundEvent, bool force = false, float volumeMult = 1f )\n\t{\n\t\tif ( string.IsNullOrWhiteSpace( soundEvent ) )\n\t\t\treturn;\n\n\t\tif ( !force \u0026\u0026 _music.IsValid() \u0026\u0026 _music.IsPlaying \u0026\u0026 _musicEvent == soundEvent )\n\t\t{\n\t\t\tApplyMusicVolume( volumeMult );\n\t\t\treturn;\n\t\t}\n\n\t\t// Don\u0027t thrash the mixer every frame if a track failed to resolve.\n\t\tif ( !force \u0026\u0026 _musicRetryCooldown \u003E 0f \u0026\u0026 _musicEvent == soundEvent \u0026\u0026 !_music.IsValid() )\n\t\t\treturn;\n\n\t\tStopMusic();\n\n\t\tvar vol = GameSettings.MusicVolume * volumeMult;\n\t\t_music = PlayLocal( soundEvent, vol );\n\t\t_musicEvent = soundEvent;\n\n\t\tif ( !_music.IsValid() )\n\t\t{\n\t\t\t_musicRetryCooldown = 2.5f;\n\t\t\tif ( _lastMissingMusic != soundEvent )\n\t\t\t{\n\t\t\t\t_lastMissingMusic = soundEvent;\n\t\t\t\tLog.Warning( $\u0022[NO-CHILLquarium] Music missing: {soundEvent} (recompile Assets/sounds/music if needed)\u0022 );\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t_musicRetryCooldown = 0f;\n\t\t_lastMissingMusic = null;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Play a project .sound event as local 2D audio.\n\t/// Paths are under Assets/ (e.g. sounds/music/moonberry).\n\t/// Tries several path forms; failures are quiet (no resource-spam).\n\t/// \u003C/summary\u003E\n\tstatic SoundHandle PlayLocal( string path, float volume, float pitch = 1f )\n\t{\n\t\tif ( string.IsNullOrWhiteSpace( path ) )\n\t\t\treturn default;\n\n\t\tvar clean = path.Trim().Replace( \u0027\\\\\u0027, \u0027/\u0027 );\n\t\tif ( clean.EndsWith( \u0022.sound\u0022, StringComparison.OrdinalIgnoreCase ) )\n\t\t\tclean = clean[..^6];\n\t\tif ( clean.EndsWith( \u0022.vsnd\u0022, StringComparison.OrdinalIgnoreCase ) )\n\t\t\tclean = clean[..^5];\n\n\t\t// Path forms S\u0026box commonly accepts for SoundEvents.\n\t\tvar candidates = new[]\n\t\t{\n\t\t\tclean \u002B \u0022.sound\u0022,\n\t\t\tclean,\n\t\t\t\u0022sounds/\u0022 \u002B clean.TrimStart( \u0027/\u0027 ).Replace( \u0022sounds/\u0022, \u0022\u0022 ) \u002B \u0022.sound\u0022,\n\t\t\tclean \u002B \u0022.vsnd\u0022,\n\t\t};\n\n\t\tforeach ( var candidate in candidates )\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tif ( ResourceLibrary.Get\u003CSoundEvent\u003E( candidate ) is SoundEvent evt )\n\t\t\t\t{\n\t\t\t\t\tvar fromEvt = Sound.Play( evt, Vector3.Zero );\n\t\t\t\t\tif ( fromEvt.IsValid() )\n\t\t\t\t\t{\n\t\t\t\t\t\tfromEvt.ListenLocal = true;\n\t\t\t\t\t\tfromEvt.SpacialBlend = 0f;\n\t\t\t\t\t\tfromEvt.Volume = volume;\n\t\t\t\t\t\tif ( pitch \u003E 0.01f \u0026\u0026 MathF.Abs( pitch - 1f ) \u003E 0.01f )\n\t\t\t\t\t\t\tfromEvt.Pitch = pitch;\n\t\t\t\t\t\treturn fromEvt;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcatch\n\t\t\t{\n\t\t\t\t// next form\n\t\t\t}\n\n\t\t\ttry\n\t\t\t{\n\t\t\t\tvar h = Sound.Play( candidate, Vector3.Zero );\n\t\t\t\tif ( !h.IsValid() )\n\t\t\t\t\tcontinue;\n\t\t\t\th.ListenLocal = true;\n\t\t\t\th.SpacialBlend = 0f;\n\t\t\t\th.Volume = volume;\n\t\t\t\tif ( pitch \u003E 0.01f \u0026\u0026 MathF.Abs( pitch - 1f ) \u003E 0.01f )\n\t\t\t\t\th.Pitch = pitch;\n\t\t\t\treturn h;\n\t\t\t}\n\t\t\tcatch\n\t\t\t{\n\t\t\t\t// next form\n\t\t\t}\n\t\t}\n\n\t\treturn default;\n\t}\n\n\tpublic static void StopMusic()\n\t{\n\t\tif ( _music.IsValid() )\n\t\t\t_music.Stop();\n\n\t\t_music = default;\n\t\t// Keep _musicEvent so retry cooldown can match the failed track.\n\t}\n\n\tpublic static void ApplyMusicVolume()\n\t{\n\t\tResolvePlayingBed( out var mult, out _, out _, out _ );\n\t\tApplyMusicVolume( mult );\n\t}\n\n\tstatic void ApplyMusicVolume( float mult )\n\t{\n\t\tif ( !_music.IsValid() )\n\t\t\treturn;\n\n\t\t_music.Volume = GameSettings.MusicVolume * mult;\n\t}\n\n\t/// \u003Csummary\u003E\n\t/// Keep bed looping; re-resolve every tick so calm \u2194 absurd swaps cleanly.\n\t/// \u003C/summary\u003E\n\tpublic static void TickMusic()\n\t{\n\t\tif ( _musicRetryCooldown \u003E 0f )\n\t\t\t_musicRetryCooldown = MathF.Max( 0f, _musicRetryCooldown - Time.Delta );\n\n\t\tif ( GameFlow.Screen is GameScreen.MainMenu or GameScreen.Settings )\n\t\t{\n\t\t\t_detonationStingLeft = 0f;\n\t\t\tif ( !_music.IsValid() || !_music.IsPlaying || _musicEvent != MenuMusic )\n\t\t\t\tStartMusic( MenuMusic, force: _musicEvent != MenuMusic );\n\t\t\treturn;\n\t\t}\n\n\t\t// Ending screens own their own bed (chill vs metal) \u2014 just keep it looping.\n\t\tif ( GameFlow.Screen == GameScreen.Ending )\n\t\t{\n\t\t\tif ( !_music.IsValid() || !_music.IsPlaying )\n\t\t\t{\n\t\t\t\tif ( EndingSystem.Kind == EndingKind.GoodBoy )\n\t\t\t\t\tStartMusicChillEnding();\n\t\t\t\telse\n\t\t\t\t\tStartMusicMetalEnding( EndingSystem.Kind == EndingKind.BadBoy );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tApplyMusicVolume( EndingSystem.Kind == EndingKind.BadBoy ? 1.15f : 1f );\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif ( GameFlow.Screen != GameScreen.Playing )\n\t\t\treturn;\n\n\t\t// Count down detonation sting, then hand bus back.\n\t\tif ( _detonationStingLeft \u003E 0f )\n\t\t{\n\t\t\t_detonationStingLeft -= Time.Delta;\n\t\t\tif ( _detonationStingLeft \u003C= 0f )\n\t\t\t{\n\t\t\t\t_detonationStingLeft = 0f;\n\t\t\t\tRefreshPlayingMusic( force: true );\n\t\t\t}\n\t\t}\n\n\t\tvar want = ResolvePlayingBed( out var mult, out var deathStage, out var noChillBed, out var reason );\n\n\t\t// While stuck in no-chill (e.g. monsters), rotate metal beds on a timer.\n\t\tif ( noChillBed \u0026\u0026 deathStage \u003C= 0 \u0026\u0026 !DetonationStingActive\n\t\t\t\u0026\u0026 reason is not \u0022dying\u0022 and not \u0022detonation\u0022 )\n\t\t{\n\t\t\t_metalBedTimer \u002B= Time.Delta;\n\t\t\tif ( _metalBedTimer \u003E= MetalRotateSeconds )\n\t\t\t{\n\t\t\t\t_metalBedTimer = 0f;\n\t\t\t\tRefreshPlayingMusic( force: true, rotateMetal: true );\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t_metalBedTimer = 0f;\n\t\t}\n\n\t\t// Pulse decay \u2014 big gameplay moments kick the bed.\n\t\tif ( _pulseTimer \u003E 0f )\n\t\t{\n\t\t\t_pulseTimer = MathF.Max( 0f, _pulseTimer - Time.Delta );\n\t\t\tif ( _pulseTimer \u003C= 0f )\n\t\t\t\t_pulseBoost = 0f;\n\t\t\telse\n\t\t\t\t_pulseBoost *= 0.985f; // soft falloff while timer runs\n\t\t}\n\n\t\t// Continuous volume ride as death / sting / sharks / chaos / pulse.\n\t\tif ( _music.IsValid() \u0026\u0026 _music.IsPlaying )\n\t\t{\n\t\t\tvar progressBoost = 1f;\n\t\t\tif ( deathStage \u003E 0 )\n\t\t\t\tprogressBoost \u002B= DeathFeel.PeakProgress * 0.16f;\n\t\t\tif ( DetonationStingActive )\n\t\t\t\tprogressBoost \u002B= 0.1f;\n\t\t\tif ( SharkSystem.HasSharks )\n\t\t\t\tprogressBoost \u002B= 0.08f \u002B Math.Min( 0.06f, SharkSystem.SharkCount * 0.02f );\n\t\t\tif ( TankSim.HasAnyMonster )\n\t\t\t\tprogressBoost \u002B= 0.06f;\n\t\t\tif ( Chaos.Value \u003E 40f )\n\t\t\t\tprogressBoost \u002B= Math.Clamp( (Chaos.Value - 40f) / 200f, 0f, 0.1f );\n\t\t\tif ( BattleSystem.Phase == BattlePhase.Fighting )\n\t\t\t\tprogressBoost \u002B= 0.07f;\n\t\t\tif ( _pulseBoost \u003E 0.01f )\n\t\t\t\tprogressBoost \u002B= _pulseBoost;\n\t\t\t// Thriving calm porch: slight chill bed lift so silence never feels empty.\n\t\t\tif ( !noChillBed \u0026\u0026 TankSim.FishCount \u003E= 3 )\n\t\t\t\tprogressBoost \u002B= 0.03f;\n\t\t\tApplyMusicVolume( mult * progressBoost );\n\t\t}\n\n\t\t// Track ended while still no-chill \u2192 next metal bed (not the same one).\n\t\tif ( noChillBed \u0026\u0026 deathStage \u003C= 0 \u0026\u0026 !DetonationStingActive\n\t\t\t\u0026\u0026 reason is not \u0022dying\u0022 and not \u0022detonation\u0022\n\t\t\t\u0026\u0026 (!_music.IsValid() || !_music.IsPlaying) )\n\t\t{\n\t\t\tRefreshPlayingMusic( force: true, rotateMetal: true );\n\t\t\treturn;\n\t\t}\n\n\t\t// Bed mismatch (calm \u2194 absurd) or wrong track \u2014 switch immediately.\n\t\tif ( _music.IsValid() \u0026\u0026 _music.IsPlaying \u0026\u0026 _musicEvent == want\n\t\t\t\u0026\u0026 _lastNoChillBed == noChillBed\n\t\t\t\u0026\u0026 _lastAbsurdReason == (reason ?? \u0022\u0022) )\n\t\t\treturn;\n\n\t\tRefreshPlayingMusic( force: true );\n\t}\n}\n"}]}